/* 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 * */ #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: */