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