|
|
/* 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 "FAudioFX.h"
|
|
|
#include "FACT_internal.h"
|
|
|
|
|
|
/* AudioEngine implementation */
|
|
|
|
|
|
uint32_t FACTCreateEngine(
|
|
|
uint32_t dwCreationFlags,
|
|
|
FACTAudioEngine **ppEngine
|
|
|
) {
|
|
|
return FACTCreateEngineWithCustomAllocatorEXT(
|
|
|
dwCreationFlags,
|
|
|
ppEngine,
|
|
|
FAudio_malloc,
|
|
|
FAudio_free,
|
|
|
FAudio_realloc
|
|
|
);
|
|
|
}
|
|
|
|
|
|
uint32_t FACTCreateEngineWithCustomAllocatorEXT(
|
|
|
uint32_t dwCreationFlags,
|
|
|
FACTAudioEngine **ppEngine,
|
|
|
FAudioMallocFunc customMalloc,
|
|
|
FAudioFreeFunc customFree,
|
|
|
FAudioReallocFunc customRealloc
|
|
|
) {
|
|
|
/* TODO: Anything fun with dwCreationFlags? */
|
|
|
FAudio_PlatformAddRef();
|
|
|
*ppEngine = (FACTAudioEngine*) customMalloc(sizeof(FACTAudioEngine));
|
|
|
if (*ppEngine == NULL)
|
|
|
{
|
|
|
return -1; /* TODO: E_OUTOFMEMORY */
|
|
|
}
|
|
|
FAudio_zero(*ppEngine, sizeof(FACTAudioEngine));
|
|
|
(*ppEngine)->sbLock = FAudio_PlatformCreateMutex();
|
|
|
(*ppEngine)->wbLock = FAudio_PlatformCreateMutex();
|
|
|
(*ppEngine)->apiLock = FAudio_PlatformCreateMutex();
|
|
|
(*ppEngine)->pMalloc = customMalloc;
|
|
|
(*ppEngine)->pFree = customFree;
|
|
|
(*ppEngine)->pRealloc = customRealloc;
|
|
|
(*ppEngine)->refcount = 1;
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTAudioEngine_AddRef(FACTAudioEngine *pEngine)
|
|
|
{
|
|
|
FAudio_PlatformLockMutex(pEngine->apiLock);
|
|
|
pEngine->refcount += 1;
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
return pEngine->refcount;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTAudioEngine_Release(FACTAudioEngine *pEngine)
|
|
|
{
|
|
|
FAudio_PlatformLockMutex(pEngine->apiLock);
|
|
|
pEngine->refcount -= 1;
|
|
|
if (pEngine->refcount > 0)
|
|
|
{
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
return pEngine->refcount;
|
|
|
}
|
|
|
FACTAudioEngine_ShutDown(pEngine);
|
|
|
FAudio_PlatformDestroyMutex(pEngine->sbLock);
|
|
|
FAudio_PlatformDestroyMutex(pEngine->wbLock);
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
FAudio_PlatformDestroyMutex(pEngine->apiLock);
|
|
|
pEngine->pFree(pEngine);
|
|
|
FAudio_PlatformRelease();
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTAudioEngine_GetRendererCount(
|
|
|
FACTAudioEngine *pEngine,
|
|
|
uint16_t *pnRendererCount
|
|
|
) {
|
|
|
FAudio_PlatformLockMutex(pEngine->apiLock);
|
|
|
*pnRendererCount = (uint16_t) FAudio_PlatformGetDeviceCount();
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTAudioEngine_GetRendererDetails(
|
|
|
FACTAudioEngine *pEngine,
|
|
|
uint16_t nRendererIndex,
|
|
|
FACTRendererDetails *pRendererDetails
|
|
|
) {
|
|
|
FAudioDeviceDetails deviceDetails;
|
|
|
|
|
|
FAudio_PlatformLockMutex(pEngine->apiLock);
|
|
|
|
|
|
FAudio_PlatformGetDeviceDetails(
|
|
|
nRendererIndex,
|
|
|
&deviceDetails
|
|
|
);
|
|
|
FAudio_memcpy(
|
|
|
pRendererDetails->rendererID,
|
|
|
deviceDetails.DeviceID,
|
|
|
sizeof(int16_t) * 0xFF
|
|
|
);
|
|
|
FAudio_memcpy(
|
|
|
pRendererDetails->displayName,
|
|
|
deviceDetails.DisplayName,
|
|
|
sizeof(int16_t) * 0xFF
|
|
|
);
|
|
|
/* FIXME: Which defaults does it care about...? */
|
|
|
pRendererDetails->defaultDevice = (deviceDetails.Role & (
|
|
|
FAudioGlobalDefaultDevice |
|
|
|
FAudioDefaultGameDevice
|
|
|
)) != 0;
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTAudioEngine_GetFinalMixFormat(
|
|
|
FACTAudioEngine *pEngine,
|
|
|
FAudioWaveFormatExtensible *pFinalMixFormat
|
|
|
) {
|
|
|
FAudio_PlatformLockMutex(pEngine->apiLock);
|
|
|
FAudio_memcpy(
|
|
|
pFinalMixFormat,
|
|
|
&pEngine->audio->mixFormat,
|
|
|
sizeof(FAudioWaveFormatExtensible)
|
|
|
);
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTAudioEngine_Initialize(
|
|
|
FACTAudioEngine *pEngine,
|
|
|
const FACTRuntimeParameters *pParams
|
|
|
) {
|
|
|
uint32_t parseRet;
|
|
|
uint32_t deviceIndex;
|
|
|
FAudioVoiceDetails masterDetails;
|
|
|
FAudioEffectDescriptor reverbDesc;
|
|
|
FAudioEffectChain reverbChain;
|
|
|
|
|
|
FAudio_PlatformLockMutex(pEngine->apiLock);
|
|
|
|
|
|
/* Parse the file */
|
|
|
parseRet = FACT_INTERNAL_ParseAudioEngine(pEngine, pParams);
|
|
|
if (parseRet != 0)
|
|
|
{
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
return parseRet;
|
|
|
}
|
|
|
|
|
|
/* Assign the callbacks */
|
|
|
pEngine->notificationCallback = pParams->fnNotificationCallback;
|
|
|
pEngine->pReadFile = pParams->fileIOCallbacks.readFileCallback;
|
|
|
pEngine->pGetOverlappedResult = pParams->fileIOCallbacks.getOverlappedResultCallback;
|
|
|
if (pEngine->pReadFile == NULL)
|
|
|
{
|
|
|
pEngine->pReadFile = FACT_INTERNAL_DefaultReadFile;
|
|
|
}
|
|
|
if (pEngine->pGetOverlappedResult == NULL)
|
|
|
{
|
|
|
pEngine->pGetOverlappedResult = FACT_INTERNAL_DefaultGetOverlappedResult;
|
|
|
}
|
|
|
|
|
|
/* Init the FAudio subsystem */
|
|
|
pEngine->audio = pParams->pXAudio2;
|
|
|
if (pEngine->audio == NULL)
|
|
|
{
|
|
|
FAudio_assert(pParams->pMasteringVoice == NULL);
|
|
|
FAudioCreate(&pEngine->audio, 0, FAUDIO_DEFAULT_PROCESSOR);
|
|
|
}
|
|
|
|
|
|
/* Create the audio device */
|
|
|
pEngine->master = pParams->pMasteringVoice;
|
|
|
if (pEngine->master == NULL)
|
|
|
{
|
|
|
if (pParams->pRendererID == NULL || pParams->pRendererID[0] == 0)
|
|
|
{
|
|
|
deviceIndex = 0;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
deviceIndex = pParams->pRendererID[0] - L'0';
|
|
|
if (deviceIndex > FAudio_PlatformGetDeviceCount())
|
|
|
{
|
|
|
deviceIndex = 0;
|
|
|
}
|
|
|
}
|
|
|
if (FAudio_CreateMasteringVoice(
|
|
|
pEngine->audio,
|
|
|
&pEngine->master,
|
|
|
FAUDIO_DEFAULT_CHANNELS,
|
|
|
FAUDIO_DEFAULT_SAMPLERATE,
|
|
|
0,
|
|
|
deviceIndex,
|
|
|
NULL
|
|
|
) != 0) {
|
|
|
FAudio_Release(pEngine->audio);
|
|
|
return FAUDIO_E_INVALID_CALL;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* Create the reverb effect, if applicable */
|
|
|
if (pEngine->dspPresetCount > 0) /* Never more than 1...? */
|
|
|
{
|
|
|
FAudioVoice_GetVoiceDetails(pEngine->master, &masterDetails);
|
|
|
|
|
|
/* Reverb effect chain... */
|
|
|
FAudioCreateReverb(&reverbDesc.pEffect, 0);
|
|
|
reverbDesc.InitialState = 1;
|
|
|
reverbDesc.OutputChannels = (masterDetails.InputChannels == 6) ? 6 : 1;
|
|
|
reverbChain.EffectCount = 1;
|
|
|
reverbChain.pEffectDescriptors = &reverbDesc;
|
|
|
|
|
|
/* Reverb submix voice... */
|
|
|
FAudio_CreateSubmixVoice(
|
|
|
pEngine->audio,
|
|
|
&pEngine->reverbVoice,
|
|
|
1, /* Reverb will be omnidirectional */
|
|
|
masterDetails.InputSampleRate,
|
|
|
0,
|
|
|
0,
|
|
|
NULL,
|
|
|
&reverbChain
|
|
|
);
|
|
|
|
|
|
/* We can release now, the submix owns this! */
|
|
|
FAPOBase_Release((FAPOBase*) reverbDesc.pEffect);
|
|
|
}
|
|
|
|
|
|
pEngine->initialized = 1;
|
|
|
pEngine->apiThread = FAudio_PlatformCreateThread(
|
|
|
FACT_INTERNAL_APIThread,
|
|
|
"FACT Thread",
|
|
|
pEngine
|
|
|
);
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTAudioEngine_ShutDown(FACTAudioEngine *pEngine)
|
|
|
{
|
|
|
uint32_t i, refcount;
|
|
|
FAudioMutex mutex;
|
|
|
FAudioMallocFunc pMalloc;
|
|
|
FAudioFreeFunc pFree;
|
|
|
FAudioReallocFunc pRealloc;
|
|
|
|
|
|
/* Close thread, then lock ASAP */
|
|
|
pEngine->initialized = 0;
|
|
|
FAudio_PlatformWaitThread(pEngine->apiThread, NULL);
|
|
|
FAudio_PlatformLockMutex(pEngine->apiLock);
|
|
|
|
|
|
/* Stop the platform stream before freeing stuff! */
|
|
|
if (pEngine->audio != NULL)
|
|
|
{
|
|
|
FAudio_StopEngine(pEngine->audio);
|
|
|
}
|
|
|
|
|
|
/* This method destroys all existing cues, sound banks, and wave banks.
|
|
|
* It blocks until all cues are destroyed.
|
|
|
*/
|
|
|
while (pEngine->wbList != NULL)
|
|
|
{
|
|
|
FACTWaveBank_Destroy((FACTWaveBank*) pEngine->wbList->entry);
|
|
|
}
|
|
|
while (pEngine->sbList != NULL)
|
|
|
{
|
|
|
FACTSoundBank_Destroy((FACTSoundBank*) pEngine->sbList->entry);
|
|
|
}
|
|
|
|
|
|
/* Category data */
|
|
|
for (i = 0; i < pEngine->categoryCount; i += 1)
|
|
|
{
|
|
|
pEngine->pFree(pEngine->categoryNames[i]);
|
|
|
}
|
|
|
pEngine->pFree(pEngine->categoryNames);
|
|
|
pEngine->pFree(pEngine->categories);
|
|
|
|
|
|
/* Variable data */
|
|
|
for (i = 0; i < pEngine->variableCount; i += 1)
|
|
|
{
|
|
|
pEngine->pFree(pEngine->variableNames[i]);
|
|
|
}
|
|
|
pEngine->pFree(pEngine->variableNames);
|
|
|
pEngine->pFree(pEngine->variables);
|
|
|
pEngine->pFree(pEngine->globalVariableValues);
|
|
|
|
|
|
/* RPC data */
|
|
|
for (i = 0; i < pEngine->rpcCount; i += 1)
|
|
|
{
|
|
|
pEngine->pFree(pEngine->rpcs[i].points);
|
|
|
}
|
|
|
pEngine->pFree(pEngine->rpcs);
|
|
|
pEngine->pFree(pEngine->rpcCodes);
|
|
|
|
|
|
/* DSP data */
|
|
|
for (i = 0; i < pEngine->dspPresetCount; i += 1)
|
|
|
{
|
|
|
pEngine->pFree(pEngine->dspPresets[i].parameters);
|
|
|
}
|
|
|
pEngine->pFree(pEngine->dspPresets);
|
|
|
pEngine->pFree(pEngine->dspPresetCodes);
|
|
|
|
|
|
/* Audio resources */
|
|
|
if (pEngine->reverbVoice != NULL)
|
|
|
{
|
|
|
FAudioVoice_DestroyVoice(pEngine->reverbVoice);
|
|
|
}
|
|
|
if (pEngine->master != NULL)
|
|
|
{
|
|
|
FAudioVoice_DestroyVoice(pEngine->master);
|
|
|
}
|
|
|
if (pEngine->audio != NULL)
|
|
|
{
|
|
|
FAudio_Release(pEngine->audio);
|
|
|
}
|
|
|
|
|
|
/* Finally. */
|
|
|
refcount = pEngine->refcount;
|
|
|
mutex = pEngine->apiLock;
|
|
|
pMalloc = pEngine->pMalloc;
|
|
|
pFree = pEngine->pFree;
|
|
|
pRealloc = pEngine->pRealloc;
|
|
|
FAudio_zero(pEngine, sizeof(FACTAudioEngine));
|
|
|
pEngine->pMalloc = pMalloc;
|
|
|
pEngine->pFree = pFree;
|
|
|
pEngine->pRealloc = pRealloc;
|
|
|
pEngine->refcount = refcount;
|
|
|
pEngine->apiLock = mutex;
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTAudioEngine_DoWork(FACTAudioEngine *pEngine)
|
|
|
{
|
|
|
uint8_t i;
|
|
|
FACTCue *cue;
|
|
|
LinkedList *list;
|
|
|
|
|
|
FAudio_PlatformLockMutex(pEngine->apiLock);
|
|
|
|
|
|
list = pEngine->sbList;
|
|
|
while (list != NULL)
|
|
|
{
|
|
|
cue = ((FACTSoundBank*) list->entry)->cueList;
|
|
|
while (cue != NULL)
|
|
|
{
|
|
|
if (cue->playingSound != NULL)
|
|
|
for (i = 0; i < cue->playingSound->sound->trackCount; i += 1)
|
|
|
{
|
|
|
if ( cue->playingSound->tracks[i].upcomingWave.wave == NULL &&
|
|
|
cue->playingSound->tracks[i].waveEvtInst->loopCount > 0 )
|
|
|
{
|
|
|
FACT_INTERNAL_GetNextWave(
|
|
|
cue,
|
|
|
cue->playingSound->sound,
|
|
|
&cue->playingSound->sound->tracks[i],
|
|
|
&cue->playingSound->tracks[i],
|
|
|
cue->playingSound->tracks[i].waveEvt,
|
|
|
cue->playingSound->tracks[i].waveEvtInst
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
cue = cue->next;
|
|
|
}
|
|
|
list = list->next;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTAudioEngine_CreateSoundBank(
|
|
|
FACTAudioEngine *pEngine,
|
|
|
const void *pvBuffer,
|
|
|
uint32_t dwSize,
|
|
|
uint32_t dwFlags,
|
|
|
uint32_t dwAllocAttributes,
|
|
|
FACTSoundBank **ppSoundBank
|
|
|
) {
|
|
|
uint32_t retval;
|
|
|
FAudio_PlatformLockMutex(pEngine->apiLock);
|
|
|
retval = FACT_INTERNAL_ParseSoundBank(
|
|
|
pEngine,
|
|
|
pvBuffer,
|
|
|
dwSize,
|
|
|
ppSoundBank
|
|
|
);
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
return retval;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTAudioEngine_CreateInMemoryWaveBank(
|
|
|
FACTAudioEngine *pEngine,
|
|
|
const void *pvBuffer,
|
|
|
uint32_t dwSize,
|
|
|
uint32_t dwFlags,
|
|
|
uint32_t dwAllocAttributes,
|
|
|
FACTWaveBank **ppWaveBank
|
|
|
) {
|
|
|
uint32_t retval;
|
|
|
FAudio_PlatformLockMutex(pEngine->apiLock);
|
|
|
retval = FACT_INTERNAL_ParseWaveBank(
|
|
|
pEngine,
|
|
|
FAudio_memopen((void*) pvBuffer, dwSize),
|
|
|
0,
|
|
|
0,
|
|
|
FACT_INTERNAL_DefaultReadFile,
|
|
|
FACT_INTERNAL_DefaultGetOverlappedResult,
|
|
|
0,
|
|
|
ppWaveBank
|
|
|
);
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
return retval;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTAudioEngine_CreateStreamingWaveBank(
|
|
|
FACTAudioEngine *pEngine,
|
|
|
const FACTStreamingParameters *pParms,
|
|
|
FACTWaveBank **ppWaveBank
|
|
|
) {
|
|
|
uint32_t retval, packetSize;
|
|
|
FAudio_PlatformLockMutex(pEngine->apiLock);
|
|
|
if ( pEngine->pReadFile == FACT_INTERNAL_DefaultReadFile &&
|
|
|
pEngine->pGetOverlappedResult == FACT_INTERNAL_DefaultGetOverlappedResult )
|
|
|
{
|
|
|
/* Our I/O doesn't care about packets, set to 0 as an optimization */
|
|
|
packetSize = 0;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
packetSize = pParms->packetSize * 2048;
|
|
|
}
|
|
|
retval = FACT_INTERNAL_ParseWaveBank(
|
|
|
pEngine,
|
|
|
pParms->file,
|
|
|
pParms->offset,
|
|
|
packetSize,
|
|
|
pEngine->pReadFile,
|
|
|
pEngine->pGetOverlappedResult,
|
|
|
1,
|
|
|
ppWaveBank
|
|
|
);
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
return retval;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTAudioEngine_PrepareWave(
|
|
|
FACTAudioEngine *pEngine,
|
|
|
uint32_t dwFlags,
|
|
|
const char *szWavePath,
|
|
|
uint32_t wStreamingPacketSize,
|
|
|
uint32_t dwAlignment,
|
|
|
uint32_t dwPlayOffset,
|
|
|
uint8_t nLoopCount,
|
|
|
FACTWave **ppWave
|
|
|
) {
|
|
|
/* TODO: FACTWave */
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTAudioEngine_PrepareInMemoryWave(
|
|
|
FACTAudioEngine *pEngine,
|
|
|
uint32_t dwFlags,
|
|
|
FACTWaveBankEntry entry,
|
|
|
uint32_t *pdwSeekTable, /* Optional! */
|
|
|
uint8_t *pbWaveData,
|
|
|
uint32_t dwPlayOffset,
|
|
|
uint8_t nLoopCount,
|
|
|
FACTWave **ppWave
|
|
|
) {
|
|
|
/* TODO: FACTWave */
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTAudioEngine_PrepareStreamingWave(
|
|
|
FACTAudioEngine *pEngine,
|
|
|
uint32_t dwFlags,
|
|
|
FACTWaveBankEntry entry,
|
|
|
FACTStreamingParameters streamingParams,
|
|
|
uint32_t dwAlignment,
|
|
|
uint32_t *pdwSeekTable, /* Optional! */
|
|
|
uint8_t *pbWaveData,
|
|
|
uint32_t dwPlayOffset,
|
|
|
uint8_t nLoopCount,
|
|
|
FACTWave **ppWave
|
|
|
) {
|
|
|
/* TODO: FACTWave */
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTAudioEngine_RegisterNotification(
|
|
|
FACTAudioEngine *pEngine,
|
|
|
const FACTNotificationDescription *pNotificationDescription
|
|
|
) {
|
|
|
FAudio_assert(pEngine != NULL);
|
|
|
FAudio_assert(pNotificationDescription != NULL);
|
|
|
FAudio_assert(pEngine->notificationCallback != NULL);
|
|
|
|
|
|
FAudio_PlatformLockMutex(pEngine->apiLock);
|
|
|
|
|
|
if (pNotificationDescription->type == FACTNOTIFICATIONTYPE_CUEDESTROYED)
|
|
|
{
|
|
|
if (pNotificationDescription->flags & FACT_FLAG_NOTIFICATION_PERSIST)
|
|
|
{
|
|
|
pEngine->notifications |= NOTIFY_CUEDESTROY;
|
|
|
pEngine->cue_context = pNotificationDescription->pvContext;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
pNotificationDescription->pCue->notifyOnDestroy = 1;
|
|
|
pNotificationDescription->pCue->usercontext = pNotificationDescription->pvContext;
|
|
|
}
|
|
|
}
|
|
|
else if (pNotificationDescription->type == FACTNOTIFICATIONTYPE_SOUNDBANKDESTROYED)
|
|
|
{
|
|
|
if (pNotificationDescription->flags & FACT_FLAG_NOTIFICATION_PERSIST)
|
|
|
{
|
|
|
pEngine->notifications |= NOTIFY_SOUNDBANKDESTROY;
|
|
|
pEngine->sb_context = pNotificationDescription->pvContext;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
pNotificationDescription->pSoundBank->notifyOnDestroy = 1;
|
|
|
pNotificationDescription->pSoundBank->usercontext = pNotificationDescription->pvContext;
|
|
|
}
|
|
|
}
|
|
|
else if (pNotificationDescription->type == FACTNOTIFICATIONTYPE_WAVEBANKDESTROYED)
|
|
|
{
|
|
|
if (pNotificationDescription->flags & FACT_FLAG_NOTIFICATION_PERSIST)
|
|
|
{
|
|
|
pEngine->notifications |= NOTIFY_WAVEBANKDESTROY;
|
|
|
pEngine->wb_context = pNotificationDescription->pvContext;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
pNotificationDescription->pWaveBank->notifyOnDestroy = 1;
|
|
|
pNotificationDescription->pWaveBank->usercontext = pNotificationDescription->pvContext;
|
|
|
}
|
|
|
}
|
|
|
else if (pNotificationDescription->type == FACTNOTIFICATIONTYPE_WAVEDESTROYED)
|
|
|
{
|
|
|
if (pNotificationDescription->flags & FACT_FLAG_NOTIFICATION_PERSIST)
|
|
|
{
|
|
|
pEngine->notifications |= NOTIFY_WAVEDESTROY;
|
|
|
pEngine->wave_context = pNotificationDescription->pvContext;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
pNotificationDescription->pWave->notifyOnDestroy = 1;
|
|
|
pNotificationDescription->pWave->usercontext = pNotificationDescription->pvContext;
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
FAudio_assert(0 && "TODO: Unimplemented notification!");
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTAudioEngine_UnRegisterNotification(
|
|
|
FACTAudioEngine *pEngine,
|
|
|
const FACTNotificationDescription *pNotificationDescription
|
|
|
) {
|
|
|
FAudio_assert(pEngine != NULL);
|
|
|
FAudio_assert(pNotificationDescription != NULL);
|
|
|
FAudio_assert(pEngine->notificationCallback != NULL);
|
|
|
|
|
|
FAudio_PlatformLockMutex(pEngine->apiLock);
|
|
|
|
|
|
if (pNotificationDescription->type == FACTNOTIFICATIONTYPE_CUEDESTROYED)
|
|
|
{
|
|
|
if (pNotificationDescription->flags & FACT_FLAG_NOTIFICATION_PERSIST)
|
|
|
{
|
|
|
pEngine->notifications &= ~NOTIFY_CUEDESTROY;
|
|
|
pEngine->cue_context = pNotificationDescription->pvContext;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
pNotificationDescription->pCue->notifyOnDestroy = 0;
|
|
|
pNotificationDescription->pCue->usercontext = pNotificationDescription->pvContext;
|
|
|
}
|
|
|
}
|
|
|
else if (pNotificationDescription->type == FACTNOTIFICATIONTYPE_SOUNDBANKDESTROYED)
|
|
|
{
|
|
|
if (pNotificationDescription->flags & FACT_FLAG_NOTIFICATION_PERSIST)
|
|
|
{
|
|
|
pEngine->notifications &= ~NOTIFY_SOUNDBANKDESTROY;
|
|
|
pEngine->sb_context = pNotificationDescription->pvContext;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
pNotificationDescription->pSoundBank->notifyOnDestroy = 0;
|
|
|
pNotificationDescription->pSoundBank->usercontext = pNotificationDescription->pvContext;
|
|
|
}
|
|
|
}
|
|
|
else if (pNotificationDescription->type == FACTNOTIFICATIONTYPE_WAVEBANKDESTROYED)
|
|
|
{
|
|
|
if (pNotificationDescription->flags & FACT_FLAG_NOTIFICATION_PERSIST)
|
|
|
{
|
|
|
pEngine->notifications &= ~NOTIFY_WAVEBANKDESTROY;
|
|
|
pEngine->wb_context = pNotificationDescription->pvContext;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
pNotificationDescription->pWaveBank->notifyOnDestroy = 0;
|
|
|
pNotificationDescription->pWaveBank->usercontext = pNotificationDescription->pvContext;
|
|
|
}
|
|
|
}
|
|
|
else if (pNotificationDescription->type == FACTNOTIFICATIONTYPE_WAVEDESTROYED)
|
|
|
{
|
|
|
if (pNotificationDescription->flags & FACT_FLAG_NOTIFICATION_PERSIST)
|
|
|
{
|
|
|
pEngine->notifications &= ~NOTIFY_WAVEDESTROY;
|
|
|
pEngine->wave_context = pNotificationDescription->pvContext;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
pNotificationDescription->pWave->notifyOnDestroy = 0;
|
|
|
pNotificationDescription->pWave->usercontext = pNotificationDescription->pvContext;
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
FAudio_assert(0 && "TODO: Unimplemented notification!");
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint16_t FACTAudioEngine_GetCategory(
|
|
|
FACTAudioEngine *pEngine,
|
|
|
const char *szFriendlyName
|
|
|
) {
|
|
|
uint16_t i;
|
|
|
FAudio_PlatformLockMutex(pEngine->apiLock);
|
|
|
for (i = 0; i < pEngine->categoryCount; i += 1)
|
|
|
{
|
|
|
if (FAudio_strcmp(szFriendlyName, pEngine->categoryNames[i]) == 0)
|
|
|
{
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
return i;
|
|
|
}
|
|
|
}
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
return FACTCATEGORY_INVALID;
|
|
|
}
|
|
|
|
|
|
uint8_t FACT_INTERNAL_IsInCategory(
|
|
|
FACTAudioEngine *engine,
|
|
|
uint16_t target,
|
|
|
uint16_t category
|
|
|
) {
|
|
|
FACTAudioCategory *cat;
|
|
|
|
|
|
/* Same category, no need to go on a crazy hunt */
|
|
|
if (category == target)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
/* Right, on with the crazy hunt */
|
|
|
cat = &engine->categories[category];
|
|
|
while (cat->parentCategory != -1)
|
|
|
{
|
|
|
if (cat->parentCategory == target)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
cat = &engine->categories[cat->parentCategory];
|
|
|
}
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTAudioEngine_Stop(
|
|
|
FACTAudioEngine *pEngine,
|
|
|
uint16_t nCategory,
|
|
|
uint32_t dwFlags
|
|
|
) {
|
|
|
FACTCue *cue, *backup;
|
|
|
LinkedList *list;
|
|
|
|
|
|
FAudio_PlatformLockMutex(pEngine->apiLock);
|
|
|
list = pEngine->sbList;
|
|
|
while (list != NULL)
|
|
|
{
|
|
|
cue = ((FACTSoundBank*) list->entry)->cueList;
|
|
|
while (cue != NULL)
|
|
|
{
|
|
|
if ( cue->playingSound != NULL &&
|
|
|
FACT_INTERNAL_IsInCategory(
|
|
|
pEngine,
|
|
|
nCategory,
|
|
|
cue->playingSound->sound->category
|
|
|
) )
|
|
|
{
|
|
|
if ( dwFlags == FACT_FLAG_STOP_IMMEDIATE &&
|
|
|
cue->managed )
|
|
|
{
|
|
|
/* Just blow this up now */
|
|
|
backup = cue->next;
|
|
|
FACTCue_Destroy(cue);
|
|
|
cue = backup;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
/* If managed, the mixer will destroy for us */
|
|
|
FACTCue_Stop(cue, dwFlags);
|
|
|
cue = cue->next;
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
cue = cue->next;
|
|
|
}
|
|
|
}
|
|
|
list = list->next;
|
|
|
}
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTAudioEngine_SetVolume(
|
|
|
FACTAudioEngine *pEngine,
|
|
|
uint16_t nCategory,
|
|
|
float volume
|
|
|
) {
|
|
|
uint16_t i;
|
|
|
FAudio_PlatformLockMutex(pEngine->apiLock);
|
|
|
pEngine->categories[nCategory].currentVolume = (
|
|
|
pEngine->categories[nCategory].volume *
|
|
|
volume
|
|
|
);
|
|
|
for (i = 0; i < pEngine->categoryCount; i += 1)
|
|
|
{
|
|
|
if (pEngine->categories[i].parentCategory == nCategory)
|
|
|
{
|
|
|
FACTAudioEngine_SetVolume(
|
|
|
pEngine,
|
|
|
i,
|
|
|
pEngine->categories[i].currentVolume
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTAudioEngine_Pause(
|
|
|
FACTAudioEngine *pEngine,
|
|
|
uint16_t nCategory,
|
|
|
int32_t fPause
|
|
|
) {
|
|
|
FACTCue *cue;
|
|
|
LinkedList *list;
|
|
|
|
|
|
FAudio_PlatformLockMutex(pEngine->apiLock);
|
|
|
list = pEngine->sbList;
|
|
|
while (list != NULL)
|
|
|
{
|
|
|
cue = ((FACTSoundBank*) list->entry)->cueList;
|
|
|
while (cue != NULL)
|
|
|
{
|
|
|
if ( cue->playingSound != NULL &&
|
|
|
FACT_INTERNAL_IsInCategory(
|
|
|
pEngine,
|
|
|
nCategory,
|
|
|
cue->playingSound->sound->category
|
|
|
) )
|
|
|
{
|
|
|
FACTCue_Pause(cue, fPause);
|
|
|
}
|
|
|
cue = cue->next;
|
|
|
}
|
|
|
list = list->next;
|
|
|
}
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint16_t FACTAudioEngine_GetGlobalVariableIndex(
|
|
|
FACTAudioEngine *pEngine,
|
|
|
const char *szFriendlyName
|
|
|
) {
|
|
|
uint16_t i;
|
|
|
FAudio_PlatformLockMutex(pEngine->apiLock);
|
|
|
for (i = 0; i < pEngine->variableCount; i += 1)
|
|
|
{
|
|
|
if ( FAudio_strcmp(szFriendlyName, pEngine->variableNames[i]) == 0 &&
|
|
|
!(pEngine->variables[i].accessibility & 0x04) )
|
|
|
{
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
return i;
|
|
|
}
|
|
|
}
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
return FACTVARIABLEINDEX_INVALID;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTAudioEngine_SetGlobalVariable(
|
|
|
FACTAudioEngine *pEngine,
|
|
|
uint16_t nIndex,
|
|
|
float nValue
|
|
|
) {
|
|
|
FACTVariable *var;
|
|
|
|
|
|
FAudio_PlatformLockMutex(pEngine->apiLock);
|
|
|
|
|
|
var = &pEngine->variables[nIndex];
|
|
|
FAudio_assert(var->accessibility & 0x01);
|
|
|
FAudio_assert(!(var->accessibility & 0x02));
|
|
|
FAudio_assert(!(var->accessibility & 0x04));
|
|
|
pEngine->globalVariableValues[nIndex] = FAudio_clamp(
|
|
|
nValue,
|
|
|
var->minValue,
|
|
|
var->maxValue
|
|
|
);
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTAudioEngine_GetGlobalVariable(
|
|
|
FACTAudioEngine *pEngine,
|
|
|
uint16_t nIndex,
|
|
|
float *pnValue
|
|
|
) {
|
|
|
FACTVariable *var;
|
|
|
|
|
|
FAudio_PlatformLockMutex(pEngine->apiLock);
|
|
|
|
|
|
var = &pEngine->variables[nIndex];
|
|
|
FAudio_assert(var->accessibility & 0x01);
|
|
|
FAudio_assert(!(var->accessibility & 0x04));
|
|
|
*pnValue = pEngine->globalVariableValues[nIndex];
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
/* SoundBank implementation */
|
|
|
|
|
|
uint16_t FACTSoundBank_GetCueIndex(
|
|
|
FACTSoundBank *pSoundBank,
|
|
|
const char *szFriendlyName
|
|
|
) {
|
|
|
uint16_t i;
|
|
|
if (pSoundBank == NULL)
|
|
|
{
|
|
|
return FACTINDEX_INVALID;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(pSoundBank->parentEngine->apiLock);
|
|
|
for (i = 0; i < pSoundBank->cueCount; i += 1)
|
|
|
{
|
|
|
if (FAudio_strcmp(szFriendlyName, pSoundBank->cueNames[i]) == 0)
|
|
|
{
|
|
|
FAudio_PlatformUnlockMutex(
|
|
|
pSoundBank->parentEngine->apiLock
|
|
|
);
|
|
|
return i;
|
|
|
}
|
|
|
}
|
|
|
FAudio_PlatformUnlockMutex(pSoundBank->parentEngine->apiLock);
|
|
|
return FACTINDEX_INVALID;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTSoundBank_GetNumCues(
|
|
|
FACTSoundBank *pSoundBank,
|
|
|
uint16_t *pnNumCues
|
|
|
) {
|
|
|
if (pSoundBank == NULL)
|
|
|
{
|
|
|
*pnNumCues = 0;
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(pSoundBank->parentEngine->apiLock);
|
|
|
*pnNumCues = pSoundBank->cueCount;
|
|
|
FAudio_PlatformUnlockMutex(pSoundBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTSoundBank_GetCueProperties(
|
|
|
FACTSoundBank *pSoundBank,
|
|
|
uint16_t nCueIndex,
|
|
|
FACTCueProperties *pProperties
|
|
|
) {
|
|
|
uint16_t i;
|
|
|
if (pSoundBank == NULL)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(pSoundBank->parentEngine->apiLock);
|
|
|
|
|
|
FAudio_strlcpy(
|
|
|
pProperties->friendlyName,
|
|
|
pSoundBank->cueNames[nCueIndex],
|
|
|
0xFF
|
|
|
);
|
|
|
if (!(pSoundBank->cues[nCueIndex].flags & 0x04))
|
|
|
{
|
|
|
for (i = 0; i < pSoundBank->variationCount; i += 1)
|
|
|
{
|
|
|
if (pSoundBank->variationCodes[i] == pSoundBank->cues[nCueIndex].sbCode)
|
|
|
{
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
FAudio_assert(i < pSoundBank->variationCount && "Variation table not found!");
|
|
|
|
|
|
if (pSoundBank->variations[i].flags == 3)
|
|
|
{
|
|
|
pProperties->interactive = 1;
|
|
|
pProperties->iaVariableIndex = pSoundBank->variations[i].variable;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
pProperties->interactive = 0;
|
|
|
pProperties->iaVariableIndex = 0;
|
|
|
}
|
|
|
pProperties->numVariations = pSoundBank->variations[i].entryCount;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
pProperties->interactive = 0;
|
|
|
pProperties->iaVariableIndex = 0;
|
|
|
pProperties->numVariations = 0;
|
|
|
}
|
|
|
pProperties->maxInstances = pSoundBank->cues[nCueIndex].instanceLimit;
|
|
|
pProperties->currentInstances = pSoundBank->cues[nCueIndex].instanceCount;
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pSoundBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTSoundBank_Prepare(
|
|
|
FACTSoundBank *pSoundBank,
|
|
|
uint16_t nCueIndex,
|
|
|
uint32_t dwFlags,
|
|
|
int32_t timeOffset,
|
|
|
FACTCue** ppCue
|
|
|
) {
|
|
|
uint16_t i;
|
|
|
FACTCue *latest;
|
|
|
|
|
|
if (pSoundBank == NULL)
|
|
|
{
|
|
|
*ppCue = NULL;
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
*ppCue = (FACTCue*) pSoundBank->parentEngine->pMalloc(sizeof(FACTCue));
|
|
|
FAudio_zero(*ppCue, sizeof(FACTCue));
|
|
|
|
|
|
FAudio_PlatformLockMutex(pSoundBank->parentEngine->apiLock);
|
|
|
|
|
|
/* Engine references */
|
|
|
(*ppCue)->parentBank = pSoundBank;
|
|
|
(*ppCue)->next = NULL;
|
|
|
(*ppCue)->managed = 0;
|
|
|
(*ppCue)->index = nCueIndex;
|
|
|
(*ppCue)->notifyOnDestroy = 0;
|
|
|
(*ppCue)->usercontext = NULL;
|
|
|
|
|
|
/* Sound data */
|
|
|
(*ppCue)->data = &pSoundBank->cues[nCueIndex];
|
|
|
if ((*ppCue)->data->flags & 0x04)
|
|
|
{
|
|
|
for (i = 0; i < pSoundBank->soundCount; i += 1)
|
|
|
{
|
|
|
if ((*ppCue)->data->sbCode == pSoundBank->soundCodes[i])
|
|
|
{
|
|
|
(*ppCue)->sound = &pSoundBank->sounds[i];
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
for (i = 0; i < pSoundBank->variationCount; i += 1)
|
|
|
{
|
|
|
if ((*ppCue)->data->sbCode == pSoundBank->variationCodes[i])
|
|
|
{
|
|
|
(*ppCue)->variation = &pSoundBank->variations[i];
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
if ((*ppCue)->variation->flags == 3)
|
|
|
{
|
|
|
(*ppCue)->interactive = pSoundBank->parentEngine->variables[
|
|
|
(*ppCue)->variation->variable
|
|
|
].initialValue;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* Instance data */
|
|
|
(*ppCue)->variableValues = (float*) pSoundBank->parentEngine->pMalloc(
|
|
|
sizeof(float) * pSoundBank->parentEngine->variableCount
|
|
|
);
|
|
|
for (i = 0; i < pSoundBank->parentEngine->variableCount; i += 1)
|
|
|
{
|
|
|
(*ppCue)->variableValues[i] =
|
|
|
pSoundBank->parentEngine->variables[i].initialValue;
|
|
|
}
|
|
|
|
|
|
/* Playback */
|
|
|
(*ppCue)->state = FACT_STATE_PREPARED;
|
|
|
|
|
|
/* Add to the SoundBank Cue list */
|
|
|
if (pSoundBank->cueList == NULL)
|
|
|
{
|
|
|
pSoundBank->cueList = *ppCue;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
latest = pSoundBank->cueList;
|
|
|
while (latest->next != NULL)
|
|
|
{
|
|
|
latest = latest->next;
|
|
|
}
|
|
|
latest->next = *ppCue;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pSoundBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTSoundBank_Play(
|
|
|
FACTSoundBank *pSoundBank,
|
|
|
uint16_t nCueIndex,
|
|
|
uint32_t dwFlags,
|
|
|
int32_t timeOffset,
|
|
|
FACTCue** ppCue /* Optional! */
|
|
|
) {
|
|
|
FACTCue *result;
|
|
|
if (pSoundBank == NULL)
|
|
|
{
|
|
|
if (ppCue != NULL)
|
|
|
{
|
|
|
*ppCue = NULL;
|
|
|
}
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(pSoundBank->parentEngine->apiLock);
|
|
|
|
|
|
FACTSoundBank_Prepare(
|
|
|
pSoundBank,
|
|
|
nCueIndex,
|
|
|
dwFlags,
|
|
|
timeOffset,
|
|
|
&result
|
|
|
);
|
|
|
if (ppCue != NULL)
|
|
|
{
|
|
|
*ppCue = result;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
/* AKA we get to Destroy() this ourselves */
|
|
|
result->managed = 1;
|
|
|
}
|
|
|
FACTCue_Play(result);
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pSoundBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTSoundBank_Play3D(
|
|
|
FACTSoundBank *pSoundBank,
|
|
|
uint16_t nCueIndex,
|
|
|
uint32_t dwFlags,
|
|
|
int32_t timeOffset,
|
|
|
F3DAUDIO_DSP_SETTINGS *pDSPSettings,
|
|
|
FACTCue** ppCue /* Optional! */
|
|
|
) {
|
|
|
FACTCue *result;
|
|
|
if (pSoundBank == NULL)
|
|
|
{
|
|
|
if (ppCue != NULL)
|
|
|
{
|
|
|
*ppCue = NULL;
|
|
|
}
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(pSoundBank->parentEngine->apiLock);
|
|
|
|
|
|
FACTSoundBank_Prepare(
|
|
|
pSoundBank,
|
|
|
nCueIndex,
|
|
|
dwFlags,
|
|
|
timeOffset,
|
|
|
&result
|
|
|
);
|
|
|
if (ppCue != NULL)
|
|
|
{
|
|
|
*ppCue = result;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
/* AKA we get to Destroy() this ourselves */
|
|
|
result->managed = 1;
|
|
|
}
|
|
|
FACT3DApply(pDSPSettings, result);
|
|
|
FACTCue_Play(result);
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pSoundBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTSoundBank_Stop(
|
|
|
FACTSoundBank *pSoundBank,
|
|
|
uint16_t nCueIndex,
|
|
|
uint32_t dwFlags
|
|
|
) {
|
|
|
FACTCue *backup, *cue;
|
|
|
if (pSoundBank == NULL)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(pSoundBank->parentEngine->apiLock);
|
|
|
cue = pSoundBank->cueList;
|
|
|
while (cue != NULL)
|
|
|
{
|
|
|
if (cue->index == nCueIndex)
|
|
|
{
|
|
|
if ( dwFlags == FACT_FLAG_STOP_IMMEDIATE &&
|
|
|
cue->managed )
|
|
|
{
|
|
|
/* Just blow this up now */
|
|
|
backup = cue->next;
|
|
|
FACTCue_Destroy(cue);
|
|
|
cue = backup;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
/* If managed, the mixer will destroy for us */
|
|
|
FACTCue_Stop(cue, dwFlags);
|
|
|
cue = cue->next;
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
cue = cue->next;
|
|
|
}
|
|
|
}
|
|
|
FAudio_PlatformUnlockMutex(pSoundBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTSoundBank_Destroy(FACTSoundBank *pSoundBank)
|
|
|
{
|
|
|
uint16_t i, j, k;
|
|
|
FAudioMutex mutex;
|
|
|
FACTNotification note;
|
|
|
if (pSoundBank == NULL)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(pSoundBank->parentEngine->apiLock);
|
|
|
|
|
|
/* Synchronously destroys all cues that are associated */
|
|
|
while (pSoundBank->cueList != NULL)
|
|
|
{
|
|
|
FACTCue_Destroy(pSoundBank->cueList);
|
|
|
}
|
|
|
|
|
|
if (pSoundBank->parentEngine != NULL)
|
|
|
{
|
|
|
/* Remove this SoundBank from the Engine list */
|
|
|
LinkedList_RemoveEntry(
|
|
|
&pSoundBank->parentEngine->sbList,
|
|
|
pSoundBank,
|
|
|
pSoundBank->parentEngine->sbLock,
|
|
|
pSoundBank->parentEngine->pFree
|
|
|
);
|
|
|
}
|
|
|
|
|
|
/* SoundBank Name */
|
|
|
pSoundBank->parentEngine->pFree(pSoundBank->name);
|
|
|
|
|
|
/* Cue data */
|
|
|
pSoundBank->parentEngine->pFree(pSoundBank->cues);
|
|
|
|
|
|
/* WaveBank Name data */
|
|
|
for (i = 0; i < pSoundBank->wavebankCount; i += 1)
|
|
|
{
|
|
|
pSoundBank->parentEngine->pFree(pSoundBank->wavebankNames[i]);
|
|
|
}
|
|
|
pSoundBank->parentEngine->pFree(pSoundBank->wavebankNames);
|
|
|
|
|
|
/* Sound data */
|
|
|
for (i = 0; i < pSoundBank->soundCount; i += 1)
|
|
|
{
|
|
|
for (j = 0; j < pSoundBank->sounds[i].trackCount; j += 1)
|
|
|
{
|
|
|
for (k = 0; k < pSoundBank->sounds[i].tracks[j].eventCount; k += 1)
|
|
|
{
|
|
|
#define MATCH(t) \
|
|
|
pSoundBank->sounds[i].tracks[j].events[k].type == t
|
|
|
if ( MATCH(FACTEVENT_PLAYWAVE) ||
|
|
|
MATCH(FACTEVENT_PLAYWAVETRACKVARIATION) ||
|
|
|
MATCH(FACTEVENT_PLAYWAVEEFFECTVARIATION) ||
|
|
|
MATCH(FACTEVENT_PLAYWAVETRACKEFFECTVARIATION) )
|
|
|
{
|
|
|
if (pSoundBank->sounds[i].tracks[j].events[k].wave.isComplex)
|
|
|
{
|
|
|
pSoundBank->parentEngine->pFree(
|
|
|
pSoundBank->sounds[i].tracks[j].events[k].wave.complex.tracks
|
|
|
);
|
|
|
pSoundBank->parentEngine->pFree(
|
|
|
pSoundBank->sounds[i].tracks[j].events[k].wave.complex.wavebanks
|
|
|
);
|
|
|
pSoundBank->parentEngine->pFree(
|
|
|
pSoundBank->sounds[i].tracks[j].events[k].wave.complex.weights
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
#undef MATCH
|
|
|
}
|
|
|
pSoundBank->parentEngine->pFree(
|
|
|
pSoundBank->sounds[i].tracks[j].events
|
|
|
);
|
|
|
}
|
|
|
pSoundBank->parentEngine->pFree(pSoundBank->sounds[i].tracks);
|
|
|
pSoundBank->parentEngine->pFree(pSoundBank->sounds[i].rpcCodes);
|
|
|
pSoundBank->parentEngine->pFree(pSoundBank->sounds[i].dspCodes);
|
|
|
}
|
|
|
pSoundBank->parentEngine->pFree(pSoundBank->sounds);
|
|
|
pSoundBank->parentEngine->pFree(pSoundBank->soundCodes);
|
|
|
|
|
|
/* Variation data */
|
|
|
for (i = 0; i < pSoundBank->variationCount; i += 1)
|
|
|
{
|
|
|
pSoundBank->parentEngine->pFree(
|
|
|
pSoundBank->variations[i].entries
|
|
|
);
|
|
|
}
|
|
|
pSoundBank->parentEngine->pFree(pSoundBank->variations);
|
|
|
pSoundBank->parentEngine->pFree(pSoundBank->variationCodes);
|
|
|
|
|
|
/* Transition data */
|
|
|
for (i = 0; i < pSoundBank->transitionCount; i += 1)
|
|
|
{
|
|
|
pSoundBank->parentEngine->pFree(
|
|
|
pSoundBank->transitions[i].entries
|
|
|
);
|
|
|
}
|
|
|
pSoundBank->parentEngine->pFree(pSoundBank->transitions);
|
|
|
pSoundBank->parentEngine->pFree(pSoundBank->transitionCodes);
|
|
|
|
|
|
/* Cue Name data */
|
|
|
for (i = 0; i < pSoundBank->cueCount; i += 1)
|
|
|
{
|
|
|
pSoundBank->parentEngine->pFree(pSoundBank->cueNames[i]);
|
|
|
}
|
|
|
pSoundBank->parentEngine->pFree(pSoundBank->cueNames);
|
|
|
|
|
|
/* Finally. */
|
|
|
if (pSoundBank->notifyOnDestroy || pSoundBank->parentEngine->notifications & NOTIFY_SOUNDBANKDESTROY)
|
|
|
{
|
|
|
note.type = FACTNOTIFICATIONTYPE_SOUNDBANKDESTROYED;
|
|
|
note.soundBank.pSoundBank = pSoundBank;
|
|
|
if (pSoundBank->parentEngine->notifications & NOTIFY_SOUNDBANKDESTROY)
|
|
|
{
|
|
|
note.pvContext = pSoundBank->parentEngine->sb_context;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
note.pvContext = pSoundBank->usercontext;
|
|
|
}
|
|
|
pSoundBank->parentEngine->notificationCallback(¬e);
|
|
|
}
|
|
|
|
|
|
mutex = pSoundBank->parentEngine->apiLock;
|
|
|
pSoundBank->parentEngine->pFree(pSoundBank);
|
|
|
FAudio_PlatformUnlockMutex(mutex);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTSoundBank_GetState(
|
|
|
FACTSoundBank *pSoundBank,
|
|
|
uint32_t *pdwState
|
|
|
) {
|
|
|
uint16_t i;
|
|
|
if (pSoundBank == NULL)
|
|
|
{
|
|
|
*pdwState = 0;
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(pSoundBank->parentEngine->apiLock);
|
|
|
|
|
|
*pdwState = FACT_STATE_PREPARED;
|
|
|
for (i = 0; i < pSoundBank->cueCount; i += 1)
|
|
|
{
|
|
|
if (pSoundBank->cues[i].instanceCount > 0)
|
|
|
{
|
|
|
*pdwState |= FACT_STATE_INUSE;
|
|
|
FAudio_PlatformUnlockMutex(
|
|
|
pSoundBank->parentEngine->apiLock
|
|
|
);
|
|
|
return 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pSoundBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
/* WaveBank implementation */
|
|
|
|
|
|
uint32_t FACTWaveBank_Destroy(FACTWaveBank *pWaveBank)
|
|
|
{
|
|
|
uint32_t i;
|
|
|
FACTWave *wave;
|
|
|
FAudioMutex mutex;
|
|
|
FACTNotification note;
|
|
|
if (pWaveBank == NULL)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(pWaveBank->parentEngine->apiLock);
|
|
|
|
|
|
/* Synchronously destroys any cues that are using the wavebank */
|
|
|
while (pWaveBank->waveList != NULL)
|
|
|
{
|
|
|
wave = (FACTWave*) pWaveBank->waveList->entry;
|
|
|
if (wave->parentCue != NULL)
|
|
|
{
|
|
|
/* Destroying this Cue destroys the Wave */
|
|
|
FACTCue_Destroy(wave->parentCue);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
FACTWave_Destroy(wave);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (pWaveBank->parentEngine != NULL)
|
|
|
{
|
|
|
/* Remove this WaveBank from the Engine list */
|
|
|
LinkedList_RemoveEntry(
|
|
|
&pWaveBank->parentEngine->wbList,
|
|
|
pWaveBank,
|
|
|
pWaveBank->parentEngine->wbLock,
|
|
|
pWaveBank->parentEngine->pFree
|
|
|
);
|
|
|
}
|
|
|
|
|
|
/* Free everything, finally. */
|
|
|
pWaveBank->parentEngine->pFree(pWaveBank->name);
|
|
|
pWaveBank->parentEngine->pFree(pWaveBank->entries);
|
|
|
pWaveBank->parentEngine->pFree(pWaveBank->entryRefs);
|
|
|
if (pWaveBank->seekTables != NULL)
|
|
|
{
|
|
|
for (i = 0; i < pWaveBank->entryCount; i += 1)
|
|
|
{
|
|
|
if (pWaveBank->seekTables[i].entries != NULL)
|
|
|
{
|
|
|
pWaveBank->parentEngine->pFree(
|
|
|
pWaveBank->seekTables[i].entries
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
pWaveBank->parentEngine->pFree(pWaveBank->seekTables);
|
|
|
}
|
|
|
FAudio_close(pWaveBank->io);
|
|
|
if (pWaveBank->packetBuffer != NULL)
|
|
|
{
|
|
|
pWaveBank->parentEngine->pFree(pWaveBank->packetBuffer);
|
|
|
}
|
|
|
if (pWaveBank->notifyOnDestroy || pWaveBank->parentEngine->notifications & NOTIFY_WAVEBANKDESTROY)
|
|
|
{
|
|
|
note.type = FACTNOTIFICATIONTYPE_WAVEBANKDESTROYED;
|
|
|
note.waveBank.pWaveBank = pWaveBank;
|
|
|
if (pWaveBank->parentEngine->notifications & NOTIFY_WAVEBANKDESTROY)
|
|
|
{
|
|
|
note.pvContext = pWaveBank->parentEngine->wb_context;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
note.pvContext = pWaveBank->usercontext;
|
|
|
}
|
|
|
pWaveBank->parentEngine->notificationCallback(¬e);
|
|
|
}
|
|
|
FAudio_PlatformDestroyMutex(pWaveBank->waveLock);
|
|
|
|
|
|
mutex = pWaveBank->parentEngine->apiLock;
|
|
|
pWaveBank->parentEngine->pFree(pWaveBank);
|
|
|
FAudio_PlatformUnlockMutex(mutex);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTWaveBank_GetState(
|
|
|
FACTWaveBank *pWaveBank,
|
|
|
uint32_t *pdwState
|
|
|
) {
|
|
|
uint32_t i;
|
|
|
if (pWaveBank == NULL)
|
|
|
{
|
|
|
*pdwState = 0;
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(pWaveBank->parentEngine->apiLock);
|
|
|
|
|
|
*pdwState = FACT_STATE_PREPARED;
|
|
|
for (i = 0; i < pWaveBank->entryCount; i += 1)
|
|
|
{
|
|
|
if (pWaveBank->entryRefs[i] > 0)
|
|
|
{
|
|
|
*pdwState |= FACT_STATE_INUSE;
|
|
|
FAudio_PlatformUnlockMutex(
|
|
|
pWaveBank->parentEngine->apiLock
|
|
|
);
|
|
|
return 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pWaveBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTWaveBank_GetNumWaves(
|
|
|
FACTWaveBank *pWaveBank,
|
|
|
uint16_t *pnNumWaves
|
|
|
) {
|
|
|
if (pWaveBank == NULL)
|
|
|
{
|
|
|
*pnNumWaves = 0;
|
|
|
return 1;
|
|
|
}
|
|
|
FAudio_PlatformLockMutex(pWaveBank->parentEngine->apiLock);
|
|
|
*pnNumWaves = pWaveBank->entryCount;
|
|
|
FAudio_PlatformUnlockMutex(pWaveBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint16_t FACTWaveBank_GetWaveIndex(
|
|
|
FACTWaveBank *pWaveBank,
|
|
|
const char *szFriendlyName
|
|
|
) {
|
|
|
FAudio_assert(0 && "WaveBank name tables are not supported!");
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTWaveBank_GetWaveProperties(
|
|
|
FACTWaveBank *pWaveBank,
|
|
|
uint16_t nWaveIndex,
|
|
|
FACTWaveProperties *pWaveProperties
|
|
|
) {
|
|
|
FACTWaveBankEntry *entry;
|
|
|
if (pWaveBank == NULL)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(pWaveBank->parentEngine->apiLock);
|
|
|
|
|
|
entry = &pWaveBank->entries[nWaveIndex];
|
|
|
|
|
|
/* FIXME: Name tables! -flibit */
|
|
|
FAudio_zero(
|
|
|
pWaveProperties->friendlyName,
|
|
|
sizeof(pWaveProperties->friendlyName)
|
|
|
);
|
|
|
|
|
|
pWaveProperties->format = entry->Format;
|
|
|
pWaveProperties->durationInSamples = entry->PlayRegion.dwLength;
|
|
|
if (entry->Format.wFormatTag == 0)
|
|
|
{
|
|
|
pWaveProperties->durationInSamples /= (8 << entry->Format.wBitsPerSample) / 8;
|
|
|
pWaveProperties->durationInSamples /= entry->Format.nChannels;
|
|
|
}
|
|
|
else if (entry->Format.wFormatTag == FAUDIO_FORMAT_MSADPCM)
|
|
|
{
|
|
|
pWaveProperties->durationInSamples = (
|
|
|
pWaveProperties->durationInSamples /
|
|
|
((entry->Format.wBlockAlign + 22) * entry->Format.nChannels) *
|
|
|
((entry->Format.wBlockAlign + 16) * 2)
|
|
|
);
|
|
|
}
|
|
|
|
|
|
pWaveProperties->loopRegion = entry->LoopRegion;
|
|
|
pWaveProperties->streaming = pWaveBank->streaming;
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pWaveBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTWaveBank_Prepare(
|
|
|
FACTWaveBank *pWaveBank,
|
|
|
uint16_t nWaveIndex,
|
|
|
uint32_t dwFlags,
|
|
|
uint32_t dwPlayOffset,
|
|
|
uint8_t nLoopCount,
|
|
|
FACTWave **ppWave
|
|
|
) {
|
|
|
FAudioBuffer buffer;
|
|
|
FAudioBufferWMA bufferWMA;
|
|
|
FAudioVoiceSends sends;
|
|
|
FAudioSendDescriptor send;
|
|
|
FAudioADPCMWaveFormat format;
|
|
|
FACTWaveBankEntry *entry;
|
|
|
if (pWaveBank == NULL)
|
|
|
{
|
|
|
*ppWave = NULL;
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
*ppWave = (FACTWave*) pWaveBank->parentEngine->pMalloc(sizeof(FACTWave));
|
|
|
|
|
|
FAudio_PlatformLockMutex(pWaveBank->parentEngine->apiLock);
|
|
|
|
|
|
entry = &pWaveBank->entries[nWaveIndex];
|
|
|
|
|
|
/* Engine references */
|
|
|
(*ppWave)->parentBank = pWaveBank;
|
|
|
(*ppWave)->parentCue = NULL;
|
|
|
(*ppWave)->index = nWaveIndex;
|
|
|
(*ppWave)->notifyOnDestroy = 0;
|
|
|
(*ppWave)->usercontext = NULL;
|
|
|
|
|
|
/* Playback */
|
|
|
(*ppWave)->state = FACT_STATE_PREPARED;
|
|
|
(*ppWave)->volume = 1.0f;
|
|
|
(*ppWave)->pitch = 0;
|
|
|
(*ppWave)->loopCount = nLoopCount;
|
|
|
|
|
|
/* TODO: Convert dwPlayOffset to a byte offset */
|
|
|
FAudio_assert(dwPlayOffset == 0);
|
|
|
#if 0
|
|
|
if (dwFlags & FACT_FLAG_UNITS_MS)
|
|
|
{
|
|
|
dwPlayOffset = (uint32_t) (
|
|
|
( /* Samples per millisecond... */
|
|
|
(float) entry->Format.nSamplesPerSec /
|
|
|
1000.0f
|
|
|
) * (float) dwPlayOffset
|
|
|
);
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
/* Create the voice */
|
|
|
send.Flags = 0;
|
|
|
send.pOutputVoice = pWaveBank->parentEngine->master;
|
|
|
sends.SendCount = 1;
|
|
|
sends.pSends = &send;
|
|
|
format.wfx.nChannels = entry->Format.nChannels;
|
|
|
format.wfx.nSamplesPerSec = entry->Format.nSamplesPerSec;
|
|
|
if (entry->Format.wFormatTag == 0x0)
|
|
|
{
|
|
|
format.wfx.wFormatTag = FAUDIO_FORMAT_PCM;
|
|
|
format.wfx.wBitsPerSample = 8 << entry->Format.wBitsPerSample;
|
|
|
format.wfx.nBlockAlign = format.wfx.nChannels * format.wfx.wBitsPerSample / 8;
|
|
|
format.wfx.nAvgBytesPerSec = format.wfx.nBlockAlign * format.wfx.nSamplesPerSec;
|
|
|
format.wfx.cbSize = 0;
|
|
|
}
|
|
|
else if (entry->Format.wFormatTag == 0x1)
|
|
|
{
|
|
|
/* XMA2 is quite similar to WMA Pro. */
|
|
|
FAudio_assert(entry->Format.wBitsPerSample != 0);
|
|
|
|
|
|
format.wfx.wFormatTag = FAUDIO_FORMAT_XMAUDIO2;
|
|
|
format.wfx.nAvgBytesPerSec = aWMAAvgBytesPerSec[entry->Format.wBlockAlign >> 5];
|
|
|
format.wfx.nBlockAlign = aWMABlockAlign[entry->Format.wBlockAlign & 0x1F];
|
|
|
format.wfx.wBitsPerSample = 16;
|
|
|
format.wfx.cbSize = 0;
|
|
|
}
|
|
|
else if (entry->Format.wFormatTag == 0x2)
|
|
|
{
|
|
|
format.wfx.wFormatTag = FAUDIO_FORMAT_MSADPCM;
|
|
|
format.wfx.nBlockAlign = (entry->Format.wBlockAlign + 22) * format.wfx.nChannels;
|
|
|
format.wfx.wBitsPerSample = 16;
|
|
|
format.wfx.cbSize = (
|
|
|
sizeof(FAudioADPCMWaveFormat) -
|
|
|
sizeof(FAudioWaveFormatEx)
|
|
|
);
|
|
|
format.wSamplesPerBlock = (
|
|
|
((format.wfx.nBlockAlign / format.wfx.nChannels) - 6) * 2
|
|
|
);
|
|
|
}
|
|
|
else if (entry->Format.wFormatTag == 0x3)
|
|
|
{
|
|
|
/* Apparently this is used to detect WMA Pro...? */
|
|
|
FAudio_assert(entry->Format.wBitsPerSample == 0);
|
|
|
|
|
|
format.wfx.wFormatTag = FAUDIO_FORMAT_WMAUDIO2;
|
|
|
format.wfx.nAvgBytesPerSec = aWMAAvgBytesPerSec[entry->Format.wBlockAlign >> 5];
|
|
|
format.wfx.nBlockAlign = aWMABlockAlign[entry->Format.wBlockAlign & 0x1F];
|
|
|
format.wfx.wBitsPerSample = 16;
|
|
|
format.wfx.cbSize = 0;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
FAudio_assert(0 && "Rebuild your WaveBanks with ADPCM!");
|
|
|
}
|
|
|
(*ppWave)->callback.callback.OnBufferEnd = pWaveBank->streaming ?
|
|
|
FACT_INTERNAL_OnBufferEnd :
|
|
|
NULL;
|
|
|
(*ppWave)->callback.callback.OnBufferStart = NULL;
|
|
|
(*ppWave)->callback.callback.OnLoopEnd = NULL;
|
|
|
(*ppWave)->callback.callback.OnStreamEnd = FACT_INTERNAL_OnStreamEnd;
|
|
|
(*ppWave)->callback.callback.OnVoiceError = NULL;
|
|
|
(*ppWave)->callback.callback.OnVoiceProcessingPassEnd = NULL;
|
|
|
(*ppWave)->callback.callback.OnVoiceProcessingPassStart = NULL;
|
|
|
(*ppWave)->callback.wave = *ppWave;
|
|
|
(*ppWave)->srcChannels = format.wfx.nChannels;
|
|
|
FAudio_CreateSourceVoice(
|
|
|
pWaveBank->parentEngine->audio,
|
|
|
&(*ppWave)->voice,
|
|
|
&format.wfx,
|
|
|
FAUDIO_VOICE_USEFILTER, /* FIXME: Can this be optional? */
|
|
|
4.0f,
|
|
|
(FAudioVoiceCallback*) &(*ppWave)->callback,
|
|
|
&sends,
|
|
|
NULL
|
|
|
);
|
|
|
if (pWaveBank->streaming)
|
|
|
{
|
|
|
/* Init stream cache info */
|
|
|
if (format.wfx.wFormatTag == FAUDIO_FORMAT_PCM)
|
|
|
{
|
|
|
(*ppWave)->streamSize = (
|
|
|
format.wfx.nSamplesPerSec *
|
|
|
format.wfx.nBlockAlign
|
|
|
);
|
|
|
}
|
|
|
else if (format.wfx.wFormatTag == FAUDIO_FORMAT_MSADPCM)
|
|
|
{
|
|
|
(*ppWave)->streamSize = (
|
|
|
format.wfx.nSamplesPerSec /
|
|
|
format.wSamplesPerBlock *
|
|
|
format.wfx.nBlockAlign
|
|
|
);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
/* Screw it, load the whole thing */
|
|
|
(*ppWave)->streamSize = entry->PlayRegion.dwLength;
|
|
|
|
|
|
/* XACT does NOT support loop subregions for these formats */
|
|
|
FAudio_assert(entry->LoopRegion.dwStartSample == 0);
|
|
|
FAudio_assert(entry->LoopRegion.dwTotalSamples == entry->Duration);
|
|
|
}
|
|
|
(*ppWave)->streamCache = (uint8_t*) pWaveBank->parentEngine->pMalloc(
|
|
|
(*ppWave)->streamSize
|
|
|
);
|
|
|
(*ppWave)->streamOffset = entry->PlayRegion.dwOffset;
|
|
|
|
|
|
/* Read and submit first buffer from the WaveBank */
|
|
|
FACT_INTERNAL_OnBufferEnd(&(*ppWave)->callback.callback, NULL);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
(*ppWave)->streamCache = NULL;
|
|
|
|
|
|
buffer.Flags = FAUDIO_END_OF_STREAM;
|
|
|
buffer.AudioBytes = entry->PlayRegion.dwLength;
|
|
|
buffer.pAudioData = FAudio_memptr(
|
|
|
pWaveBank->io,
|
|
|
entry->PlayRegion.dwOffset
|
|
|
);
|
|
|
buffer.PlayBegin = 0;
|
|
|
buffer.PlayLength = entry->Duration;
|
|
|
if (nLoopCount == 0)
|
|
|
{
|
|
|
buffer.LoopBegin = 0;
|
|
|
buffer.LoopLength = 0;
|
|
|
buffer.LoopCount = 0;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
buffer.LoopBegin = entry->LoopRegion.dwStartSample;
|
|
|
buffer.LoopLength = entry->LoopRegion.dwTotalSamples;
|
|
|
buffer.LoopCount = nLoopCount;
|
|
|
}
|
|
|
buffer.pContext = NULL;
|
|
|
if ( format.wfx.wFormatTag == FAUDIO_FORMAT_WMAUDIO2 ||
|
|
|
format.wfx.wFormatTag == FAUDIO_FORMAT_XMAUDIO2 )
|
|
|
{
|
|
|
bufferWMA.pDecodedPacketCumulativeBytes =
|
|
|
pWaveBank->seekTables[nWaveIndex].entries;
|
|
|
bufferWMA.PacketCount =
|
|
|
pWaveBank->seekTables[nWaveIndex].entryCount;
|
|
|
FAudioSourceVoice_SubmitSourceBuffer(
|
|
|
(*ppWave)->voice,
|
|
|
&buffer,
|
|
|
&bufferWMA
|
|
|
);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
FAudioSourceVoice_SubmitSourceBuffer(
|
|
|
(*ppWave)->voice,
|
|
|
&buffer,
|
|
|
NULL
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* Add to the WaveBank Wave list */
|
|
|
LinkedList_AddEntry(
|
|
|
&pWaveBank->waveList,
|
|
|
*ppWave,
|
|
|
pWaveBank->waveLock,
|
|
|
pWaveBank->parentEngine->pMalloc
|
|
|
);
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pWaveBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTWaveBank_Play(
|
|
|
FACTWaveBank *pWaveBank,
|
|
|
uint16_t nWaveIndex,
|
|
|
uint32_t dwFlags,
|
|
|
uint32_t dwPlayOffset,
|
|
|
uint8_t nLoopCount,
|
|
|
FACTWave **ppWave
|
|
|
) {
|
|
|
if (pWaveBank == NULL)
|
|
|
{
|
|
|
*ppWave = NULL;
|
|
|
return 1;
|
|
|
}
|
|
|
FAudio_PlatformLockMutex(pWaveBank->parentEngine->apiLock);
|
|
|
FACTWaveBank_Prepare(
|
|
|
pWaveBank,
|
|
|
nWaveIndex,
|
|
|
dwFlags,
|
|
|
dwPlayOffset,
|
|
|
nLoopCount,
|
|
|
ppWave
|
|
|
);
|
|
|
FACTWave_Play(*ppWave);
|
|
|
FAudio_PlatformUnlockMutex(pWaveBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTWaveBank_Stop(
|
|
|
FACTWaveBank *pWaveBank,
|
|
|
uint16_t nWaveIndex,
|
|
|
uint32_t dwFlags
|
|
|
) {
|
|
|
FACTWave *wave;
|
|
|
LinkedList *list;
|
|
|
if (pWaveBank == NULL)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
FAudio_PlatformLockMutex(pWaveBank->parentEngine->apiLock);
|
|
|
list = pWaveBank->waveList;
|
|
|
while (list != NULL)
|
|
|
{
|
|
|
wave = (FACTWave*) list->entry;
|
|
|
if (wave->index == nWaveIndex)
|
|
|
{
|
|
|
FACTWave_Stop(wave, dwFlags);
|
|
|
}
|
|
|
list = list->next;
|
|
|
}
|
|
|
FAudio_PlatformUnlockMutex(pWaveBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
/* Wave implementation */
|
|
|
|
|
|
uint32_t FACTWave_Destroy(FACTWave *pWave)
|
|
|
{
|
|
|
FAudioMutex mutex;
|
|
|
FACTNotification note;
|
|
|
if (pWave == NULL)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(pWave->parentBank->parentEngine->apiLock);
|
|
|
|
|
|
/* Stop before we start deleting everything */
|
|
|
FACTWave_Stop(pWave, FACT_FLAG_STOP_IMMEDIATE);
|
|
|
|
|
|
LinkedList_RemoveEntry(
|
|
|
&pWave->parentBank->waveList,
|
|
|
pWave,
|
|
|
pWave->parentBank->waveLock,
|
|
|
pWave->parentBank->parentEngine->pFree
|
|
|
);
|
|
|
|
|
|
FAudioVoice_DestroyVoice(pWave->voice);
|
|
|
if (pWave->streamCache != NULL)
|
|
|
{
|
|
|
pWave->parentBank->parentEngine->pFree(pWave->streamCache);
|
|
|
}
|
|
|
if (pWave->notifyOnDestroy || pWave->parentBank->parentEngine->notifications & NOTIFY_WAVEDESTROY)
|
|
|
{
|
|
|
note.type = FACTNOTIFICATIONTYPE_WAVEDESTROYED;
|
|
|
note.wave.pWave = pWave;
|
|
|
if (pWave->parentBank->parentEngine->notifications & NOTIFY_WAVEDESTROY)
|
|
|
{
|
|
|
note.pvContext = pWave->parentBank->parentEngine->wave_context;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
note.pvContext = pWave->usercontext;
|
|
|
}
|
|
|
pWave->parentBank->parentEngine->notificationCallback(¬e);
|
|
|
}
|
|
|
|
|
|
mutex = pWave->parentBank->parentEngine->apiLock;
|
|
|
pWave->parentBank->parentEngine->pFree(pWave);
|
|
|
FAudio_PlatformUnlockMutex(mutex);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTWave_Play(FACTWave *pWave)
|
|
|
{
|
|
|
if (pWave == NULL)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
FAudio_PlatformLockMutex(pWave->parentBank->parentEngine->apiLock);
|
|
|
FAudio_assert(!(pWave->state & (FACT_STATE_PLAYING | FACT_STATE_STOPPING)));
|
|
|
pWave->state |= FACT_STATE_PLAYING;
|
|
|
pWave->state &= ~(
|
|
|
FACT_STATE_PAUSED |
|
|
|
FACT_STATE_STOPPED
|
|
|
);
|
|
|
FAudioSourceVoice_Start(pWave->voice, 0, 0);
|
|
|
FAudio_PlatformUnlockMutex(pWave->parentBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTWave_Stop(FACTWave *pWave, uint32_t dwFlags)
|
|
|
{
|
|
|
if (pWave == NULL)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
FAudio_PlatformLockMutex(pWave->parentBank->parentEngine->apiLock);
|
|
|
|
|
|
/* There are two ways that a Wave might be stopped immediately:
|
|
|
* 1. The program explicitly asks for it
|
|
|
* 2. The Wave is paused and therefore we can't do fade/release effects
|
|
|
*/
|
|
|
if ( dwFlags & FACT_FLAG_STOP_IMMEDIATE ||
|
|
|
pWave->state & FACT_STATE_PAUSED )
|
|
|
{
|
|
|
pWave->state |= FACT_STATE_STOPPED;
|
|
|
pWave->state &= ~(
|
|
|
FACT_STATE_PLAYING |
|
|
|
FACT_STATE_STOPPING |
|
|
|
FACT_STATE_PAUSED
|
|
|
);
|
|
|
FAudioSourceVoice_Stop(pWave->voice, 0, 0);
|
|
|
FAudioSourceVoice_FlushSourceBuffers(pWave->voice);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
pWave->state |= FACT_STATE_STOPPING;
|
|
|
FAudioSourceVoice_ExitLoop(pWave->voice, 0);
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pWave->parentBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTWave_Pause(FACTWave *pWave, int32_t fPause)
|
|
|
{
|
|
|
if (pWave == NULL)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
FAudio_PlatformLockMutex(pWave->parentBank->parentEngine->apiLock);
|
|
|
|
|
|
/* FIXME: Does the Cue STOPPING/STOPPED rule apply here too? */
|
|
|
if (pWave->state & (FACT_STATE_STOPPING | FACT_STATE_STOPPED))
|
|
|
{
|
|
|
FAudio_PlatformUnlockMutex(
|
|
|
pWave->parentBank->parentEngine->apiLock
|
|
|
);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
/* All we do is set the flag, the mixer handles the rest */
|
|
|
if (fPause)
|
|
|
{
|
|
|
pWave->state |= FACT_STATE_PAUSED;
|
|
|
FAudioSourceVoice_Stop(pWave->voice, 0, 0);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
pWave->state &= ~FACT_STATE_PAUSED;
|
|
|
FAudioSourceVoice_Start(pWave->voice, 0, 0);
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pWave->parentBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTWave_GetState(FACTWave *pWave, uint32_t *pdwState)
|
|
|
{
|
|
|
if (pWave == NULL)
|
|
|
{
|
|
|
*pdwState = 0;
|
|
|
return 1;
|
|
|
}
|
|
|
FAudio_PlatformLockMutex(pWave->parentBank->parentEngine->apiLock);
|
|
|
*pdwState = pWave->state;
|
|
|
FAudio_PlatformUnlockMutex(pWave->parentBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTWave_SetPitch(FACTWave *pWave, int16_t pitch)
|
|
|
{
|
|
|
if (pWave == NULL)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
FAudio_PlatformLockMutex(pWave->parentBank->parentEngine->apiLock);
|
|
|
pWave->pitch = FAudio_clamp(
|
|
|
pitch,
|
|
|
FACTPITCH_MIN_TOTAL,
|
|
|
FACTPITCH_MAX_TOTAL
|
|
|
);
|
|
|
FAudioSourceVoice_SetFrequencyRatio(
|
|
|
pWave->voice,
|
|
|
(float) FAudio_pow(2.0, pWave->pitch / 1200.0),
|
|
|
0
|
|
|
);
|
|
|
FAudio_PlatformUnlockMutex(pWave->parentBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTWave_SetVolume(FACTWave *pWave, float volume)
|
|
|
{
|
|
|
if (pWave == NULL)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
FAudio_PlatformLockMutex(pWave->parentBank->parentEngine->apiLock);
|
|
|
pWave->volume = FAudio_clamp(
|
|
|
volume,
|
|
|
FACTVOLUME_MIN,
|
|
|
FACTVOLUME_MAX
|
|
|
);
|
|
|
FAudioVoice_SetVolume(
|
|
|
pWave->voice,
|
|
|
pWave->volume,
|
|
|
0
|
|
|
);
|
|
|
FAudio_PlatformUnlockMutex(pWave->parentBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTWave_SetMatrixCoefficients(
|
|
|
FACTWave *pWave,
|
|
|
uint32_t uSrcChannelCount,
|
|
|
uint32_t uDstChannelCount,
|
|
|
float *pMatrixCoefficients
|
|
|
) {
|
|
|
uint32_t i;
|
|
|
float *mtxDst, *mtxSrc;
|
|
|
if (pWave == NULL)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(pWave->parentBank->parentEngine->apiLock);
|
|
|
|
|
|
/* There seems to be this weird feature in XACT where the channel count
|
|
|
* can be completely wrong and it'll go to the right place.
|
|
|
* I guess these XACT functions do some extra work to merge coefficients
|
|
|
* but I have no idea where it really happens and XAudio2 definitely
|
|
|
* does NOT like it when this is wrong, so here it goes...
|
|
|
* -flibit
|
|
|
*/
|
|
|
if (uSrcChannelCount == 1 && pWave->srcChannels == 2)
|
|
|
{
|
|
|
mtxDst = pMatrixCoefficients + ((uDstChannelCount - 1) * 2);
|
|
|
mtxSrc = pMatrixCoefficients + (uDstChannelCount - 1);
|
|
|
for (i = 0; i < uDstChannelCount; i += 1)
|
|
|
{
|
|
|
mtxDst[0] = *mtxSrc;
|
|
|
mtxDst[1] = *mtxSrc;
|
|
|
mtxDst -= 2;
|
|
|
mtxSrc -= 1;
|
|
|
}
|
|
|
uSrcChannelCount = 2;
|
|
|
}
|
|
|
else if (uSrcChannelCount == 2 && pWave->srcChannels == 1)
|
|
|
{
|
|
|
mtxDst = pMatrixCoefficients;
|
|
|
mtxSrc = pMatrixCoefficients;
|
|
|
for (i = 0; i < uDstChannelCount; i += 1)
|
|
|
{
|
|
|
*mtxDst = (mtxSrc[0] + mtxSrc[1]) / 2.0f;
|
|
|
mtxDst += 1;
|
|
|
mtxSrc += 2;
|
|
|
}
|
|
|
uSrcChannelCount = 1;
|
|
|
}
|
|
|
|
|
|
FAudioVoice_SetOutputMatrix(
|
|
|
pWave->voice,
|
|
|
pWave->voice->sends.pSends->pOutputVoice,
|
|
|
uSrcChannelCount,
|
|
|
uDstChannelCount,
|
|
|
pMatrixCoefficients,
|
|
|
0
|
|
|
);
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pWave->parentBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTWave_GetProperties(
|
|
|
FACTWave *pWave,
|
|
|
FACTWaveInstanceProperties *pProperties
|
|
|
) {
|
|
|
if (pWave == NULL)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
FAudio_PlatformLockMutex(pWave->parentBank->parentEngine->apiLock);
|
|
|
|
|
|
FACTWaveBank_GetWaveProperties(
|
|
|
pWave->parentBank,
|
|
|
pWave->index,
|
|
|
&pProperties->properties
|
|
|
);
|
|
|
|
|
|
/* FIXME: This is unsupported on PC, do we care about this? */
|
|
|
pProperties->backgroundMusic = 0;
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pWave->parentBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
/* Cue implementation */
|
|
|
|
|
|
uint32_t FACTCue_Destroy(FACTCue *pCue)
|
|
|
{
|
|
|
FACTCue *cue, *prev;
|
|
|
FAudioMutex mutex;
|
|
|
FACTNotification note;
|
|
|
if (pCue == NULL)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(pCue->parentBank->parentEngine->apiLock);
|
|
|
|
|
|
/* Stop before we start deleting everything */
|
|
|
FACTCue_Stop(pCue, FACT_FLAG_STOP_IMMEDIATE);
|
|
|
|
|
|
/* Remove this Cue from the SoundBank list */
|
|
|
cue = pCue->parentBank->cueList;
|
|
|
prev = cue;
|
|
|
while (cue != NULL)
|
|
|
{
|
|
|
if (cue == pCue)
|
|
|
{
|
|
|
if (cue == prev) /* First in list */
|
|
|
{
|
|
|
pCue->parentBank->cueList = cue->next;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
prev->next = cue->next;
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
prev = cue;
|
|
|
cue = cue->next;
|
|
|
}
|
|
|
FAudio_assert(cue != NULL && "Could not find Cue reference!");
|
|
|
|
|
|
pCue->parentBank->parentEngine->pFree(pCue->variableValues);
|
|
|
if (pCue->notifyOnDestroy || pCue->parentBank->parentEngine->notifications & NOTIFY_CUEDESTROY)
|
|
|
{
|
|
|
note.type = FACTNOTIFICATIONTYPE_CUEDESTROYED;
|
|
|
note.cue.pCue = pCue;
|
|
|
if (pCue->parentBank->parentEngine->notifications & NOTIFY_CUEDESTROY)
|
|
|
{
|
|
|
note.pvContext = pCue->parentBank->parentEngine->cue_context;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
note.pvContext = pCue->usercontext;
|
|
|
}
|
|
|
pCue->parentBank->parentEngine->notificationCallback(¬e);
|
|
|
}
|
|
|
|
|
|
mutex = pCue->parentBank->parentEngine->apiLock;
|
|
|
pCue->parentBank->parentEngine->pFree(pCue);
|
|
|
FAudio_PlatformUnlockMutex(mutex);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTCue_Play(FACTCue *pCue)
|
|
|
{
|
|
|
union
|
|
|
{
|
|
|
float maxf;
|
|
|
uint8_t maxi;
|
|
|
} limitmax;
|
|
|
FACTCue *tmp, *wnr;
|
|
|
uint16_t fadeInMS = 0;
|
|
|
FACTCueData *data;
|
|
|
if (pCue == NULL)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(pCue->parentBank->parentEngine->apiLock);
|
|
|
|
|
|
FAudio_assert(!(pCue->state & (FACT_STATE_PLAYING | FACT_STATE_STOPPING)));
|
|
|
|
|
|
data = &pCue->parentBank->cues[pCue->index];
|
|
|
|
|
|
/* Cue Instance Limits */
|
|
|
if (data->instanceCount >= data->instanceLimit)
|
|
|
{
|
|
|
wnr = NULL;
|
|
|
tmp = pCue->parentBank->cueList;
|
|
|
if (data->maxInstanceBehavior == 0) /* Fail */
|
|
|
{
|
|
|
pCue->state |= FACT_STATE_STOPPED;
|
|
|
pCue->state &= ~(
|
|
|
FACT_STATE_PLAYING |
|
|
|
FACT_STATE_STOPPING |
|
|
|
FACT_STATE_PAUSED
|
|
|
);
|
|
|
FAudio_PlatformUnlockMutex(
|
|
|
pCue->parentBank->parentEngine->apiLock
|
|
|
);
|
|
|
return 1;
|
|
|
}
|
|
|
else if (data->maxInstanceBehavior == 1) /* Queue */
|
|
|
{
|
|
|
/* FIXME: How is this different from Replace Oldest? */
|
|
|
while (tmp != NULL)
|
|
|
{
|
|
|
if ( tmp != pCue &&
|
|
|
tmp->index == pCue->index &&
|
|
|
!(tmp->state & (FACT_STATE_STOPPING | FACT_STATE_STOPPED)) )
|
|
|
{
|
|
|
wnr = tmp;
|
|
|
break;
|
|
|
}
|
|
|
tmp = tmp->next;
|
|
|
}
|
|
|
}
|
|
|
else if (data->maxInstanceBehavior == 2) /* Replace Oldest */
|
|
|
{
|
|
|
while (tmp != NULL)
|
|
|
{
|
|
|
if ( tmp != pCue &&
|
|
|
tmp->index == pCue->index &&
|
|
|
!(tmp->state & (FACT_STATE_STOPPING | FACT_STATE_STOPPED)) )
|
|
|
{
|
|
|
wnr = tmp;
|
|
|
break;
|
|
|
}
|
|
|
tmp = tmp->next;
|
|
|
}
|
|
|
}
|
|
|
else if (data->maxInstanceBehavior == 3) /* Replace Quietest */
|
|
|
{
|
|
|
limitmax.maxf = FACTVOLUME_MAX;
|
|
|
while (tmp != NULL)
|
|
|
{
|
|
|
if ( tmp != pCue &&
|
|
|
tmp->index == pCue->index &&
|
|
|
tmp->playingSound != NULL &&
|
|
|
/*FIXME: tmp->playingSound->volume < limitmax.maxf &&*/
|
|
|
!(tmp->state & (FACT_STATE_STOPPING | FACT_STATE_STOPPED)) )
|
|
|
{
|
|
|
wnr = tmp;
|
|
|
/* limitmax.maxf = tmp->playingSound->volume; */
|
|
|
}
|
|
|
tmp = tmp->next;
|
|
|
}
|
|
|
}
|
|
|
else if (data->maxInstanceBehavior == 4) /* Replace Lowest Priority */
|
|
|
{
|
|
|
limitmax.maxi = 0xFF;
|
|
|
while (tmp != NULL)
|
|
|
{
|
|
|
if ( tmp != pCue &&
|
|
|
tmp->index == pCue->index &&
|
|
|
tmp->playingSound != NULL &&
|
|
|
tmp->playingSound->sound->priority < limitmax.maxi &&
|
|
|
!(tmp->state & (FACT_STATE_STOPPING | FACT_STATE_STOPPED)) )
|
|
|
{
|
|
|
wnr = tmp;
|
|
|
limitmax.maxi = tmp->playingSound->sound->priority;
|
|
|
}
|
|
|
tmp = tmp->next;
|
|
|
}
|
|
|
}
|
|
|
if (wnr != NULL)
|
|
|
{
|
|
|
fadeInMS = data->fadeInMS;
|
|
|
if (wnr->playingSound != NULL)
|
|
|
{
|
|
|
FACT_INTERNAL_BeginFadeOut(wnr->playingSound, data->fadeOutMS);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
FACTCue_Stop(wnr, 0);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* Need an initial sound to play */
|
|
|
if (!FACT_INTERNAL_CreateSound(pCue, fadeInMS))
|
|
|
{
|
|
|
FAudio_PlatformUnlockMutex(
|
|
|
pCue->parentBank->parentEngine->apiLock
|
|
|
);
|
|
|
return 1;
|
|
|
}
|
|
|
data->instanceCount += 1;
|
|
|
|
|
|
pCue->state |= FACT_STATE_PLAYING;
|
|
|
pCue->state &= ~(
|
|
|
FACT_STATE_PAUSED |
|
|
|
FACT_STATE_STOPPED |
|
|
|
FACT_STATE_PREPARED
|
|
|
);
|
|
|
pCue->start = FAudio_timems();
|
|
|
|
|
|
/* If it's a simple wave, just play it! */
|
|
|
if (pCue->simpleWave != NULL)
|
|
|
{
|
|
|
if (pCue->active3D)
|
|
|
{
|
|
|
FACTWave_SetMatrixCoefficients(
|
|
|
pCue->simpleWave,
|
|
|
pCue->srcChannels,
|
|
|
pCue->dstChannels,
|
|
|
pCue->matrixCoefficients
|
|
|
);
|
|
|
}
|
|
|
FACTWave_Play(pCue->simpleWave);
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pCue->parentBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTCue_Stop(FACTCue *pCue, uint32_t dwFlags)
|
|
|
{
|
|
|
if (pCue == NULL)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
FAudio_PlatformLockMutex(pCue->parentBank->parentEngine->apiLock);
|
|
|
|
|
|
/* If we're already stopped, there's nothing to do... */
|
|
|
if (pCue->state & FACT_STATE_STOPPED)
|
|
|
{
|
|
|
FAudio_PlatformUnlockMutex(
|
|
|
pCue->parentBank->parentEngine->apiLock
|
|
|
);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
/* If we're stopping and we haven't asked for IMMEDIATE, we're already
|
|
|
* doing what the application is asking us to do...
|
|
|
*/
|
|
|
if ( (pCue->state & FACT_STATE_STOPPING) &&
|
|
|
!(dwFlags & FACT_FLAG_STOP_IMMEDIATE) )
|
|
|
{
|
|
|
FAudio_PlatformUnlockMutex(
|
|
|
pCue->parentBank->parentEngine->apiLock
|
|
|
);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
/* There are three ways that a Cue might be stopped immediately:
|
|
|
* 1. The program explicitly asks for it
|
|
|
* 2. The Cue is paused and therefore we can't do fade/release effects
|
|
|
* 3. The Cue is stopped "as authored" and has no fade effects
|
|
|
*/
|
|
|
if ( dwFlags & FACT_FLAG_STOP_IMMEDIATE ||
|
|
|
pCue->state & FACT_STATE_PAUSED ||
|
|
|
pCue->playingSound == NULL ||
|
|
|
( pCue->parentBank->cues[pCue->index].fadeOutMS == 0 &&
|
|
|
pCue->maxRpcReleaseTime == 0 ) )
|
|
|
{
|
|
|
pCue->start = 0;
|
|
|
pCue->elapsed = 0;
|
|
|
pCue->state |= FACT_STATE_STOPPED;
|
|
|
pCue->state &= ~(
|
|
|
FACT_STATE_PLAYING |
|
|
|
FACT_STATE_STOPPING |
|
|
|
FACT_STATE_PAUSED
|
|
|
);
|
|
|
|
|
|
if (pCue->simpleWave != NULL)
|
|
|
{
|
|
|
FACTWave_Destroy(pCue->simpleWave);
|
|
|
pCue->simpleWave = NULL;
|
|
|
|
|
|
pCue->data->instanceCount -= 1;
|
|
|
}
|
|
|
else if (pCue->playingSound != NULL)
|
|
|
{
|
|
|
FACT_INTERNAL_DestroySound(pCue->playingSound);
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
if (pCue->parentBank->cues[pCue->index].fadeOutMS > 0)
|
|
|
{
|
|
|
FACT_INTERNAL_BeginFadeOut(
|
|
|
pCue->playingSound,
|
|
|
pCue->parentBank->cues[pCue->index].fadeOutMS
|
|
|
);
|
|
|
}
|
|
|
else if (pCue->maxRpcReleaseTime > 0)
|
|
|
{
|
|
|
FACT_INTERNAL_BeginReleaseRPC(
|
|
|
pCue->playingSound,
|
|
|
pCue->maxRpcReleaseTime
|
|
|
);
|
|
|
}
|
|
|
|
|
|
pCue->state |= FACT_STATE_STOPPING;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pCue->parentBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTCue_GetState(FACTCue *pCue, uint32_t *pdwState)
|
|
|
{
|
|
|
if (pCue == NULL)
|
|
|
{
|
|
|
*pdwState = 0;
|
|
|
return 1;
|
|
|
}
|
|
|
FAudio_PlatformLockMutex(pCue->parentBank->parentEngine->apiLock);
|
|
|
*pdwState = pCue->state;
|
|
|
FAudio_PlatformUnlockMutex(pCue->parentBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTCue_SetMatrixCoefficients(
|
|
|
FACTCue *pCue,
|
|
|
uint32_t uSrcChannelCount,
|
|
|
uint32_t uDstChannelCount,
|
|
|
float *pMatrixCoefficients
|
|
|
) {
|
|
|
uint8_t i;
|
|
|
|
|
|
FAudio_PlatformLockMutex(pCue->parentBank->parentEngine->apiLock);
|
|
|
|
|
|
/* See FACTCue.matrixCoefficients declaration */
|
|
|
FAudio_assert(uSrcChannelCount > 0 && uSrcChannelCount < 3);
|
|
|
FAudio_assert(uDstChannelCount > 0 && uDstChannelCount < 9);
|
|
|
|
|
|
/* Local storage */
|
|
|
pCue->srcChannels = uSrcChannelCount;
|
|
|
pCue->dstChannels = uDstChannelCount;
|
|
|
FAudio_memcpy(
|
|
|
pCue->matrixCoefficients,
|
|
|
pMatrixCoefficients,
|
|
|
sizeof(float) * uSrcChannelCount * uDstChannelCount
|
|
|
);
|
|
|
pCue->active3D = 1;
|
|
|
|
|
|
/* Apply to Waves if they exist */
|
|
|
if (pCue->simpleWave != NULL)
|
|
|
{
|
|
|
FACTWave_SetMatrixCoefficients(
|
|
|
pCue->simpleWave,
|
|
|
uSrcChannelCount,
|
|
|
uDstChannelCount,
|
|
|
pMatrixCoefficients
|
|
|
);
|
|
|
}
|
|
|
else if (pCue->playingSound != NULL)
|
|
|
{
|
|
|
for (i = 0; i < pCue->playingSound->sound->trackCount; i += 1)
|
|
|
{
|
|
|
if (pCue->playingSound->tracks[i].activeWave.wave != NULL)
|
|
|
{
|
|
|
FACTWave_SetMatrixCoefficients(
|
|
|
pCue->playingSound->tracks[i].activeWave.wave,
|
|
|
uSrcChannelCount,
|
|
|
uDstChannelCount,
|
|
|
pMatrixCoefficients
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pCue->parentBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint16_t FACTCue_GetVariableIndex(
|
|
|
FACTCue *pCue,
|
|
|
const char *szFriendlyName
|
|
|
) {
|
|
|
uint16_t i;
|
|
|
if (pCue == NULL)
|
|
|
{
|
|
|
return FACTVARIABLEINDEX_INVALID;
|
|
|
}
|
|
|
FAudio_PlatformLockMutex(pCue->parentBank->parentEngine->apiLock);
|
|
|
for (i = 0; i < pCue->parentBank->parentEngine->variableCount; i += 1)
|
|
|
{
|
|
|
if ( FAudio_strcmp(szFriendlyName, pCue->parentBank->parentEngine->variableNames[i]) == 0 &&
|
|
|
pCue->parentBank->parentEngine->variables[i].accessibility & 0x04 )
|
|
|
{
|
|
|
FAudio_PlatformUnlockMutex(
|
|
|
pCue->parentBank->parentEngine->apiLock
|
|
|
);
|
|
|
return i;
|
|
|
}
|
|
|
}
|
|
|
FAudio_PlatformUnlockMutex(pCue->parentBank->parentEngine->apiLock);
|
|
|
return FACTVARIABLEINDEX_INVALID;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTCue_SetVariable(
|
|
|
FACTCue *pCue,
|
|
|
uint16_t nIndex,
|
|
|
float nValue
|
|
|
) {
|
|
|
FACTVariable *var;
|
|
|
if (pCue == NULL)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
if (nIndex == FACTINDEX_INVALID)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(pCue->parentBank->parentEngine->apiLock);
|
|
|
|
|
|
var = &pCue->parentBank->parentEngine->variables[nIndex];
|
|
|
FAudio_assert(var->accessibility & 0x01);
|
|
|
FAudio_assert(!(var->accessibility & 0x02));
|
|
|
FAudio_assert(var->accessibility & 0x04);
|
|
|
pCue->variableValues[nIndex] = FAudio_clamp(
|
|
|
nValue,
|
|
|
var->minValue,
|
|
|
var->maxValue
|
|
|
);
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pCue->parentBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTCue_GetVariable(
|
|
|
FACTCue *pCue,
|
|
|
uint16_t nIndex,
|
|
|
float *nValue
|
|
|
) {
|
|
|
FACTVariable *var;
|
|
|
if (pCue == NULL)
|
|
|
{
|
|
|
*nValue = 0.0f;
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
if (nIndex == FACTINDEX_INVALID)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(pCue->parentBank->parentEngine->apiLock);
|
|
|
|
|
|
var = &pCue->parentBank->parentEngine->variables[nIndex];
|
|
|
FAudio_assert(var->accessibility & 0x01);
|
|
|
FAudio_assert(var->accessibility & 0x04);
|
|
|
|
|
|
if (nIndex == 0) /* NumCueInstances */
|
|
|
{
|
|
|
*nValue = pCue->parentBank->cues[pCue->index].instanceCount;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
*nValue = pCue->variableValues[nIndex];
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pCue->parentBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTCue_Pause(FACTCue *pCue, int32_t fPause)
|
|
|
{
|
|
|
uint8_t i;
|
|
|
if (pCue == NULL)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(pCue->parentBank->parentEngine->apiLock);
|
|
|
|
|
|
/* "A stopping or stopped cue cannot be paused." */
|
|
|
if (pCue->state & (FACT_STATE_STOPPING | FACT_STATE_STOPPED))
|
|
|
{
|
|
|
FAudio_PlatformUnlockMutex(
|
|
|
pCue->parentBank->parentEngine->apiLock
|
|
|
);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
/* Store elapsed time */
|
|
|
pCue->elapsed += FAudio_timems() - pCue->start;
|
|
|
|
|
|
/* All we do is set the flag, not much to see here */
|
|
|
if (fPause)
|
|
|
{
|
|
|
pCue->state |= FACT_STATE_PAUSED;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
pCue->state &= ~FACT_STATE_PAUSED;
|
|
|
}
|
|
|
|
|
|
/* Pause the Waves */
|
|
|
if (pCue->simpleWave != NULL)
|
|
|
{
|
|
|
FACTWave_Pause(pCue->simpleWave, fPause);
|
|
|
}
|
|
|
else if (pCue->playingSound != NULL)
|
|
|
{
|
|
|
for (i = 0; i < pCue->playingSound->sound->trackCount; i += 1)
|
|
|
{
|
|
|
if (pCue->playingSound->tracks[i].activeWave.wave != NULL)
|
|
|
{
|
|
|
FACTWave_Pause(
|
|
|
pCue->playingSound->tracks[i].activeWave.wave,
|
|
|
fPause
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pCue->parentBank->parentEngine->apiLock);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTCue_GetProperties(
|
|
|
FACTCue *pCue,
|
|
|
FACTCueInstanceProperties **ppProperties
|
|
|
) {
|
|
|
uint32_t i;
|
|
|
size_t allocSize;
|
|
|
FACTCueInstanceProperties *cueProps;
|
|
|
FACTVariationProperties *varProps;
|
|
|
FACTSoundProperties *sndProps;
|
|
|
FACTWaveInstanceProperties waveProps;
|
|
|
if (pCue == NULL)
|
|
|
{
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(pCue->parentBank->parentEngine->apiLock);
|
|
|
|
|
|
/* Alloc container (including variable length array space) */
|
|
|
allocSize = sizeof(FACTCueInstanceProperties);
|
|
|
if (pCue->playingSound != NULL)
|
|
|
{
|
|
|
allocSize += (
|
|
|
sizeof(FACTTrackProperties) *
|
|
|
pCue->playingSound->sound->trackCount
|
|
|
);
|
|
|
}
|
|
|
cueProps = (FACTCueInstanceProperties*) pCue->parentBank->parentEngine->pMalloc(
|
|
|
allocSize
|
|
|
);
|
|
|
FAudio_zero(cueProps, allocSize);
|
|
|
|
|
|
/* Cue Properties */
|
|
|
FACTSoundBank_GetCueProperties(
|
|
|
pCue->parentBank,
|
|
|
pCue->index,
|
|
|
&cueProps->cueProperties
|
|
|
);
|
|
|
|
|
|
/* Variation Properties */
|
|
|
varProps = &cueProps->activeVariationProperties.variationProperties;
|
|
|
if (pCue->playingVariation != NULL)
|
|
|
{
|
|
|
varProps->index = 0; /* TODO: Index of what...? */
|
|
|
/* TODO: This is just max - min right? Also why u8 wtf */
|
|
|
varProps->weight = (uint8_t) (
|
|
|
pCue->playingVariation->maxWeight -
|
|
|
pCue->playingVariation->minWeight
|
|
|
);
|
|
|
if (pCue->variation->flags == 3)
|
|
|
{
|
|
|
varProps->iaVariableMin =
|
|
|
pCue->playingVariation->minWeight;
|
|
|
varProps->iaVariableMax =
|
|
|
pCue->playingVariation->maxWeight;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
varProps->iaVariableMin = 0;
|
|
|
varProps->iaVariableMax = 0;
|
|
|
}
|
|
|
varProps->linger = pCue->playingVariation->linger;
|
|
|
}
|
|
|
|
|
|
/* Sound Properties */
|
|
|
sndProps = &cueProps->activeVariationProperties.soundProperties;
|
|
|
if (pCue->playingSound != NULL)
|
|
|
{
|
|
|
sndProps->category = pCue->playingSound->sound->category;
|
|
|
sndProps->priority = pCue->playingSound->sound->priority;
|
|
|
sndProps->pitch = pCue->playingSound->sound->pitch;
|
|
|
sndProps->volume = pCue->playingSound->sound->volume;
|
|
|
sndProps->numTracks = pCue->playingSound->sound->trackCount;
|
|
|
|
|
|
for (i = 0; i < sndProps->numTracks; i += 1)
|
|
|
{
|
|
|
if (FACTWave_GetProperties(
|
|
|
pCue->playingSound->tracks[i].activeWave.wave,
|
|
|
&waveProps
|
|
|
) == 0) {
|
|
|
sndProps->arrTrackProperties[i].duration = (uint32_t) (
|
|
|
(
|
|
|
(float) waveProps.properties.durationInSamples /
|
|
|
(float) waveProps.properties.format.nSamplesPerSec
|
|
|
) / 1000.0f
|
|
|
);
|
|
|
sndProps->arrTrackProperties[i].numVariations = 1; /* ? */
|
|
|
sndProps->arrTrackProperties[i].numChannels =
|
|
|
waveProps.properties.format.nChannels;
|
|
|
sndProps->arrTrackProperties[i].waveVariation = 0; /* ? */
|
|
|
sndProps->arrTrackProperties[i].loopCount =
|
|
|
pCue->playingSound->tracks[i].waveEvt->wave.loopCount;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(pCue->parentBank->parentEngine->apiLock);
|
|
|
|
|
|
*ppProperties = cueProps;
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTCue_SetOutputVoices(
|
|
|
FACTCue *pCue,
|
|
|
const FAudioVoiceSends *pSendList /* Optional XAUDIO2_VOICE_SENDS */
|
|
|
) {
|
|
|
/* TODO */
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FACTCue_SetOutputVoiceMatrix(
|
|
|
FACTCue *pCue,
|
|
|
const FAudioVoice *pDestinationVoice, /* Optional! */
|
|
|
uint32_t SourceChannels,
|
|
|
uint32_t DestinationChannels,
|
|
|
const float *pLevelMatrix /* SourceChannels * DestinationChannels */
|
|
|
) {
|
|
|
/* TODO */
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
/* vim: set noexpandtab shiftwidth=8 tabstop=8: */
|
|
|
|