From 1cf5d965c4e0357870e4ad6797420fca764ac8fc Mon Sep 17 00:00:00 2001 From: Dennis Brakhane Date: Fri, 17 Jan 2025 23:14:58 +0100 Subject: [PATCH] Linux: update FAudio to 25.01 --- WickedEngine/Utility/FAudio/CMakeLists.txt | 54 +- .../Utility/FAudio/include/F3DAudio.h | 2 +- WickedEngine/Utility/FAudio/include/FACT.h | 2 +- WickedEngine/Utility/FAudio/include/FACT3D.h | 2 +- WickedEngine/Utility/FAudio/include/FAPO.h | 2 +- .../Utility/FAudio/include/FAPOBase.h | 2 +- WickedEngine/Utility/FAudio/include/FAPOFX.h | 2 +- WickedEngine/Utility/FAudio/include/FAudio.h | 72 +- .../Utility/FAudio/include/FAudioFX.h | 2 +- WickedEngine/Utility/FAudio/src/F3DAudio.c | 10 +- WickedEngine/Utility/FAudio/src/FACT.c | 107 ++- WickedEngine/Utility/FAudio/src/FACT3D.c | 2 +- .../Utility/FAudio/src/FACT_internal.c | 131 ++- .../Utility/FAudio/src/FACT_internal.h | 6 +- WickedEngine/Utility/FAudio/src/FAPOBase.c | 2 +- WickedEngine/Utility/FAudio/src/FAPOFX.c | 2 +- WickedEngine/Utility/FAudio/src/FAPOFX_echo.c | 2 +- WickedEngine/Utility/FAudio/src/FAPOFX_eq.c | 2 +- .../FAudio/src/FAPOFX_masteringlimiter.c | 2 +- .../Utility/FAudio/src/FAPOFX_reverb.c | 2 +- WickedEngine/Utility/FAudio/src/FAudio.c | 203 +++- .../Utility/FAudio/src/FAudioFX_reverb.c | 111 ++- .../Utility/FAudio/src/FAudioFX_volumemeter.c | 2 +- .../Utility/FAudio/src/FAudio_gstreamer.c | 892 ------------------ .../Utility/FAudio/src/FAudio_internal.c | 83 +- .../Utility/FAudio/src/FAudio_internal.h | 74 +- .../Utility/FAudio/src/FAudio_internal_simd.c | 34 +- .../Utility/FAudio/src/FAudio_operationset.c | 18 +- .../Utility/FAudio/src/FAudio_platform_sdl2.c | 208 ++-- .../Utility/FAudio/src/FAudio_platform_sdl3.c | 734 ++++++++++++++ .../FAudio/src/FAudio_platform_win32.c | 403 ++++++-- WickedEngine/Utility/FAudio/src/XNA_Song.c | 141 ++- .../Utility/FAudio/src/matrix_defaults.inl | 62 ++ WickedEngine/Utility/FAudio/src/qoa_decoder.h | 536 +++++++++++ WickedEngine/Utility/FAudio/src/stb.h | 2 +- WickedEngine/Utility/FAudio/src/stb_vorbis.h | 2 +- WickedEngine/wiAudio.cpp | 4 +- 37 files changed, 2567 insertions(+), 1350 deletions(-) delete mode 100644 WickedEngine/Utility/FAudio/src/FAudio_gstreamer.c create mode 100644 WickedEngine/Utility/FAudio/src/FAudio_platform_sdl3.c create mode 100644 WickedEngine/Utility/FAudio/src/qoa_decoder.h diff --git a/WickedEngine/Utility/FAudio/CMakeLists.txt b/WickedEngine/Utility/FAudio/CMakeLists.txt index d576afe822..e48728f290 100644 --- a/WickedEngine/Utility/FAudio/CMakeLists.txt +++ b/WickedEngine/Utility/FAudio/CMakeLists.txt @@ -1,10 +1,18 @@ +# Based on upstream's CMakeLists.txt +# Changes compared to upstream: +# * cmake_minimum_required adjusted +# * unused options removed +# * windows related stuff removed +# * install rule added + # CMake Project for FAudio # Written by @NeroBurner -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.12) project(FAudio C) # Options option(BUILD_SHARED_LIBS "Build shared library" OFF) +option(BUILD_SDL3 "Build against SDL 3.0" OFF) # C99 Requirement set(CMAKE_C_STANDARD 99) @@ -12,8 +20,8 @@ set(CMAKE_C_EXTENSIONS OFF) # Version SET(LIB_MAJOR_VERSION "0") -SET(LIB_MINOR_VERSION "21") -SET(LIB_REVISION "09") +SET(LIB_MINOR_VERSION "25") +SET(LIB_REVISION "01") SET(LIB_VERSION "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_REVISION}") # Build Type @@ -50,7 +58,7 @@ add_library(FAudio STATIC src/FAudio_internal.h src/stb.h src/stb_vorbis.h - src/matrix_defaults.inl + src/qoa_decoder.h # Source Files src/F3DAudio.c src/FACT3D.c @@ -69,10 +77,10 @@ add_library(FAudio STATIC src/FAudio_internal_simd.c src/FAudio_operationset.c src/FAudio_platform_sdl2.c + src/FAudio_platform_sdl3.c src/FAudio_platform_win32.c # Optional source files src/XNA_Song.c - src/FAudio_gstreamer.c ) install(FILES ${PUBLIC_HEADERS} @@ -92,9 +100,41 @@ set_target_properties(FAudio PROPERTIES SOVERSION ${LIB_MAJOR_VERSION} ) -# SDL2 Dependency +# SDL Dependency if (PLATFORM_WIN32) - message(STATUS "not using SDL2") + message(STATUS "not using SDL") +elseif (BUILD_SDL3) + if (DEFINED SDL3_INCLUDE_DIRS AND DEFINED SDL3_LIBRARIES) + message(STATUS "using pre-defined SDL3 variables SDL3_INCLUDE_DIRS and SDL3_LIBRARIES") + target_include_directories(FAudio PUBLIC "$") + target_link_libraries(FAudio PUBLIC ${SDL3_LIBRARIES}) + if(INSTALL_MINGW_DEPENDENCIES) + install_shared_libs(${SDL3_LIBRARIES} DESTINATION bin NO_INSTALL_SYMLINKS) + endif() + else() + # Only try to autodetect if both SDL3 variables aren't explicitly set + find_package(SDL3 CONFIG) + if (TARGET SDL3::SDL3) + message(STATUS "using TARGET SDL3::SDL3") + target_link_libraries(FAudio PUBLIC SDL3::SDL3) + if(INSTALL_MINGW_DEPENDENCIES) + install_shared_libs(TARGETS SDL3::SDL3 DESTINATION bin NO_INSTALL_SYMLINKS REQUIRED) + endif() + elseif (TARGET SDL3) + message(STATUS "using TARGET SDL3") + target_link_libraries(FAudio PUBLIC SDL3) + if(INSTALL_MINGW_DEPENDENCIES) + install_shared_libs(TARGETS SDL3 DESTINATION bin NO_INSTALL_SYMLINKS REQUIRED) + endif() + else() + message(STATUS "no TARGET SDL3::SDL3, or SDL3, using variables") + target_include_directories(FAudio PUBLIC "$") + target_link_libraries(FAudio PUBLIC ${SDL3_LIBRARIES}) + if(INSTALL_MINGW_DEPENDENCIES) + install_shared_libs(${SDL3_LIBRARIES} DESTINATION bin NO_INSTALL_SYMLINKS) + endif() + endif() + endif() elseif (DEFINED SDL2_INCLUDE_DIRS AND DEFINED SDL2_LIBRARIES) message(STATUS "using pre-defined SDL2 variables SDL2_INCLUDE_DIRS and SDL2_LIBRARIES") target_include_directories(FAudio PUBLIC "$") diff --git a/WickedEngine/Utility/FAudio/include/F3DAudio.h b/WickedEngine/Utility/FAudio/include/F3DAudio.h index 5fbb618243..cca0432c75 100644 --- a/WickedEngine/Utility/FAudio/include/F3DAudio.h +++ b/WickedEngine/Utility/FAudio/include/F3DAudio.h @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 diff --git a/WickedEngine/Utility/FAudio/include/FACT.h b/WickedEngine/Utility/FAudio/include/FACT.h index 579b716881..7134f5ccc1 100644 --- a/WickedEngine/Utility/FAudio/include/FACT.h +++ b/WickedEngine/Utility/FAudio/include/FACT.h @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 diff --git a/WickedEngine/Utility/FAudio/include/FACT3D.h b/WickedEngine/Utility/FAudio/include/FACT3D.h index 6ac02a8298..b40224179c 100644 --- a/WickedEngine/Utility/FAudio/include/FACT3D.h +++ b/WickedEngine/Utility/FAudio/include/FACT3D.h @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 diff --git a/WickedEngine/Utility/FAudio/include/FAPO.h b/WickedEngine/Utility/FAudio/include/FAPO.h index 0609770047..5b8442bc6f 100644 --- a/WickedEngine/Utility/FAudio/include/FAPO.h +++ b/WickedEngine/Utility/FAudio/include/FAPO.h @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 diff --git a/WickedEngine/Utility/FAudio/include/FAPOBase.h b/WickedEngine/Utility/FAudio/include/FAPOBase.h index c2fcdd2981..a9351cd3c9 100644 --- a/WickedEngine/Utility/FAudio/include/FAPOBase.h +++ b/WickedEngine/Utility/FAudio/include/FAPOBase.h @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 diff --git a/WickedEngine/Utility/FAudio/include/FAPOFX.h b/WickedEngine/Utility/FAudio/include/FAPOFX.h index 5aae441b2c..3600886e55 100644 --- a/WickedEngine/Utility/FAudio/include/FAPOFX.h +++ b/WickedEngine/Utility/FAudio/include/FAPOFX.h @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 diff --git a/WickedEngine/Utility/FAudio/include/FAudio.h b/WickedEngine/Utility/FAudio/include/FAudio.h index b3699d8239..34daf8ea74 100644 --- a/WickedEngine/Utility/FAudio/include/FAudio.h +++ b/WickedEngine/Utility/FAudio/include/FAudio.h @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 @@ -239,6 +239,14 @@ typedef struct FAudioFilterParameters float OneOverQ; /* [0, FAUDIO_MAX_FILTER_ONEOVERQ] */ } FAudioFilterParameters; +typedef struct FAudioFilterParametersEXT +{ + FAudioFilterType Type; + float Frequency; /* [0, FAUDIO_MAX_FILTER_FREQUENCY] */ + float OneOverQ; /* [0, FAUDIO_MAX_FILTER_ONEOVERQ] */ + float WetDryMix; /* [0, 1] */ +} FAudioFilterParametersEXT; + typedef struct FAudioBuffer { /* Either 0 or FAUDIO_END_OF_STREAM */ @@ -375,6 +383,7 @@ typedef struct FAudioXMA2WaveFormatEx #define FAUDIO_DEFAULT_FILTER_TYPE FAudioLowPassFilter #define FAUDIO_DEFAULT_FILTER_FREQUENCY FAUDIO_MAX_FILTER_FREQUENCY #define FAUDIO_DEFAULT_FILTER_ONEOVERQ 1.0f +#define FAUDIO_DEFAULT_FILTER_WETDRYMIX_EXT 1.0f #define FAUDIO_LOG_ERRORS 0x0001 #define FAUDIO_LOG_WARNINGS 0x0002 @@ -484,8 +493,8 @@ extern FAudioGUID DATAFORMAT_SUBTYPE_IEEE_FLOAT; #define FAUDIO_TARGET_VERSION 8 /* Targeting compatibility with XAudio 2.8 */ #define FAUDIO_ABI_VERSION 0 -#define FAUDIO_MAJOR_VERSION 21 -#define FAUDIO_MINOR_VERSION 9 +#define FAUDIO_MAJOR_VERSION 25 +#define FAUDIO_MINOR_VERSION 1 #define FAUDIO_PATCH_VERSION 0 #define FAUDIO_COMPILED_VERSION ( \ @@ -909,6 +918,58 @@ FAUDIOAPI void FAudioVoice_GetOutputFilterParameters( FAudioFilterParameters *pParameters ); +/* Sets the filter variables for a voice. + * This is only valid on voices with the USEFILTER flag. + * + * pParameters: See FAudioFilterParametersEXT for details. + * OperationSet: See CommitChanges. Default is FAUDIO_COMMIT_NOW. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudioVoice_SetFilterParametersEXT( + FAudioVoice* voice, + const FAudioFilterParametersEXT* pParameters, + uint32_t OperationSet +); + +/* Requests the filter variables for a voice. + * This is only valid on voices with the USEFILTER flag. + * + * pParameters: See FAudioFilterParametersEXT for details. + */ +FAUDIOAPI void FAudioVoice_GetFilterParametersEXT( + FAudioVoice* voice, + FAudioFilterParametersEXT* pParameters +); + +/* Sets the filter variables for a voice's output voice. + * This is only valid on sends with the USEFILTER flag. + * + * pDestinationVoice: An output voice from the voice's send list. + * pParameters: See FAudioFilterParametersEXT for details. + * OperationSet: See CommitChanges. Default is FAUDIO_COMMIT_NOW. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudioVoice_SetOutputFilterParametersEXT( + FAudioVoice* voice, + FAudioVoice* pDestinationVoice, + const FAudioFilterParametersEXT* pParameters, + uint32_t OperationSet +); + +/* Requests the filter variables for a voice's output voice. + * This is only valid on sends with the USEFILTER flag. + * + * pDestinationVoice: An output voice from the voice's send list. + * pParameters: See FAudioFilterParametersEXT for details. + */ +FAUDIOAPI void FAudioVoice_GetOutputFilterParametersEXT( + FAudioVoice* voice, + FAudioVoice* pDestinationVoice, + FAudioFilterParametersEXT* pParameters +); + /* Sets the global volume of a voice. * * Volume: Amplitude ratio. 1.0f is default, 0.0f is silence. @@ -1003,6 +1064,11 @@ FAUDIOAPI void FAudioVoice_GetOutputMatrix( /* Removes this voice from the audio graph and frees memory. */ FAUDIOAPI void FAudioVoice_DestroyVoice(FAudioVoice *voice); +/* + * Returns S_OK on success and E_FAIL if voice could not be destroyed (e. g., because it is in use). + */ +FAUDIOAPI uint32_t FAudioVoice_DestroyVoiceSafeEXT(FAudioVoice *voice); + /* FAudioSourceVoice Interface */ /* Starts processing for a source voice. diff --git a/WickedEngine/Utility/FAudio/include/FAudioFX.h b/WickedEngine/Utility/FAudio/include/FAudioFX.h index 22052d33e8..2ed0d2ab8f 100644 --- a/WickedEngine/Utility/FAudio/include/FAudioFX.h +++ b/WickedEngine/Utility/FAudio/include/FAudioFX.h @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 diff --git a/WickedEngine/Utility/FAudio/src/F3DAudio.c b/WickedEngine/Utility/FAudio/src/F3DAudio.c index bc2b9edad2..8da22fcb01 100644 --- a/WickedEngine/Utility/FAudio/src/F3DAudio.c +++ b/WickedEngine/Utility/FAudio/src/F3DAudio.c @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 @@ -1274,7 +1274,7 @@ static inline void CalculateMatrix( } } } - else + else if (curConfig != NULL) { listenerToEmitter = VectorScale(emitterToListener, -1.0f); @@ -1351,8 +1351,10 @@ static inline void CalculateMatrix( } } } - - + } + else + { + FAudio_assert(0 && "Config info not found!"); } /* TODO: add post check to validate values diff --git a/WickedEngine/Utility/FAudio/src/FACT.c b/WickedEngine/Utility/FAudio/src/FACT.c index 5eca83b389..013cea36b5 100644 --- a/WickedEngine/Utility/FAudio/src/FACT.c +++ b/WickedEngine/Utility/FAudio/src/FACT.c @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 @@ -290,6 +290,7 @@ uint32_t FACTAudioEngine_Initialize( NULL ) != 0) { FAudio_Release(pEngine->audio); + FAudio_PlatformUnlockMutex(pEngine->apiLock); return FAUDIO_E_INVALID_CALL; } } @@ -352,6 +353,16 @@ uint32_t FACTAudioEngine_ShutDown(FACTAudioEngine *pEngine) FAudio_StopEngine(pEngine->audio); } + /* Purge All pending notifactions */ + while (pEngine->wb_notifications_list) + { + FACTNotification *note = (FACTNotification*) pEngine->wb_notifications_list->entry; + pEngine->notificationCallback(note); + LinkedList_RemoveEntry(&pEngine->wb_notifications_list, note, pEngine->apiLock, pEngine->pFree); + } + + pEngine->notifications = 0; + /* This method destroys all existing cues, sound banks, and wave banks. * It blocks until all cues are destroyed. */ @@ -433,9 +444,17 @@ uint32_t FACTAudioEngine_DoWork(FACTAudioEngine *pEngine) uint8_t i; FACTCue *cue; LinkedList *list; + FACTNotification *note; FAudio_PlatformLockMutex(pEngine->apiLock); + while (pEngine->wb_notifications_list) + { + note = (FACTNotification*) pEngine->wb_notifications_list->entry; + pEngine->notificationCallback(note); + LinkedList_RemoveEntry(&pEngine->wb_notifications_list, note, pEngine->apiLock, pEngine->pFree); + } + list = pEngine->sbList; while (list != NULL) { @@ -495,6 +514,7 @@ uint32_t FACTAudioEngine_CreateInMemoryWaveBank( uint32_t dwAllocAttributes, FACTWaveBank **ppWaveBank ) { + FACTNotification *note; uint32_t retval; FAudio_PlatformLockMutex(pEngine->apiLock); retval = FACT_INTERNAL_ParseWaveBank( @@ -507,6 +527,14 @@ uint32_t FACTAudioEngine_CreateInMemoryWaveBank( 0, ppWaveBank ); + if (pEngine->notifications & NOTIFY_WAVEBANKPREPARED) + { + note = (FACTNotification*) pEngine->pMalloc(sizeof(FACTNotification)); + note->type = FACTNOTIFICATIONTYPE_WAVEBANKPREPARED; + note->waveBank.pWaveBank = *ppWaveBank; + note->pvContext = pEngine->wb_context; + LinkedList_AddEntry(&pEngine->wb_notifications_list, note, pEngine->apiLock, pEngine->pMalloc); + } FAudio_PlatformUnlockMutex(pEngine->apiLock); return retval; } @@ -516,6 +544,7 @@ uint32_t FACTAudioEngine_CreateStreamingWaveBank( const FACTStreamingParameters *pParms, FACTWaveBank **ppWaveBank ) { + FACTNotification *note; uint32_t retval, packetSize; FAudio_PlatformLockMutex(pEngine->apiLock); if ( pEngine->pReadFile == FACT_INTERNAL_DefaultReadFile && @@ -538,6 +567,14 @@ uint32_t FACTAudioEngine_CreateStreamingWaveBank( 1, ppWaveBank ); + if (pEngine->notifications & NOTIFY_WAVEBANKPREPARED) + { + note = (FACTNotification*) pEngine->pMalloc(sizeof(FACTNotification)); + note->type = FACTNOTIFICATIONTYPE_WAVEBANKPREPARED; + note->waveBank.pWaveBank = *ppWaveBank; + note->pvContext = pEngine->wb_context; + LinkedList_AddEntry(&pEngine->wb_notifications_list, note, pEngine->apiLock, pEngine->pMalloc); + } FAudio_PlatformUnlockMutex(pEngine->apiLock); return retval; } @@ -695,6 +732,7 @@ uint32_t FACTAudioEngine_RegisterNotification( /* WaveBanks */ #define PERSIST_ACTION pEngine->wb_context = pNotificationDescription->pvContext; else HANDLE_PERSIST(WAVEBANKPREPARED) + else HANDLE_PERSIST(WAVEBANKSTREAMING_INVALIDCONTENT) #undef PERSIST_ACTION /* Anything else? */ @@ -818,6 +856,7 @@ uint32_t FACTAudioEngine_UnRegisterNotification( /* WaveBanks */ #define PERSIST_ACTION pEngine->wb_context = pNotificationDescription->pvContext; else HANDLE_PERSIST(WAVEBANKPREPARED) + else HANDLE_PERSIST(WAVEBANKSTREAMING_INVALIDCONTENT) #undef PERSIST_ACTION /* Anything else? */ @@ -1200,7 +1239,7 @@ uint32_t FACTSoundBank_Prepare( break; } } - if ((*ppCue)->variation->flags == 3) + if ((*ppCue)->variation && (*ppCue)->variation->flags == 3) { (*ppCue)->interactive = pSoundBank->parentEngine->variables[ (*ppCue)->variation->variable @@ -1383,16 +1422,13 @@ uint32_t FACTSoundBank_Destroy(FACTSoundBank *pSoundBank) 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 - ); - } + /* 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); @@ -1559,16 +1595,13 @@ uint32_t FACTWaveBank_Destroy(FACTWaveBank *pWaveBank) } } - if (pWaveBank->parentEngine != NULL) - { - /* Remove this WaveBank from the Engine list */ - LinkedList_RemoveEntry( - &pWaveBank->parentEngine->wbList, - pWaveBank, - pWaveBank->parentEngine->wbLock, - pWaveBank->parentEngine->pFree - ); - } + /* 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); @@ -2175,11 +2208,13 @@ uint32_t FACTWave_Stop(FACTWave *pWave, uint32_t dwFlags) { FACTNotification note; note.type = FACTNOTIFICATIONTYPE_WAVESTOP; + note.wave.cueIndex = pWave->parentCue->index; + note.wave.pCue = pWave->parentCue; + note.wave.pSoundBank = pWave->parentCue->parentBank; note.wave.pWave = pWave; - if (pWave->parentBank->parentEngine->notifications & NOTIFY_WAVESTOP) - { - note.pvContext = pWave->parentBank->parentEngine->wave_context; - } + note.wave.pWaveBank = pWave->parentBank; + note.pvContext = pWave->parentBank->parentEngine->wave_context; + pWave->parentBank->parentEngine->notificationCallback(¬e); } @@ -2381,7 +2416,6 @@ uint32_t FACTCue_Destroy(FACTCue *pCue) { FACTCue *cue, *prev; FAudioMutex mutex; - FACTNotification note; if (pCue == NULL) { return 1; @@ -2415,20 +2449,7 @@ uint32_t FACTCue_Destroy(FACTCue *pCue) 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); - } + FACT_INTERNAL_SendCueNotification(pCue, NOTIFY_CUEDESTROY, FACTNOTIFICATIONTYPE_CUEDESTROYED); mutex = pCue->parentBank->parentEngine->apiLock; pCue->parentBank->parentEngine->pFree(pCue); @@ -2679,6 +2700,8 @@ uint32_t FACTCue_Stop(FACTCue *pCue, uint32_t dwFlags) } } + FACT_INTERNAL_SendCueNotification(pCue, NOTIFY_CUESTOP, FACTNOTIFICATIONTYPE_CUESTOP); + FAudio_PlatformUnlockMutex(pCue->parentBank->parentEngine->apiLock); return 0; } @@ -2746,6 +2769,8 @@ uint32_t FACTCue_SetMatrixCoefficients( } } + FACT_INTERNAL_SendCueNotification(pCue, NOTIFY_CUESTOP, FACTNOTIFICATIONTYPE_CUESTOP); + FAudio_PlatformUnlockMutex(pCue->parentBank->parentEngine->apiLock); return 0; } diff --git a/WickedEngine/Utility/FAudio/src/FACT3D.c b/WickedEngine/Utility/FAudio/src/FACT3D.c index c9a8f61a4d..434107fca1 100644 --- a/WickedEngine/Utility/FAudio/src/FACT3D.c +++ b/WickedEngine/Utility/FAudio/src/FACT3D.c @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 diff --git a/WickedEngine/Utility/FAudio/src/FACT_internal.c b/WickedEngine/Utility/FAudio/src/FACT_internal.c index 63b191c4ae..a18f37a578 100644 --- a/WickedEngine/Utility/FAudio/src/FACT_internal.c +++ b/WickedEngine/Utility/FAudio/src/FACT_internal.c @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 @@ -39,6 +39,8 @@ #define FACT_CONTENT_VERSION_3_4 45 #define FACT_CONTENT_VERSION_3_1 44 #define FACT_CONTENT_VERSION_3_0 43 +#define FACT_CONTENT_VERSION_2_4 41 +#define FACT_CONTENT_VERSION_2_0 37 static inline int FACT_INTERNAL_SupportedContent(uint16_t version) { @@ -83,7 +85,7 @@ static inline float FACT_INTERNAL_CalculateFilterFrequency( * * -@Woflox */ - float freq = 2 * FAudio_sin( + float freq = 2.0f * FAudio_sinf( F3DAUDIO_PI * FAudio_min(desiredFrequency / sampleRate, 0.5f) ); @@ -463,6 +465,14 @@ void FACT_INTERNAL_GetNextWave( track->frequency, cue->parentBank->parentEngine->audio->master->master.inputSampleRate ); + + /* FIXME: For some reason the 0.67 Q Factor causes problems, but it's also + * the only possible value until ~1 so just clamp it for now. + */ + trackInst->upcomingWave.baseQFactor = FAudio_min( + trackInst->upcomingWave.baseQFactor, + 1.0f + ); } /* Try to change loop counter at the very end */ @@ -505,7 +515,7 @@ uint8_t FACT_INTERNAL_CreateSound(FACTCue *cue, uint16_t fadeInMS) /* Sound */ baseSound = cue->sound; } - else + else if (cue->variation) { /* Variation */ if (cue->variation->flags == 3) @@ -1017,21 +1027,21 @@ float FACT_INTERNAL_CalculateRPC( } else if (rpc->points[i].type == 1) /* Fast */ { - result += maxY * (1.0f - FAudio_pow(1.0f - FAudio_pow(deltaXNormalized, 1.0f / 1.5f), 1.5f)); + result += maxY * (1.0f - FAudio_powf(1.0f - FAudio_powf(deltaXNormalized, 1.0f / 1.5f), 1.5f)); } else if (rpc->points[i].type == 2) /* Slow */ { - result += maxY * (1.0f - FAudio_pow(1.0f - FAudio_pow(deltaXNormalized, 1.5f), 1.0f / 1.5f)); + result += maxY * (1.0f - FAudio_powf(1.0f - FAudio_powf(deltaXNormalized, 1.5f), 1.0f / 1.5f)); } else if (rpc->points[i].type == 3) /* SinCos */ { if (maxY > 0.0f) { - result += maxY * (1.0f - FAudio_pow(1.0f - FAudio_sqrtf(deltaXNormalized), 2.0f)); + result += maxY * (1.0f - FAudio_powf(1.0f - FAudio_sqrtf(deltaXNormalized), 2.0f)); } else { - result += maxY * (1.0f - FAudio_sqrtf(1.0f - FAudio_pow(deltaXNormalized, 2.0f))); + result += maxY * (1.0f - FAudio_sqrtf(1.0f - FAudio_powf(deltaXNormalized, 2.0f))); } } else @@ -1652,7 +1662,7 @@ void FACT_INTERNAL_UpdateCue(FACTCue *cue) FACTSoundInstance *sound; /* Interactive sound selection */ - if (!(cue->data->flags & 0x04) && cue->variation->flags == 3) + if (!(cue->data->flags & 0x04) && cue->variation && cue->variation->flags == 3) { /* Interactive */ if (cue->parentBank->parentEngine->variables[cue->variation->variable].accessibility & 0x04) @@ -2056,8 +2066,8 @@ uint32_t FACT_INTERNAL_ParseAudioEngine( rpcOffset, dspPresetOffset, dspParameterOffset; - uint16_t blob1Count, blob2Count; - uint8_t version, tool; + uint16_t blob1Count, blob2Count, tool; + uint8_t version; uint8_t se; uint32_t magic; size_t memsize; @@ -2608,6 +2618,7 @@ uint32_t FACT_INTERNAL_ParseSoundBank( /* WaveBank Name data */ FAudio_assert((ptr - start) == wavebankNameOffset); + ptr = start + wavebankNameOffset; sb->wavebankNames = (char**) pEngine->pMalloc( sizeof(char*) * sb->wavebankCount @@ -2810,45 +2821,55 @@ uint32_t FACT_INTERNAL_ParseSoundBank( /* Simple Cue data */ FAudio_assert(cueSimpleCount == 0 || (ptr - start) == cueSimpleOffset); - for (i = 0; i < cueSimpleCount; i += 1, cur += 1) + if (cueSimpleCount > 0) { - sb->cues[cur].flags = read_u8(&ptr); - sb->cues[cur].sbCode = read_u32(&ptr, se); - sb->cues[cur].transitionOffset = 0; - sb->cues[cur].instanceLimit = 0xFF; - sb->cues[cur].fadeInMS = 0; - sb->cues[cur].fadeOutMS = 0; - sb->cues[cur].maxInstanceBehavior = 0; - sb->cues[cur].instanceCount = 0; + ptr = start + cueSimpleOffset; + + for (i = 0; i < cueSimpleCount; i += 1, cur += 1) + { + sb->cues[cur].flags = read_u8(&ptr); + sb->cues[cur].sbCode = read_u32(&ptr, se); + sb->cues[cur].transitionOffset = 0; + sb->cues[cur].instanceLimit = 0xFF; + sb->cues[cur].fadeInMS = 0; + sb->cues[cur].fadeOutMS = 0; + sb->cues[cur].maxInstanceBehavior = 0; + sb->cues[cur].instanceCount = 0; + } } /* Complex Cue data */ FAudio_assert(cueComplexCount == 0 || (ptr - start) == cueComplexOffset); - for (i = 0; i < cueComplexCount; i += 1, cur += 1) + if (cueComplexCount > 0) { - sb->cues[cur].flags = read_u8(&ptr); - sb->cues[cur].sbCode = read_u32(&ptr, se); - sb->cues[cur].transitionOffset = read_u32(&ptr, se); - if (sb->cues[cur].transitionOffset == 0xFFFFFFFF) - { - /* FIXME: Why */ - sb->cues[cur].transitionOffset = 0; - } - sb->cues[cur].instanceLimit = read_u8(&ptr); - sb->cues[cur].fadeInMS = read_u16(&ptr, se); - sb->cues[cur].fadeOutMS = read_u16(&ptr, se); - sb->cues[cur].maxInstanceBehavior = read_u8(&ptr) >> 3; - sb->cues[cur].instanceCount = 0; + ptr = start + cueComplexOffset; - if (!(sb->cues[cur].flags & 0x04)) + for (i = 0; i < cueComplexCount; i += 1, cur += 1) { - /* FIXME: Is this the only way to get this...? */ - sb->variationCount += 1; - } - if (sb->cues[cur].transitionOffset > 0) - { - /* FIXME: Is this the only way to get this...? */ - sb->transitionCount += 1; + sb->cues[cur].flags = read_u8(&ptr); + sb->cues[cur].sbCode = read_u32(&ptr, se); + sb->cues[cur].transitionOffset = read_u32(&ptr, se); + if (sb->cues[cur].transitionOffset == 0xFFFFFFFF) + { + /* FIXME: Why */ + sb->cues[cur].transitionOffset = 0; + } + sb->cues[cur].instanceLimit = read_u8(&ptr); + sb->cues[cur].fadeInMS = read_u16(&ptr, se); + sb->cues[cur].fadeOutMS = read_u16(&ptr, se); + sb->cues[cur].maxInstanceBehavior = read_u8(&ptr) >> 3; + sb->cues[cur].instanceCount = 0; + + if (!(sb->cues[cur].flags & 0x04)) + { + /* FIXME: Is this the only way to get this...? */ + sb->variationCount += 1; + } + if (sb->cues[cur].transitionOffset > 0) + { + /* FIXME: Is this the only way to get this...? */ + sb->transitionCount += 1; + } } } @@ -2856,6 +2877,7 @@ uint32_t FACT_INTERNAL_ParseSoundBank( if (sb->variationCount > 0) { FAudio_assert((ptr - start) == variationOffset); + ptr = start + variationOffset; sb->variations = (FACTVariationTable*) pEngine->pMalloc( sizeof(FACTVariationTable) * sb->variationCount @@ -2941,6 +2963,7 @@ uint32_t FACT_INTERNAL_ParseSoundBank( if (sb->transitionCount > 0) { FAudio_assert((ptr - start) == transitionOffset); + ptr = start + transitionOffset; sb->transitions = (FACTTransitionTable*) pEngine->pMalloc( sizeof(FACTTransitionTable) * sb->transitionCount @@ -2988,6 +3011,7 @@ uint32_t FACT_INTERNAL_ParseSoundBank( if (cueNameIndexOffset != -1) { FAudio_assert((ptr - start) == cueNameIndexOffset); + ptr = start + cueNameIndexOffset; ptr += 6 * sb->cueCount; /* FIXME: index as assert value? */ } @@ -2995,6 +3019,7 @@ uint32_t FACT_INTERNAL_ParseSoundBank( if (cueNameOffset != -1) { FAudio_assert((ptr - start) == cueNameOffset); + ptr = start + cueNameOffset; sb->cueNames = (char**) pEngine->pMalloc( sizeof(char*) * sb->cueCount @@ -3082,7 +3107,17 @@ uint32_t FACT_INTERNAL_ParseWaveBank( #define DOSWAP_64(x) x = FAudio_swap64BE(x) fileOffset = offset; - READ(&header, sizeof(header)) + + FAudio_zero(&header, sizeof(header)); + READ(&header.dwSignature, sizeof(header.dwSignature)); + READ(&header.dwVersion, sizeof(header.dwVersion)); + if (header.dwVersion > FACT_CONTENT_VERSION_2_4) + { + READ(&header.dwHeaderVersion, sizeof(header.dwHeaderVersion)); + } + + READ(&header.Segments, sizeof(header.Segments)); + se = header.dwSignature == 0x57424E44; if (se) { @@ -3100,12 +3135,20 @@ uint32_t FACT_INTERNAL_ParseWaveBank( return -1; /* TODO: NOT XACT FILE */ } - if (!FACT_INTERNAL_SupportedContent(header.dwVersion)) + /* We support all Wavebank versions - Restore when SoundBank support them also. */ + /*if (!FACT_INTERNAL_SupportedContent(header.dwVersion)) + { + return -2; + } + */ + if ( header.dwVersion < FACT_CONTENT_VERSION_2_4 || + header.dwVersion > FACT_CONTENT_VERSION ) { return -2; } - if (!FACT_INTERNAL_SupportedWBContent(header.dwHeaderVersion)) + if ( header.dwVersion > FACT_CONTENT_VERSION_2_4 && + !FACT_INTERNAL_SupportedWBContent(header.dwHeaderVersion) ) { return -3; } diff --git a/WickedEngine/Utility/FAudio/src/FACT_internal.h b/WickedEngine/Utility/FAudio/src/FACT_internal.h index 6bf522e64c..c7001a0fce 100644 --- a/WickedEngine/Utility/FAudio/src/FACT_internal.h +++ b/WickedEngine/Utility/FAudio/src/FACT_internal.h @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 @@ -111,7 +111,8 @@ typedef enum FACTNoticationsFlags NOTIFY_WAVESTOP = 0x00002000, NOTIFY_WAVELOOPED = 0x00004000, NOTIFY_WAVEDESTROY = 0x00008000, - NOTIFY_WAVEBANKPREPARED = 0x00010000 + NOTIFY_WAVEBANKPREPARED = 0x00010000, + NOTIFY_WAVEBANKSTREAMING_INVALIDCONTENT = 0x00020000 } FACTNoticationsFlags; /* Internal SoundBank Types */ @@ -440,6 +441,7 @@ struct FACTAudioEngine void *sb_context; void *wb_context; void *wave_context; + LinkedList *wb_notifications_list; /* Settings handle */ void *settings; diff --git a/WickedEngine/Utility/FAudio/src/FAPOBase.c b/WickedEngine/Utility/FAudio/src/FAPOBase.c index 91b294ddab..27a98e1216 100644 --- a/WickedEngine/Utility/FAudio/src/FAPOBase.c +++ b/WickedEngine/Utility/FAudio/src/FAPOBase.c @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 diff --git a/WickedEngine/Utility/FAudio/src/FAPOFX.c b/WickedEngine/Utility/FAudio/src/FAPOFX.c index 70739bd061..5e9c3b82c2 100644 --- a/WickedEngine/Utility/FAudio/src/FAPOFX.c +++ b/WickedEngine/Utility/FAudio/src/FAPOFX.c @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 diff --git a/WickedEngine/Utility/FAudio/src/FAPOFX_echo.c b/WickedEngine/Utility/FAudio/src/FAPOFX_echo.c index d87d5c5b95..ec6454384f 100644 --- a/WickedEngine/Utility/FAudio/src/FAPOFX_echo.c +++ b/WickedEngine/Utility/FAudio/src/FAPOFX_echo.c @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 diff --git a/WickedEngine/Utility/FAudio/src/FAPOFX_eq.c b/WickedEngine/Utility/FAudio/src/FAPOFX_eq.c index 4ac45728aa..1b575e6bdf 100644 --- a/WickedEngine/Utility/FAudio/src/FAPOFX_eq.c +++ b/WickedEngine/Utility/FAudio/src/FAPOFX_eq.c @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 diff --git a/WickedEngine/Utility/FAudio/src/FAPOFX_masteringlimiter.c b/WickedEngine/Utility/FAudio/src/FAPOFX_masteringlimiter.c index 1b02ffe90a..2a8bb92dda 100644 --- a/WickedEngine/Utility/FAudio/src/FAPOFX_masteringlimiter.c +++ b/WickedEngine/Utility/FAudio/src/FAPOFX_masteringlimiter.c @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 diff --git a/WickedEngine/Utility/FAudio/src/FAPOFX_reverb.c b/WickedEngine/Utility/FAudio/src/FAPOFX_reverb.c index 782dddafa2..bd2ea48e24 100644 --- a/WickedEngine/Utility/FAudio/src/FAPOFX_reverb.c +++ b/WickedEngine/Utility/FAudio/src/FAPOFX_reverb.c @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 diff --git a/WickedEngine/Utility/FAudio/src/FAudio.c b/WickedEngine/Utility/FAudio/src/FAudio.c index 81363b983c..8a4ad9a4b0 100644 --- a/WickedEngine/Utility/FAudio/src/FAudio.c +++ b/WickedEngine/Utility/FAudio/src/FAudio.c @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 @@ -278,6 +278,7 @@ uint32_t FAudio_CreateSourceVoice( (*ppSourceVoice)->filter.Type = FAUDIO_DEFAULT_FILTER_TYPE; (*ppSourceVoice)->filter.Frequency = FAUDIO_DEFAULT_FILTER_FREQUENCY; (*ppSourceVoice)->filter.OneOverQ = FAUDIO_DEFAULT_FILTER_ONEOVERQ; + (*ppSourceVoice)->filter.WetDryMix = FAUDIO_DEFAULT_FILTER_WETDRYMIX_EXT; (*ppSourceVoice)->sendLock = FAudio_PlatformCreateMutex(); LOG_MUTEX_CREATE(audio, (*ppSourceVoice)->sendLock) (*ppSourceVoice)->effectLock = FAudio_PlatformCreateMutex(); @@ -293,7 +294,8 @@ uint32_t FAudio_CreateSourceVoice( if ( pSourceFormat->wFormatTag == FAUDIO_FORMAT_PCM || pSourceFormat->wFormatTag == FAUDIO_FORMAT_IEEE_FLOAT || - pSourceFormat->wFormatTag == FAUDIO_FORMAT_WMAUDIO2 ) + pSourceFormat->wFormatTag == FAUDIO_FORMAT_WMAUDIO2 || + pSourceFormat->wFormatTag == FAUDIO_FORMAT_WMAUDIO3 ) { FAudioWaveFormatExtensible *fmtex = (FAudioWaveFormatExtensible*) audio->pMalloc( sizeof(FAudioWaveFormatExtensible) @@ -320,6 +322,10 @@ uint32_t FAudio_CreateSourceVoice( { FAudio_memcpy(&fmtex->SubFormat, &DATAFORMAT_SUBTYPE_WMAUDIO2, sizeof(FAudioGUID)); } + else if (pSourceFormat->wFormatTag == FAUDIO_FORMAT_WMAUDIO3) + { + FAudio_memcpy(&fmtex->SubFormat, &DATAFORMAT_SUBTYPE_WMAUDIO3, sizeof(FAudioGUID)); + } (*ppSourceVoice)->src.format = &fmtex->Format; } else if (pSourceFormat->wFormatTag == FAUDIO_FORMAT_MSADPCM) @@ -586,6 +592,7 @@ uint32_t FAudio_CreateSubmixVoice( (*ppSubmixVoice)->filter.Type = FAUDIO_DEFAULT_FILTER_TYPE; (*ppSubmixVoice)->filter.Frequency = FAUDIO_DEFAULT_FILTER_FREQUENCY; (*ppSubmixVoice)->filter.OneOverQ = FAUDIO_DEFAULT_FILTER_ONEOVERQ; + (*ppSubmixVoice)->filter.WetDryMix = FAUDIO_DEFAULT_FILTER_WETDRYMIX_EXT; (*ppSubmixVoice)->sendLock = FAudio_PlatformCreateMutex(); LOG_MUTEX_CREATE(audio, (*ppSubmixVoice)->sendLock) (*ppSubmixVoice)->effectLock = FAudio_PlatformCreateMutex(); @@ -678,16 +685,27 @@ uint32_t FAudio_CreateMasteringVoice( 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) + if ( InputChannels == FAUDIO_DEFAULT_CHANNELS || + InputSampleRate == FAUDIO_DEFAULT_SAMPLERATE ) { - return FAUDIO_E_INVALID_CALL; + FAudioDeviceDetails details; + if (FAudio_GetDeviceDetails(audio, DeviceIndex, &details) != 0) + { + return FAUDIO_E_INVALID_CALL; + } + if (InputChannels == FAUDIO_DEFAULT_CHANNELS) + { + InputChannels = details.OutputFormat.Format.nChannels; + } + if (InputSampleRate == FAUDIO_DEFAULT_SAMPLERATE) + { + InputSampleRate = details.OutputFormat.Format.nSamplesPerSec; + } } *ppMasteringVoice = (FAudioMasteringVoice*) audio->pMalloc(sizeof(FAudioVoice)); @@ -704,12 +722,8 @@ uint32_t FAudio_CreateMasteringVoice( (*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; + (*ppMasteringVoice)->master.inputChannels = InputChannels; + (*ppMasteringVoice)->master.inputSampleRate = InputSampleRate; /* Sends/Effects */ FAudio_zero(&(*ppMasteringVoice)->sends, sizeof(FAudioVoiceSends)); @@ -1289,8 +1303,8 @@ uint32_t FAudioVoice_SetOutputVoices( /* Allocate the whole send filter array if needed... */ if (voice->sendFilter == NULL) { - voice->sendFilter = (FAudioFilterParameters*) voice->audio->pMalloc( - sizeof(FAudioFilterParameters) * pSendList->SendCount + voice->sendFilter = (FAudioFilterParametersEXT*) voice->audio->pMalloc( + sizeof(FAudioFilterParametersEXT) * pSendList->SendCount ); } if (voice->sendFilterState == NULL) @@ -1308,6 +1322,7 @@ uint32_t FAudioVoice_SetOutputVoices( 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->sendFilter[i].WetDryMix = FAUDIO_DEFAULT_FILTER_WETDRYMIX_EXT; voice->sendFilterState[i] = (FAudioFilterState*) voice->audio->pMalloc( sizeof(FAudioFilterState) * outChannels ); @@ -1633,9 +1648,9 @@ uint32_t FAudioVoice_GetEffectParameters( return 0; } -uint32_t FAudioVoice_SetFilterParameters( +uint32_t FAudioVoice_SetFilterParametersEXT( FAudioVoice *voice, - const FAudioFilterParameters *pParameters, + const FAudioFilterParametersEXT *pParameters, uint32_t OperationSet ) { LOG_API_ENTER(voice->audio) @@ -1671,7 +1686,7 @@ uint32_t FAudioVoice_SetFilterParameters( FAudio_memcpy( &voice->filter, pParameters, - sizeof(FAudioFilterParameters) + sizeof(FAudioFilterParametersEXT) ); FAudio_PlatformUnlockMutex(voice->filterLock); LOG_MUTEX_UNLOCK(voice->audio, voice->filterLock) @@ -1680,9 +1695,23 @@ uint32_t FAudioVoice_SetFilterParameters( return 0; } -void FAudioVoice_GetFilterParameters( +uint32_t FAudioVoice_SetFilterParameters( + FAudioVoice* voice, + const FAudioFilterParameters* pParameters, + uint32_t OperationSet +) { + FAudioFilterParametersEXT ext_parameters; + ext_parameters.Type = pParameters->Type; + ext_parameters.OneOverQ = pParameters->OneOverQ; + ext_parameters.Frequency = pParameters->Frequency; + ext_parameters.WetDryMix = FAUDIO_DEFAULT_FILTER_WETDRYMIX_EXT; + + return FAudioVoice_SetFilterParametersEXT(voice, &ext_parameters, OperationSet); +} + +void FAudioVoice_GetFilterParametersEXT( FAudioVoice *voice, - FAudioFilterParameters *pParameters + FAudioFilterParametersEXT *pParameters ) { LOG_API_ENTER(voice->audio) @@ -1706,17 +1735,34 @@ void FAudioVoice_GetFilterParameters( FAudio_memcpy( pParameters, &voice->filter, - sizeof(FAudioFilterParameters) + sizeof(FAudioFilterParametersEXT) ); FAudio_PlatformUnlockMutex(voice->filterLock); LOG_MUTEX_UNLOCK(voice->audio, voice->filterLock) LOG_API_EXIT(voice->audio) } -uint32_t FAudioVoice_SetOutputFilterParameters( +void FAudioVoice_GetFilterParameters( + FAudioVoice* voice, + FAudioFilterParameters* pParameters +) { + FAudioFilterParametersEXT ext_parameters; + ext_parameters.Type = pParameters->Type; + ext_parameters.OneOverQ = pParameters->OneOverQ; + ext_parameters.Frequency = pParameters->Frequency; + ext_parameters.WetDryMix = FAUDIO_DEFAULT_FILTER_WETDRYMIX_EXT; + + FAudioVoice_GetFilterParametersEXT(voice, &ext_parameters); + + pParameters->Type = ext_parameters.Type; + pParameters->Frequency = ext_parameters.Frequency; + pParameters->OneOverQ = ext_parameters.OneOverQ; +} + +uint32_t FAudioVoice_SetOutputFilterParametersEXT( FAudioVoice *voice, FAudioVoice *pDestinationVoice, - const FAudioFilterParameters *pParameters, + const FAudioFilterParametersEXT *pParameters, uint32_t OperationSet ) { uint32_t i; @@ -1784,7 +1830,7 @@ uint32_t FAudioVoice_SetOutputFilterParameters( FAudio_memcpy( &voice->sendFilter[i], pParameters, - sizeof(FAudioFilterParameters) + sizeof(FAudioFilterParametersEXT) ); FAudio_PlatformUnlockMutex(voice->sendLock); @@ -1793,10 +1839,25 @@ uint32_t FAudioVoice_SetOutputFilterParameters( return 0; } -void FAudioVoice_GetOutputFilterParameters( +uint32_t FAudioVoice_SetOutputFilterParameters( + FAudioVoice* voice, + FAudioVoice* pDestinationVoice, + const FAudioFilterParameters* pParameters, + uint32_t OperationSet +) { + FAudioFilterParametersEXT ext_parameters; + ext_parameters.Type = pParameters->Type; + ext_parameters.OneOverQ = pParameters->OneOverQ; + ext_parameters.Frequency = pParameters->Frequency; + ext_parameters.WetDryMix = FAUDIO_DEFAULT_FILTER_WETDRYMIX_EXT; + + return FAudioVoice_SetOutputFilterParametersEXT(voice, pDestinationVoice, &ext_parameters, OperationSet); +} + +void FAudioVoice_GetOutputFilterParametersEXT( FAudioVoice *voice, FAudioVoice *pDestinationVoice, - FAudioFilterParameters *pParameters + FAudioFilterParametersEXT *pParameters ) { uint32_t i; @@ -1852,7 +1913,7 @@ void FAudioVoice_GetOutputFilterParameters( FAudio_memcpy( pParameters, &voice->sendFilter[i], - sizeof(FAudioFilterParameters) + sizeof(FAudioFilterParametersEXT) ); FAudio_PlatformUnlockMutex(voice->sendLock); @@ -1907,6 +1968,24 @@ uint32_t FAudioVoice_SetVolume( return 0; } +void FAudioVoice_GetOutputFilterParameters( + FAudioVoice* voice, + FAudioVoice* pDestinationVoice, + FAudioFilterParameters* pParameters +) { + FAudioFilterParametersEXT ext_parameters; + ext_parameters.Type = pParameters->Type; + ext_parameters.OneOverQ = pParameters->OneOverQ; + ext_parameters.Frequency = pParameters->Frequency; + ext_parameters.WetDryMix = FAUDIO_DEFAULT_FILTER_WETDRYMIX_EXT; + + FAudioVoice_GetOutputFilterParametersEXT(voice, pDestinationVoice, &ext_parameters); + + pParameters->Type = ext_parameters.Type; + pParameters->Frequency = ext_parameters.Frequency; + pParameters->OneOverQ = ext_parameters.OneOverQ; +} + void FAudioVoice_GetVolume( FAudioVoice *voice, float *pVolume @@ -2186,11 +2265,71 @@ void FAudioVoice_GetOutputMatrix( LOG_API_EXIT(voice->audio) } -void FAudioVoice_DestroyVoice(FAudioVoice *voice) +static uint32_t check_for_sends_to_voice(FAudioVoice *voice) { + FAudio *audio = voice->audio; + uint32_t ret = 0; + FAudioSourceVoice *source; + FAudioSubmixVoice *submix; + LinkedList *list; uint32_t i; + + FAudio_PlatformLockMutex(audio->sourceLock); + list = audio->sources; + while (list != NULL) + { + source = (FAudioSourceVoice*) list->entry; + for (i = 0; i < source->sends.SendCount; i += 1) + if (source->sends.pSends[i].pOutputVoice == voice) + { + ret = 0x80004005; /* E_FAIL */ + break; + } + if (ret) + break; + list = list->next; + } + FAudio_PlatformUnlockMutex(audio->sourceLock); + + if (ret) + return ret; + + FAudio_PlatformLockMutex(audio->submixLock); + list = audio->submixes; + while (list != NULL) + { + submix = (FAudioSubmixVoice*) list->entry; + for (i = 0; i < submix->sends.SendCount; i += 1) + if (submix->sends.pSends[i].pOutputVoice == voice) + { + ret = 0x80004005; /* E_FAIL */ + break; + } + if (ret) + break; + list = list->next; + } + FAudio_PlatformUnlockMutex(audio->submixLock); + + return ret; +} + +uint32_t FAudioVoice_DestroyVoiceSafeEXT(FAudioVoice *voice) +{ + uint32_t i, ret; LOG_API_ENTER(voice->audio) + if ((ret = check_for_sends_to_voice(voice))) + { + LOG_ERROR( + voice->audio, + "Voice %p is an output for other voice(s)", + voice + ) + LOG_API_EXIT(voice->audio) + return ret; + } + /* TODO: Check for dependencies and remove from audio graph first! */ FAudio_OPERATIONSET_ClearAllForVoice(voice); @@ -2364,6 +2503,12 @@ void FAudioVoice_DestroyVoice(FAudioVoice *voice) LOG_API_EXIT(voice->audio) FAudio_Release(voice->audio); voice->audio->pFree(voice); + return 0; +} + +void FAudioVoice_DestroyVoice(FAudioVoice *voice) +{ + FAudioVoice_DestroyVoiceSafeEXT(voice); } /* FAudioSourceVoice Interface */ @@ -2453,7 +2598,9 @@ uint32_t FAudioSourceVoice_SubmitSourceBuffer( FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE); #ifdef HAVE_WMADEC - FAudio_assert( (voice->src.wmadec != NULL && (pBufferWMA != NULL || voice->src.format->wFormatTag == FAUDIO_FORMAT_XMAUDIO2)) || + FAudio_assert( (voice->src.wmadec != NULL && (pBufferWMA != NULL || + (voice->src.format->wFormatTag == FAUDIO_FORMAT_XMAUDIO2 || + voice->src.format->wFormatTag == FAUDIO_FORMAT_EXTENSIBLE))) || (voice->src.wmadec == NULL && (pBufferWMA == NULL && voice->src.format->wFormatTag != FAUDIO_FORMAT_XMAUDIO2)) ); #endif /* HAVE_WMADEC */ diff --git a/WickedEngine/Utility/FAudio/src/FAudioFX_reverb.c b/WickedEngine/Utility/FAudio/src/FAudioFX_reverb.c index 4d43fa4e76..219495e791 100644 --- a/WickedEngine/Utility/FAudio/src/FAudioFX_reverb.c +++ b/WickedEngine/Utility/FAudio/src/FAudioFX_reverb.c @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 @@ -130,11 +130,13 @@ static inline float DspDelay_Process(DspDelay *filter, float sample_in) return delay_out; } +/* FIXME: This is currently unused! What was it for...? -flibit static inline float DspDelay_Tap(DspDelay *filter, uint32_t delay) { FAudio_assert(delay <= filter->delay); return filter->buffer[(filter->write_idx - delay + filter->capacity) % filter->capacity]; } +*/ static inline void DspDelay_Reset(DspDelay *filter) { @@ -181,7 +183,7 @@ static inline void DspBiQuad_Change( float q, float gain ) { - const float TWOPI = 6.283185307179586476925286766559005; + const float TWOPI = (float)6.283185307179586476925286766559005; float theta_c = (TWOPI * frequency) / (float) filter->sampleRate; float mu = DbGainToFactor(gain); float beta = (type == DSP_BIQUAD_LOWSHELVING) ? @@ -507,7 +509,8 @@ static FAudio_ChannelPositionFlags FAudio_GetChannelPositionFlags(int32_t total_ case 3: return Position_Right | Position_Rear; } - + FAudio_assert(0 && "Unsupported channel count"); + break; case 5: switch (channel) { @@ -522,7 +525,8 @@ static FAudio_ChannelPositionFlags FAudio_GetChannelPositionFlags(int32_t total_ case 4: return Position_Right | Position_Rear; } - + FAudio_assert(0 && "Unsupported channel count"); + break; default: FAudio_assert(0 && "Unsupported channel count"); break; @@ -1013,35 +1017,35 @@ static inline float DspReverb_INTERNAL_Process_2_to_2( size_t sample_count ) { const float *in_end = samples_in + sample_count; - float in, in_ratio, early, late[2]; + float in, early, late[2]; float squared_sum = 0; while (samples_in < in_end) { /* Input - Combine 2 channels into 1 */ in = (samples_in[0] + samples_in[1]) / 2.0f; - in_ratio = in * reverb->dry_ratio; - samples_in += 2; /* Early Reflections */ early = DspReverb_INTERNAL_ProcessEarly(reverb, in); /* Reverberation with Wet/Dry Mix */ - late[0] = DspReverb_INTERNAL_ProcessChannel( + late[0] = (DspReverb_INTERNAL_ProcessChannel( reverb, &reverb->channel[0], early - ); + ) * reverb->wet_ratio) + samples_in[0] * reverb->dry_ratio; late[1] = (DspReverb_INTERNAL_ProcessChannel( reverb, &reverb->channel[1], early - ) * reverb->wet_ratio) + in_ratio; + ) * reverb->wet_ratio) + samples_in[1] * reverb->dry_ratio; squared_sum += (late[0] * late[0]) + (late[1] * late[1]); /* Output */ *samples_out++ = late[0]; *samples_out++ = late[1]; + + samples_in += 2; } return squared_sum; @@ -1405,6 +1409,22 @@ uint32_t FAudioFXReverb_LockForProcess( fapo->base.pMalloc ); + /* Initialize the effect to a default setting */ + if (fapo->apiVersion == 9) + { + DspReverb_SetParameters9( + &fapo->reverb, + (FAudioFXReverbParameters9*) fapo->base.m_pParameterBlocks + ); + } + else + { + DspReverb_SetParameters( + &fapo->reverb, + (FAudioFXReverbParameters*) fapo->base.m_pParameterBlocks + ); + } + /* Call parent to do basic validation */ return FAPOBase_LockForProcess( &fapo->base, @@ -1485,6 +1505,24 @@ void FAudioFXReverb_Process( FAudioFXReverbParameters *params; uint8_t update_params = FAPOBase_ParametersChanged(&fapo->base); float total; + + params = (FAudioFXReverbParameters*) FAPOBase_BeginProcess(&fapo->base); + + /* Update parameters before doing anything else */ + if (update_params) + { + if (fapo->apiVersion == 9) + { + DspReverb_SetParameters9( + &fapo->reverb, + (FAudioFXReverbParameters9*) params + ); + } + else + { + DspReverb_SetParameters(&fapo->reverb, params); + } + } /* Handle disabled filter */ if (IsEnabled == 0) @@ -1501,6 +1539,7 @@ void FAudioFXReverb_Process( ); } + FAPOBase_EndProcess(&fapo->base); return; } @@ -1514,24 +1553,6 @@ void FAudioFXReverb_Process( ); } - params = (FAudioFXReverbParameters*) FAPOBase_BeginProcess(&fapo->base); - - /* Update parameters */ - if (update_params) - { - if (fapo->apiVersion == 9) - { - DspReverb_SetParameters9( - &fapo->reverb, - (FAudioFXReverbParameters9*) params - ); - } - else - { - DspReverb_SetParameters(&fapo->reverb, params); - } - } - /* Run reverb effect */ #define PROCESS(pin, pout) \ DspReverb_INTERNAL_Process_##pin##_to_##pout( \ @@ -1664,16 +1685,6 @@ uint32_t FAudioCreateReverbWithCustomAllocatorEXT( sizeof(FAudioFXReverbParameters) * 3 ); result->apiVersion = 7; - #define INITPARAMS(offset) \ - FAudio_memcpy( \ - params + sizeof(FAudioFXReverbParameters) * offset, \ - &fxdefault, \ - sizeof(FAudioFXReverbParameters) \ - ); - INITPARAMS(0) - INITPARAMS(1) - INITPARAMS(2) - #undef INITPARAMS /* Initialize... */ FAudio_memcpy( @@ -1709,6 +1720,13 @@ uint32_t FAudioCreateReverbWithCustomAllocatorEXT( result->base.Destructor = FAudioFXReverb_Free; #undef ASSIGN_VT + /* Prepare the default parameters */ + result->base.base.Initialize( + result, + &fxdefault, + sizeof(FAudioFXReverbParameters) + ); + /* Finally. */ *ppApo = &result->base.base; return 0; @@ -1837,16 +1855,6 @@ uint32_t FAudioCreateReverb9WithCustomAllocatorEXT( sizeof(FAudioFXReverbParameters9) * 3 ); result->apiVersion = 9; - #define INITPARAMS(offset) \ - FAudio_memcpy( \ - params + sizeof(FAudioFXReverbParameters9) * offset, \ - &fxdefault, \ - sizeof(FAudioFXReverbParameters9) \ - ); - INITPARAMS(0) - INITPARAMS(1) - INITPARAMS(2) - #undef INITPARAMS /* Initialize... */ FAudio_memcpy( @@ -1882,6 +1890,13 @@ uint32_t FAudioCreateReverb9WithCustomAllocatorEXT( result->base.Destructor = FAudioFXReverb_Free; #undef ASSIGN_VT + /* Prepare the default parameters */ + result->base.base.Initialize( + result, + &fxdefault, + sizeof(FAudioFXReverbParameters9) + ); + /* Finally. */ *ppApo = &result->base.base; return 0; diff --git a/WickedEngine/Utility/FAudio/src/FAudioFX_volumemeter.c b/WickedEngine/Utility/FAudio/src/FAudioFX_volumemeter.c index ded77b9908..07c1c132e4 100644 --- a/WickedEngine/Utility/FAudio/src/FAudioFX_volumemeter.c +++ b/WickedEngine/Utility/FAudio/src/FAudioFX_volumemeter.c @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 diff --git a/WickedEngine/Utility/FAudio/src/FAudio_gstreamer.c b/WickedEngine/Utility/FAudio/src/FAudio_gstreamer.c deleted file mode 100644 index 28bd478a17..0000000000 --- a/WickedEngine/Utility/FAudio/src/FAudio_gstreamer.c +++ /dev/null @@ -1,892 +0,0 @@ -/* FAudio - XAudio Reimplementation for FNA - * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team - * Copyright (c) 2019-2021 Andrew Eikum for CodeWeavers, Inc - * - * 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 - * - */ - -#ifdef HAVE_GSTREAMER - -#include "FAudio_internal.h" - -#include -#include -#include - -typedef struct FAudioWMADEC -{ - GstPad *srcpad; - GstElement *pipeline; - GstElement *dst; - GstElement *resampler; - GstSegment segment; - uint8_t *convertCache, *prevConvertCache; - size_t convertCacheLen, prevConvertCacheLen; - uint32_t curBlock, prevBlock; - size_t *blockSizes; - uint32_t blockAlign; - uint32_t blockCount; - size_t maxBytes; -} FAudioWMADEC; - -#define SIZE_FROM_DST(sample) \ - ((sample) * voice->src.format->nChannels * sizeof(float)) -#define SIZE_FROM_SRC(sample) \ - ((sample) * voice->src.format->nChannels * (voice->src.format->wBitsPerSample / 8)) -#define SAMPLES_FROM_SRC(len) \ - ((len) / voice->src.format->nChannels / (voice->src.format->wBitsPerSample / 8)) -#define SAMPLES_FROM_DST(len) \ - ((len) / voice->src.format->nChannels / sizeof(float)) -#define SIZE_SRC_TO_DST(len) \ - ((len) * (sizeof(float) / (voice->src.format->wBitsPerSample / 8))) -#define SIZE_DST_TO_SRC(len) \ - ((len) / (sizeof(float) / (voice->src.format->wBitsPerSample / 8))) - -static int FAudio_GSTREAMER_RestartDecoder(FAudioWMADEC *wmadec) -{ - GstEvent *event; - - event = gst_event_new_flush_start(); - gst_pad_push_event(wmadec->srcpad, event); - - event = gst_event_new_flush_stop(TRUE); - gst_pad_push_event(wmadec->srcpad, event); - - event = gst_event_new_segment(&wmadec->segment); - gst_pad_push_event(wmadec->srcpad, event); - - wmadec->curBlock = ~0u; - wmadec->prevBlock = ~0u; - - if (gst_element_set_state(wmadec->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) - { - return 0; - } - - return 1; -} - -static int FAudio_GSTREAMER_CheckForBusErrors(FAudioVoice *voice) -{ - GstBus *bus; - GstMessage *msg; - GError *err = NULL; - gchar *debug_info = NULL; - int ret = 0; - - bus = gst_pipeline_get_bus(GST_PIPELINE(voice->src.wmadec->pipeline)); - - while((msg = gst_bus_pop_filtered(bus, GST_MESSAGE_ERROR))) - { - switch(GST_MESSAGE_TYPE(msg)) - { - case GST_MESSAGE_ERROR: - gst_message_parse_error(msg, &err, &debug_info); - LOG_ERROR( - voice->audio, - "Got gstreamer bus error from %s: %s (%s)", - GST_OBJECT_NAME(msg->src), - err->message, - debug_info ? debug_info : "none" - ) - g_clear_error(&err); - g_free(debug_info); - gst_message_unref(msg); - ret = 1; - break; - default: - gst_message_unref(msg); - break; - } - } - - gst_object_unref(bus); - - return ret; -} - -static size_t FAudio_GSTREAMER_FillConvertCache( - FAudioVoice *voice, - FAudioBuffer *buffer, - size_t availableBytes -) { - GstBuffer *src, *dst; - GstSample *sample; - GstMapInfo info; - size_t pulled, clipStartBytes, clipEndBytes, toCopyBytes; - GstAudioClippingMeta *cmeta; - GstFlowReturn push_ret; - - LOG_FUNC_ENTER(voice->audio) - - /* Write current block to input buffer, push to pipeline */ - - clipStartBytes = ( - (size_t) voice->src.wmadec->blockAlign * - voice->src.wmadec->curBlock - ); - toCopyBytes = voice->src.wmadec->blockAlign; - clipEndBytes = clipStartBytes + toCopyBytes; - if (buffer->AudioBytes < clipEndBytes) - { - /* XMA2 assets can ship with undersized ends. - * If your first instinct is to "pad with 0xFF, that's what game data trails with," - * sorry to disappoint but it inserts extra silence in loops. - * Instead, push an undersized buffer and pray that the decoder handles it properly. - * At the time of writing, it's FFmpeg, and it's handling it well. - * For everything else, uh, let's assume the same. If you're reading this: sorry. - * -ade - */ - toCopyBytes = buffer->AudioBytes - clipStartBytes; - } - clipStartBytes += (size_t) buffer->pAudioData; - - src = gst_buffer_new_allocate( - NULL, - toCopyBytes, - NULL - ); - - if ( gst_buffer_fill( - src, - 0, - (gconstpointer*) clipStartBytes, - toCopyBytes - ) != toCopyBytes ) - { - LOG_ERROR( - voice->audio, - "for voice %p, failed to copy whole chunk into buffer", - voice - ); - gst_buffer_unref(src); - return (size_t) -1; - } - - push_ret = gst_pad_push(voice->src.wmadec->srcpad, src); - if( push_ret != GST_FLOW_OK && - push_ret != GST_FLOW_EOS ) - { - LOG_ERROR( - voice->audio, - "for voice %p, pushing buffer failed: 0x%x", - voice, - push_ret - ); - return (size_t) -1; - } - - pulled = 0; - while (1) - { - /* Pull frames one into cache */ - sample = gst_app_sink_try_pull_sample( - GST_APP_SINK(voice->src.wmadec->dst), - 0 - ); - - if (!sample) - { - /* done decoding */ - break; - } - dst = gst_sample_get_buffer(sample); - gst_buffer_map(dst, &info, GST_MAP_READ); - - cmeta = gst_buffer_get_audio_clipping_meta(dst); - if (cmeta) - { - FAudio_assert(cmeta->format == GST_FORMAT_DEFAULT /* == samples */); - clipStartBytes = SIZE_FROM_DST(cmeta->start); - clipEndBytes = SIZE_FROM_DST(cmeta->end); - } - else - { - clipStartBytes = 0; - clipEndBytes = 0; - } - - toCopyBytes = FAudio_min(info.size - (clipStartBytes + clipEndBytes), availableBytes - pulled); - - if (voice->src.wmadec->convertCacheLen < pulled + toCopyBytes) - { - voice->src.wmadec->convertCacheLen = pulled + toCopyBytes; - voice->src.wmadec->convertCache = (uint8_t*) voice->audio->pRealloc( - voice->src.wmadec->convertCache, - pulled + toCopyBytes - ); - } - - - FAudio_memcpy(voice->src.wmadec->convertCache + pulled, - info.data + clipStartBytes, - toCopyBytes - ); - - gst_buffer_unmap(dst, &info); - gst_sample_unref(sample); - - pulled += toCopyBytes; - } - - LOG_FUNC_EXIT(voice->audio) - - return pulled; -} - -static int FAudio_WMADEC_DecodeBlock(FAudioVoice *voice, FAudioBuffer *buffer, uint32_t block, size_t availableBytes) -{ - FAudioWMADEC *wmadec = voice->src.wmadec; - uint8_t *tmpBuf; - size_t tmpLen; - - if (wmadec->curBlock != ~0u && block != wmadec->curBlock + 1) - { - /* XAudio2 allows looping back to start of XMA buffers, but nothing else */ - if (block != 0) - { - LOG_ERROR( - voice->audio, - "for voice %p, out of sequence block: %u (cur: %d)\n", - voice, - block, - wmadec->curBlock - ); - } - FAudio_assert(block == 0); - if (!FAudio_GSTREAMER_RestartDecoder(wmadec)) - { - LOG_WARNING( - voice->audio, - "%s", - "Restarting decoder failed!" - ) - } - } - - /* store previous block to allow for minor rewinds (FAudio quirk) */ - tmpBuf = wmadec->prevConvertCache; - tmpLen = wmadec->prevConvertCacheLen; - wmadec->prevConvertCache = wmadec->convertCache; - wmadec->prevConvertCacheLen = wmadec->convertCacheLen; - wmadec->convertCache = tmpBuf; - wmadec->convertCacheLen = tmpLen; - - wmadec->prevBlock = wmadec->curBlock; - wmadec->curBlock = block; - - wmadec->blockSizes[block] = FAudio_GSTREAMER_FillConvertCache( - voice, - buffer, - availableBytes - ); - - return wmadec->blockSizes[block] != (size_t) -1; -} - -static void FAudio_INTERNAL_DecodeGSTREAMER( - FAudioVoice *voice, - FAudioBuffer *buffer, - float *decodeCache, - uint32_t samples -) { - size_t byteOffset, siz, availableBytes, blockCount, maxBytes; - uint32_t curBlock, curBufferOffset; - uint8_t *convertCache; - int error = 0; - FAudioWMADEC *wmadec = voice->src.wmadec; - - if (wmadec->blockCount != 0) - { - blockCount = wmadec->blockCount; - maxBytes = wmadec->maxBytes; - } - else - { - blockCount = voice->src.bufferList->bufferWMA.PacketCount; - maxBytes = voice->src.bufferList->bufferWMA.pDecodedPacketCumulativeBytes[blockCount - 1]; - } - - LOG_FUNC_ENTER(voice->audio) - - if (!wmadec->blockSizes) - { - size_t sz = blockCount * sizeof(*wmadec->blockSizes); - wmadec->blockSizes = (size_t *) voice->audio->pMalloc(sz); - memset(wmadec->blockSizes, 0xff, sz); - } - - curBufferOffset = voice->src.curBufferOffset; -decode: - byteOffset = SIZE_FROM_DST(curBufferOffset); - - /* the last block size can truncate the length of the buffer */ - availableBytes = SIZE_SRC_TO_DST(maxBytes); - for (curBlock = 0; curBlock < blockCount; curBlock += 1) - { - /* decode to get real size */ - if (wmadec->blockSizes[curBlock] == (size_t)-1) - { - if (!FAudio_WMADEC_DecodeBlock(voice, buffer, curBlock, availableBytes)) - { - error = 1; - goto done; - } - } - - if (wmadec->blockSizes[curBlock] > byteOffset) - { - /* ensure curBlock is decoded in either slot */ - if (wmadec->curBlock != curBlock && wmadec->prevBlock != curBlock) - { - if (!FAudio_WMADEC_DecodeBlock(voice, buffer, curBlock, availableBytes)) - { - error = 1; - goto done; - } - } - break; - } - - byteOffset -= wmadec->blockSizes[curBlock]; - availableBytes -= wmadec->blockSizes[curBlock]; - if (availableBytes == 0) - break; - } - - if (curBlock >= blockCount || availableBytes == 0) - { - goto done; - } - - /* If we're in a different block from last time, decode! */ - if (curBlock == wmadec->curBlock) - { - convertCache = wmadec->convertCache; - } - else if (curBlock == wmadec->prevBlock) - { - convertCache = wmadec->prevConvertCache; - } - else - { - convertCache = NULL; - FAudio_assert(0 && "Somehow got an undecoded curBlock!"); - } - - /* Copy to decodeCache, finally. */ - siz = FAudio_min(SIZE_FROM_DST(samples), wmadec->blockSizes[curBlock] - byteOffset); - if (convertCache) - { - FAudio_memcpy( - decodeCache, - convertCache + byteOffset, - siz - ); - } - else - { - FAudio_memset( - decodeCache, - 0, - siz - ); - } - - /* Increment pointer, decrement remaining sample count */ - decodeCache += siz / sizeof(float); - samples -= SAMPLES_FROM_DST(siz); - curBufferOffset += SAMPLES_FROM_DST(siz); - -done: - if (FAudio_GSTREAMER_CheckForBusErrors(voice)) - { - LOG_ERROR( - voice->audio, - "%s", - "Got a bus error after decoding!" - ) - - error = 1; - } - - /* If the cache isn't filled yet, keep decoding! */ - if (samples > 0) - { - if (!error && curBlock < blockCount - 1) - { - goto decode; - } - - /* out of stuff to decode, write blank and exit */ - FAudio_memset(decodeCache, 0, SIZE_FROM_DST(samples)); - } - - LOG_FUNC_EXIT(voice->audio) -} - -void FAudio_WMADEC_end_buffer(FAudioSourceVoice *pSourceVoice) -{ - FAudioWMADEC *wmadec = pSourceVoice->src.wmadec; - - LOG_FUNC_ENTER(pSourceVoice->audio) - - pSourceVoice->audio->pFree(wmadec->blockSizes); - wmadec->blockSizes = NULL; - - wmadec->curBlock = ~0u; - wmadec->prevBlock = ~0u; - - LOG_FUNC_EXIT(pSourceVoice->audio) -} - -static void FAudio_WMADEC_NewDecodebinPad(GstElement *decodebin, - GstPad *pad, gpointer user) -{ - FAudioWMADEC *wmadec = user; - GstPad *ac_sink; - - ac_sink = gst_element_get_static_pad(wmadec->resampler, "sink"); - if (GST_PAD_IS_LINKED(ac_sink)) - { - gst_object_unref(ac_sink); - return; - } - - gst_pad_link(pad, ac_sink); - - gst_object_unref(ac_sink); -} - -/* XMA2 doesn't have a recognized mimetype and so far only libav's avdec_xma2 exists for XMA2. - * Things can change and as of writing this patch, things are actively changing. - * As for this being a possible leak: using gstreamer is a static endeavour elsewhere already~ - * -ade - */ -/* #define FAUDIO_GST_LIBAV_EXPOSES_XMA2_CAPS_IN_CURRENT_YEAR */ -#ifndef FAUDIO_GST_LIBAV_EXPOSES_XMA2_CAPS_IN_CURRENT_YEAR -static const char *FAudio_WMADEC_XMA2_Mimetype = NULL; -static GstElementFactory *FAudio_WMADEC_XMA2_DecoderFactory = NULL; -static GValueArray *FAudio_WMADEC_XMA2_DecodebinAutoplugFactories( - GstElement *decodebin, - GstPad *pad, - GstCaps *caps, - gpointer user -) { - FAudio *audio = user; - GValueArray *result; - gchar *capsText; - - if (!gst_structure_has_name(gst_caps_get_structure(caps, 0), FAudio_WMADEC_XMA2_Mimetype)) - { - capsText = gst_caps_to_string(caps); - LOG_ERROR( - audio, - "Expected %s (XMA2) caps not %s", - FAudio_WMADEC_XMA2_Mimetype, - capsText - ) - g_free(capsText); - /* - * From the docs: - * - * If this function returns NULL, @pad will be exposed as a final caps. - * - * If this function returns an empty array, the pad will be considered as - * having an unhandled type media type. - */ - return g_value_array_new(0); - } - - result = g_value_array_new(1); - GValue val = G_VALUE_INIT; - g_value_init(&val, G_TYPE_OBJECT); - g_value_set_object(&val, FAudio_WMADEC_XMA2_DecoderFactory); - g_value_array_append(result, &val); - g_value_unset(&val); - return result; -} -#endif - - -uint32_t FAudio_WMADEC_init(FAudioSourceVoice *pSourceVoice, uint32_t type) -{ - FAudioWMADEC *result; - GstElement *decoder = NULL, *converter = NULL; - const GList *tmpls; - GstStaticPadTemplate *tmpl; - GstCaps *caps; - const char *mimetype; - const char *versiontype; - int version; - GstBuffer *codec_data; - size_t codec_data_size; - uint8_t *extradata; - uint8_t fakeextradata[16]; - GstPad *decoder_sink; - GstEvent *event; - - LOG_FUNC_ENTER(pSourceVoice->audio) - - /* Init GStreamer statically. The docs tell us not to exit, so I guess - * we're supposed to just leak! - */ - if (!gst_is_initialized()) - { - /* Apparently they ask for this to leak... */ - gst_init(NULL, NULL); - } - -#ifndef FAUDIO_GST_LIBAV_EXPOSES_XMA2_CAPS_IN_CURRENT_YEAR - if (type == FAUDIO_FORMAT_XMAUDIO2 && !FAudio_WMADEC_XMA2_Mimetype) - { - /* Old versions ship with unknown/unknown as the sink caps mimetype. - * A patch has been submitted (and merged!) to expose avdec_xma2 as audio/x-xma with xmaversion 2: - * https://gitlab.freedesktop.org/gstreamer/gst-libav/-/merge_requests/124 - * For now, try to find avdec_xma2 if it's found, match the mimetype on the fly. - * This should also take future alternative XMA2 decoders into account if avdec_xma2 is missing. - * -ade - */ - FAudio_WMADEC_XMA2_Mimetype = "audio/x-xma"; - FAudio_WMADEC_XMA2_DecoderFactory = gst_element_factory_find("avdec_xma2"); - if (FAudio_WMADEC_XMA2_DecoderFactory) - { - for (tmpls = gst_element_factory_get_static_pad_templates(FAudio_WMADEC_XMA2_DecoderFactory); tmpls; tmpls = tmpls->next) - { - tmpl = (GstStaticPadTemplate*) tmpls->data; - if (tmpl->direction == GST_PAD_SINK) - { - caps = gst_static_pad_template_get_caps(tmpl); - FAudio_WMADEC_XMA2_Mimetype = gst_structure_get_name(gst_caps_get_structure(caps, 0)); - gst_caps_unref(caps); - break; - } - } - } - } -#endif - - /* Match the format with the codec */ - switch (type) - { - #define GSTTYPE(fmt, mt, vt, ver) \ - case FAUDIO_FORMAT_##fmt: mimetype = mt; versiontype = vt; version = ver; break; - GSTTYPE(WMAUDIO2, "audio/x-wma", "wmaversion", 2) - GSTTYPE(WMAUDIO3, "audio/x-wma", "wmaversion", 3) - GSTTYPE(WMAUDIO_LOSSLESS, "audio/x-wma", "wmaversion", 4) -#ifndef FAUDIO_GST_LIBAV_EXPOSES_XMA2_CAPS_IN_CURRENT_YEAR - GSTTYPE(XMAUDIO2, FAudio_WMADEC_XMA2_Mimetype, "xmaversion", 2) -#else - GSTTYPE(XMAUDIO2, "audio/x-xma", "xmaversion", 2) -#endif - #undef GSTTYPE - default: - LOG_ERROR( - pSourceVoice->audio, - "%X codec not supported!", - type - ) - LOG_FUNC_EXIT(pSourceVoice->audio) - return FAUDIO_E_UNSUPPORTED_FORMAT; - } - - /* Set up the GStreamer pipeline. - * Note that we're not assigning names, since many pipelines will exist - * at the same time (one per source voice). - */ - result = (FAudioWMADEC*) pSourceVoice->audio->pMalloc(sizeof(FAudioWMADEC)); - FAudio_zero(result, sizeof(FAudioWMADEC)); - - result->pipeline = gst_pipeline_new(NULL); - - decoder = gst_element_factory_make("decodebin", NULL); - if (!decoder) - { - LOG_ERROR( - pSourceVoice->audio, - "Unable to create gstreamer decodebin; is %zu-bit gst-plugins-base installed?", - sizeof(void *) * 8 - ) - goto free_without_bin; - } - -#ifndef FAUDIO_GST_LIBAV_EXPOSES_XMA2_CAPS_IN_CURRENT_YEAR - if (type == FAUDIO_FORMAT_XMAUDIO2 && FAudio_WMADEC_XMA2_DecoderFactory) - { - g_signal_connect(decoder, "autoplug-factories", G_CALLBACK(FAudio_WMADEC_XMA2_DecodebinAutoplugFactories), pSourceVoice->audio); - } -#endif - g_signal_connect(decoder, "pad-added", G_CALLBACK(FAudio_WMADEC_NewDecodebinPad), result); - - result->srcpad = gst_pad_new(NULL, GST_PAD_SRC); - - result->resampler = gst_element_factory_make("audioresample", NULL); - if (!result->resampler) - { - LOG_ERROR( - pSourceVoice->audio, - "Unable to create gstreamer audioresample; is %zu-bit gst-plugins-base installed?", - sizeof(void *) * 8 - ) - goto free_without_bin; - } - - converter = gst_element_factory_make("audioconvert", NULL); - if (!converter) - { - LOG_ERROR( - pSourceVoice->audio, - "Unable to create gstreamer audioconvert; is %zu-bit gst-plugins-base installed?", - sizeof(void *) * 8 - ) - goto free_without_bin; - } - - result->dst = gst_element_factory_make("appsink", NULL); - if (!result->dst) - { - LOG_ERROR( - pSourceVoice->audio, - "Unable to create gstreamer appsink; is %zu-bit gst-plugins-base installed?", - sizeof(void *) * 8 - ) - goto free_without_bin; - } - - /* turn off sync so we can pull data without waiting for it to "play" in realtime */ - g_object_set(G_OBJECT(result->dst), "sync", FALSE, "async", TRUE, NULL); - - /* Compile the pipeline, finally. */ - if (!gst_pad_set_active(result->srcpad, TRUE)) - { - LOG_ERROR( - pSourceVoice->audio, - "%s", - "Unable to activate srcpad" - ) - goto free_without_bin; - } - - gst_bin_add_many( - GST_BIN(result->pipeline), - decoder, - result->resampler, - converter, - result->dst, - NULL - ); - - decoder_sink = gst_element_get_static_pad(decoder, "sink"); - - if (gst_pad_link(result->srcpad, decoder_sink) != GST_PAD_LINK_OK) - { - LOG_ERROR( - pSourceVoice->audio, - "%s", - "Unable to get link our src pad to decoder sink pad" - ) - gst_object_unref(decoder_sink); - goto free_with_bin; - } - - gst_object_unref(decoder_sink); - - if (!gst_element_link_many( - result->resampler, - converter, - result->dst, - NULL)) - { - LOG_ERROR( - pSourceVoice->audio, - "%s", - "Unable to get link pipeline" - ) - goto free_with_bin; - } - - /* send stream-start */ - event = gst_event_new_stream_start("faudio/gstreamer"); - gst_pad_push_event(result->srcpad, event); - - /* Prepare the input format */ - result->blockAlign = (uint32_t) pSourceVoice->src.format->nBlockAlign; - if (type == FAUDIO_FORMAT_WMAUDIO3) - { - const FAudioWaveFormatExtensible *wfx = - (FAudioWaveFormatExtensible*) pSourceVoice->src.format; - extradata = (uint8_t*) &wfx->Samples; - codec_data_size = pSourceVoice->src.format->cbSize; - /* bufferList (and thus bufferWMA) can't be accessed yet. */ - } - else if (type == FAUDIO_FORMAT_WMAUDIO2) - { - FAudio_zero(fakeextradata, sizeof(fakeextradata)); - fakeextradata[4] = 31; - - extradata = fakeextradata; - codec_data_size = sizeof(fakeextradata); - /* bufferList (and thus bufferWMA) can't be accessed yet. */ - } - else if (type == FAUDIO_FORMAT_XMAUDIO2) - { - const FAudioXMA2WaveFormat *wfx = - (FAudioXMA2WaveFormat*) pSourceVoice->src.format; - extradata = (uint8_t*) &wfx->wNumStreams; - codec_data_size = pSourceVoice->src.format->cbSize; - /* XMA2 block size >= 16-bit limit and it doesn't come with bufferWMA. */ - result->blockAlign = wfx->dwBytesPerBlock; - result->blockCount = wfx->wBlockCount; - result->maxBytes = ( - (size_t) wfx->dwSamplesEncoded * - pSourceVoice->src.format->nChannels * - (pSourceVoice->src.format->wBitsPerSample / 8) - ); - } - else - { - extradata = NULL; - codec_data_size = 0; - FAudio_assert(0 && "Unrecognized WMA format!"); - } - codec_data = gst_buffer_new_and_alloc(codec_data_size); - gst_buffer_fill(codec_data, 0, extradata, codec_data_size); - caps = gst_caps_new_simple( - mimetype, - versiontype, G_TYPE_INT, version, - "bitrate", G_TYPE_INT, pSourceVoice->src.format->nAvgBytesPerSec * 8, - "channels", G_TYPE_INT, pSourceVoice->src.format->nChannels, - "rate", G_TYPE_INT, pSourceVoice->src.format->nSamplesPerSec, - "block_align", G_TYPE_INT, result->blockAlign, - "depth", G_TYPE_INT, pSourceVoice->src.format->wBitsPerSample, - "codec_data", GST_TYPE_BUFFER, codec_data, - NULL - ); - event = gst_event_new_caps(caps); - gst_pad_push_event(result->srcpad, event); - gst_caps_unref(caps); - gst_buffer_unref(codec_data); - - /* Prepare the output format */ - caps = gst_caps_new_simple( - "audio/x-raw", - "format", G_TYPE_STRING, gst_audio_format_to_string(GST_AUDIO_FORMAT_F32), - "layout", G_TYPE_STRING, "interleaved", - "channels", G_TYPE_INT, pSourceVoice->src.format->nChannels, - "rate", G_TYPE_INT, pSourceVoice->src.format->nSamplesPerSec, - NULL - ); - - gst_app_sink_set_caps(GST_APP_SINK(result->dst), caps); - gst_caps_unref(caps); - - gst_segment_init(&result->segment, GST_FORMAT_TIME); - - if (!FAudio_GSTREAMER_RestartDecoder(result)) - { - LOG_ERROR( - pSourceVoice->audio, - "%s", - "Starting decoder failed!" - ) - goto free_with_bin; - } - - pSourceVoice->src.wmadec = result; - pSourceVoice->src.decode = FAudio_INTERNAL_DecodeGSTREAMER; - - if (FAudio_GSTREAMER_CheckForBusErrors(pSourceVoice)) - { - LOG_ERROR( - pSourceVoice->audio, - "%s", - "Got a bus error after creation!" - ) - - pSourceVoice->src.wmadec = NULL; - pSourceVoice->src.decode = NULL; - - goto free_with_bin; - } - - LOG_FUNC_EXIT(pSourceVoice->audio) - return 0; - -free_without_bin: - if (result->dst) - { - gst_object_unref(result->dst); - } - if (converter) - { - gst_object_unref(converter); - } - if (result->resampler) - { - gst_object_unref(result->resampler); - } - if (result->srcpad) - { - gst_object_unref(result->srcpad); - } - if (decoder) - { - gst_object_unref(decoder); - } - if (result->pipeline) - { - gst_object_unref(result->pipeline); - } - pSourceVoice->audio->pFree(result); - LOG_FUNC_EXIT(pSourceVoice->audio) - return FAUDIO_E_UNSUPPORTED_FORMAT; - -free_with_bin: - gst_object_unref(result->srcpad); - gst_object_unref(result->pipeline); - pSourceVoice->audio->pFree(result); - LOG_FUNC_EXIT(pSourceVoice->audio) - return FAUDIO_E_UNSUPPORTED_FORMAT; -} - -void FAudio_WMADEC_free(FAudioSourceVoice *voice) -{ - LOG_FUNC_ENTER(voice->audio) - gst_element_set_state(voice->src.wmadec->pipeline, GST_STATE_NULL); - gst_object_unref(voice->src.wmadec->pipeline); - gst_object_unref(voice->src.wmadec->srcpad); - voice->audio->pFree(voice->src.wmadec->convertCache); - voice->audio->pFree(voice->src.wmadec->prevConvertCache); - voice->audio->pFree(voice->src.wmadec->blockSizes); - voice->audio->pFree(voice->src.wmadec); - voice->src.wmadec = NULL; - LOG_FUNC_EXIT(voice->audio) -} - -#else - -extern int this_tu_is_empty; - -#endif /* HAVE_GSTREAMER */ - -/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/WickedEngine/Utility/FAudio/src/FAudio_internal.c b/WickedEngine/Utility/FAudio/src/FAudio_internal.c index d7b1fed19c..37609ae888 100644 --- a/WickedEngine/Utility/FAudio/src/FAudio_internal.c +++ b/WickedEngine/Utility/FAudio/src/FAudio_internal.c @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 @@ -105,6 +105,7 @@ static const char *get_wformattag_string(const FAudioWaveFormatEx *fmt) FMT_STRING(IEEE_FLOAT) FMT_STRING(XMAUDIO2) FMT_STRING(WMAUDIO2) + FMT_STRING(WMAUDIO3) FMT_STRING(EXTENSIBLE) #undef FMT_STRING return "UNKNOWN!"; @@ -211,9 +212,9 @@ void LinkedList_RemoveEntry( FAudioFreeFunc pFree ) { LinkedList *latest, *prev; + FAudio_PlatformLockMutex(lock); latest = *start; prev = latest; - FAudio_PlatformLockMutex(lock); while (latest != NULL) { if (latest->entry == toRemove) @@ -382,6 +383,12 @@ static void FAudio_INTERNAL_DecodeBuffers( if ( voice->src.callback != NULL && voice->src.callback->OnBufferStart != NULL ) { + FAudio_PlatformUnlockMutex(voice->src.bufferLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock) + + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + FAudio_PlatformUnlockMutex(voice->audio->sourceLock); LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock) @@ -392,6 +399,12 @@ static void FAudio_INTERNAL_DecodeBuffers( FAudio_PlatformLockMutex(voice->audio->sourceLock); LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock) + + FAudio_PlatformLockMutex(voice->sendLock); + LOG_MUTEX_LOCK(voice->audio, voice->sendLock) + + FAudio_PlatformLockMutex(voice->src.bufferLock); + LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock) } } @@ -441,6 +454,12 @@ static void FAudio_INTERNAL_DecodeBuffers( if ( voice->src.callback != NULL && voice->src.callback->OnLoopEnd != NULL ) { + FAudio_PlatformUnlockMutex(voice->src.bufferLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock) + + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + FAudio_PlatformUnlockMutex(voice->audio->sourceLock); LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock) @@ -451,6 +470,12 @@ static void FAudio_INTERNAL_DecodeBuffers( FAudio_PlatformLockMutex(voice->audio->sourceLock); LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock) + + FAudio_PlatformLockMutex(voice->sendLock); + LOG_MUTEX_LOCK(voice->audio, voice->sendLock) + + FAudio_PlatformLockMutex(voice->src.bufferLock); + LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock) } } else @@ -503,6 +528,12 @@ static void FAudio_INTERNAL_DecodeBuffers( /* Callbacks */ if (voice->src.callback != NULL) { + FAudio_PlatformUnlockMutex(voice->src.bufferLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock) + + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + FAudio_PlatformUnlockMutex(voice->audio->sourceLock); LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock) @@ -521,6 +552,15 @@ static void FAudio_INTERNAL_DecodeBuffers( ); } + FAudio_PlatformLockMutex(voice->audio->sourceLock); + LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock) + + FAudio_PlatformLockMutex(voice->sendLock); + LOG_MUTEX_LOCK(voice->audio, voice->sendLock) + + FAudio_PlatformLockMutex(voice->src.bufferLock); + LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock) + /* One last chance at redemption */ if (buffer == NULL && voice->src.bufferList != NULL) { @@ -530,14 +570,29 @@ static void FAudio_INTERNAL_DecodeBuffers( if (buffer != NULL && voice->src.callback->OnBufferStart != NULL) { + FAudio_PlatformUnlockMutex(voice->src.bufferLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock) + + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + + FAudio_PlatformUnlockMutex(voice->audio->sourceLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock) + voice->src.callback->OnBufferStart( voice->src.callback, buffer->pContext ); - } - FAudio_PlatformLockMutex(voice->audio->sourceLock); - LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock) + FAudio_PlatformLockMutex(voice->audio->sourceLock); + LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock) + + FAudio_PlatformLockMutex(voice->sendLock); + LOG_MUTEX_LOCK(voice->audio, voice->sendLock) + + FAudio_PlatformLockMutex(voice->src.bufferLock); + LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock) + } } voice->audio->pFree(toDelete); @@ -598,7 +653,7 @@ static void FAudio_INTERNAL_DecodeBuffers( static inline void FAudio_INTERNAL_FilterVoice( FAudio *audio, - const FAudioFilterParameters *filter, + const FAudioFilterParametersEXT *filter, FAudioFilterState *filterState, float *samples, uint32_t numSamples, @@ -630,7 +685,7 @@ static inline void FAudio_INTERNAL_FilterVoice( filterState[ci][FAudioHighPassFilter] = samples[j * numChannels + ci] - filterState[ci][FAudioLowPassFilter] - (filter->OneOverQ * filterState[ci][FAudioBandPassFilter]); filterState[ci][FAudioBandPassFilter] = (filter->Frequency * filterState[ci][FAudioHighPassFilter]) + filterState[ci][FAudioBandPassFilter]; filterState[ci][FAudioNotchFilter] = filterState[ci][FAudioHighPassFilter] + filterState[ci][FAudioLowPassFilter]; - samples[j * numChannels + ci] = filterState[ci][filter->Type]; + samples[j * numChannels + ci] = filterState[ci][filter->Type] * filter->WetDryMix + samples[j * numChannels + ci] * (1.0f - filter->WetDryMix); } LOG_FUNC_EXIT(audio) @@ -1249,6 +1304,9 @@ static void FAudio_INTERNAL_FlushPendingBuffers(FAudioSourceVoice *voice) if (voice->src.callback != NULL && voice->src.callback->OnBufferEnd != NULL) { + FAudio_PlatformUnlockMutex(voice->src.bufferLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock) + FAudio_PlatformUnlockMutex(voice->audio->sourceLock); LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock) @@ -1259,6 +1317,9 @@ static void FAudio_INTERNAL_FlushPendingBuffers(FAudioSourceVoice *voice) FAudio_PlatformLockMutex(voice->audio->sourceLock); LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock) + + FAudio_PlatformLockMutex(voice->src.bufferLock); + LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock) } voice->audio->pFree(entry); } @@ -1862,7 +1923,7 @@ void FAudio_INTERNAL_DecodeMonoMSADPCM( int32_t midOffset; /* PCM block cache */ - int16_t blockCache[1012]; /* Max block size */ + int16_t *blockCache; /* Block size */ uint32_t bsize = ((FAudioADPCMWaveFormat*) voice->src.format)->wSamplesPerBlock; @@ -1879,6 +1940,7 @@ void FAudio_INTERNAL_DecodeMonoMSADPCM( midOffset = (voice->src.curBufferOffset % bsize); /* Read in each block directly to the decode cache */ + blockCache = (int16_t*) FAudio_alloca(bsize * sizeof(int16_t)); while (done < samples) { copy = FAudio_min(samples - done, bsize - midOffset); @@ -1896,6 +1958,7 @@ void FAudio_INTERNAL_DecodeMonoMSADPCM( done += copy; midOffset = 0; } + FAudio_dealloca(blockCache); LOG_FUNC_EXIT(voice->audio) } @@ -1913,7 +1976,7 @@ void FAudio_INTERNAL_DecodeStereoMSADPCM( int32_t midOffset; /* PCM block cache */ - int16_t blockCache[2024]; /* Max block size */ + int16_t *blockCache; /* Align, block size */ uint32_t bsize = ((FAudioADPCMWaveFormat*) voice->src.format)->wSamplesPerBlock; @@ -1930,6 +1993,7 @@ void FAudio_INTERNAL_DecodeStereoMSADPCM( midOffset = (voice->src.curBufferOffset % bsize); /* Read in each block directly to the decode cache */ + blockCache = (int16_t*) FAudio_alloca(bsize * 2 * sizeof(int16_t)); while (done < samples) { copy = FAudio_min(samples - done, bsize - midOffset); @@ -1947,6 +2011,7 @@ void FAudio_INTERNAL_DecodeStereoMSADPCM( done += copy; midOffset = 0; } + FAudio_dealloca(blockCache); LOG_FUNC_EXIT(voice->audio) } diff --git a/WickedEngine/Utility/FAudio/src/FAudio_internal.h b/WickedEngine/Utility/FAudio/src/FAudio_internal.h index d72cb37abc..039ab92164 100644 --- a/WickedEngine/Utility/FAudio/src/FAudio_internal.h +++ b/WickedEngine/Utility/FAudio/src/FAudio_internal.h @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 @@ -28,6 +28,7 @@ #include "FAPOBase.h" #include + #ifdef FAUDIO_WIN32_PLATFORM #include #include @@ -37,8 +38,8 @@ #include #include -#include -#include +#define WIN32_LEAN_AND_MEAN +#include #define FAudio_malloc malloc #define FAudio_realloc realloc @@ -57,6 +58,7 @@ #define FAudio_strlcpy(ptr1, ptr2, size) lstrcpynA(ptr1, ptr2, size) #define FAudio_pow(x, y) pow(x, y) +#define FAudio_powf(x, y) powf(x, y) #define FAudio_log(x) log(x) #define FAudio_log10(x) log10(x) #define FAudio_sin(x) sin(x) @@ -109,11 +111,44 @@ extern void FAudio_Log(char const *msg); ((x << 24) & 0x00FF000000000000) | \ ((x << 32) & 0xFF00000000000000) #else + +#ifdef FAUDIO_SDL3_PLATFORM +#include +#include +#include +#include + +#define FAudio_swap16LE(x) SDL_Swap16LE(x) +#define FAudio_swap16BE(x) SDL_Swap16BE(x) +#define FAudio_swap32LE(x) SDL_Swap32LE(x) +#define FAudio_swap32BE(x) SDL_Swap32BE(x) +#define FAudio_swap64LE(x) SDL_Swap64LE(x) +#define FAudio_swap64BE(x) SDL_Swap64BE(x) + +/* SDL3 allows memcpy/memset for compiler optimization reasons */ +#ifdef SDL_SLOW_MEMCPY +#define STB_MEMCPY_OVERRIDE +#endif +#ifdef SDL_SLOW_MEMSET +#define STB_MEMSET_OVERRIDE +#endif +#else #include #include #include #include +#define FAudio_swap16LE(x) SDL_SwapLE16(x) +#define FAudio_swap16BE(x) SDL_SwapBE16(x) +#define FAudio_swap32LE(x) SDL_SwapLE32(x) +#define FAudio_swap32BE(x) SDL_SwapBE32(x) +#define FAudio_swap64LE(x) SDL_SwapLE64(x) +#define FAudio_swap64BE(x) SDL_SwapBE64(x) + +#define STB_MEMCPY_OVERRIDE +#define STB_MEMSET_OVERRIDE +#endif + #define FAudio_malloc SDL_malloc #define FAudio_realloc SDL_realloc #define FAudio_free SDL_free @@ -131,6 +166,7 @@ extern void FAudio_Log(char const *msg); #define FAudio_strlcpy(ptr1, ptr2, size) SDL_strlcpy(ptr1, ptr2, size) #define FAudio_pow(x, y) SDL_pow(x, y) +#define FAudio_powf(x, y) SDL_powf(x, y) #define FAudio_log(x) SDL_log(x) #define FAudio_log10(x) SDL_log10(x) #define FAudio_sin(x) SDL_sin(x) @@ -171,13 +207,6 @@ extern void FAudio_Log(char const *msg); #define FAudio_getenv SDL_getenv #define FAudio_PRIu64 SDL_PRIu64 #define FAudio_PRIx64 SDL_PRIx64 - -#define FAudio_swap16LE(x) SDL_SwapLE16(x) -#define FAudio_swap16BE(x) SDL_SwapBE16(x) -#define FAudio_swap32LE(x) SDL_SwapLE32(x) -#define FAudio_swap32BE(x) SDL_SwapBE32(x) -#define FAudio_swap64LE(x) SDL_SwapLE64(x) -#define FAudio_swap64BE(x) SDL_SwapBE64(x) #endif /* Easy Macros */ @@ -210,6 +239,15 @@ extern void FAudio_Log(char const *msg); #define restrict #endif +/* Alignment macro for gcc/clang/msvc */ +#if defined(__clang__) || defined(__GNUC__) +#define ALIGN(type, boundary) type __attribute__((aligned(boundary))) +#elif defined(_MSC_VER) +#define ALIGN(type, boundary) __declspec(align(boundary)) type +#else +#define ALIGN(type, boundary) type +#endif + /* Threading Types */ typedef void* FAudioThread; @@ -323,13 +361,13 @@ void FAudio_OPERATIONSET_QueueSetEffectParameters( ); void FAudio_OPERATIONSET_QueueSetFilterParameters( FAudioVoice *voice, - const FAudioFilterParameters *pParameters, + const FAudioFilterParametersEXT *pParameters, uint32_t OperationSet ); void FAudio_OPERATIONSET_QueueSetOutputFilterParameters( FAudioVoice *voice, FAudioVoice *pDestinationVoice, - const FAudioFilterParameters *pParameters, + const FAudioFilterParametersEXT *pParameters, uint32_t OperationSet ); void FAudio_OPERATIONSET_QueueSetVolume( @@ -433,7 +471,7 @@ struct FAudioVoice float **sendCoefficients; float **mixCoefficients; FAudioMixCallback *sendMix; - FAudioFilterParameters *sendFilter; + FAudioFilterParametersEXT *sendFilter; FAudioFilterState **sendFilterState; struct { @@ -445,7 +483,7 @@ struct FAudioVoice uint8_t *parameterUpdates; uint8_t *inPlaceProcessing; } effects; - FAudioFilterParameters filter; + FAudioFilterParametersEXT filter; FAudioFilterState *filterState; FAudioMutex sendLock; FAudioMutex effectLock; @@ -618,10 +656,10 @@ void FAudio_INTERNAL_debug_fmt( #define LOG_FUNC_ENTER(engine) PRINT_DEBUG(engine, FUNC_CALLS, "FUNC Enter", "%s", __func__) #define LOG_FUNC_EXIT(engine) PRINT_DEBUG(engine, FUNC_CALLS, "FUNC Exit", "%s", __func__) /* TODO: LOG_TIMING */ -#define LOG_MUTEX_CREATE(engine, mutex) PRINT_DEBUG(engine, LOCKS, "Mutex Create", "%p", mutex) -#define LOG_MUTEX_DESTROY(engine, mutex) PRINT_DEBUG(engine, LOCKS, "Mutex Destroy", "%p", mutex) -#define LOG_MUTEX_LOCK(engine, mutex) PRINT_DEBUG(engine, LOCKS, "Mutex Lock", "%p", mutex) -#define LOG_MUTEX_UNLOCK(engine, mutex) PRINT_DEBUG(engine, LOCKS, "Mutex Unlock", "%p", mutex) +#define LOG_MUTEX_CREATE(engine, mutex) PRINT_DEBUG(engine, LOCKS, "Mutex Create", "%p (%s)", mutex, #mutex) +#define LOG_MUTEX_DESTROY(engine, mutex) PRINT_DEBUG(engine, LOCKS, "Mutex Destroy", "%p (%s)", mutex, #mutex) +#define LOG_MUTEX_LOCK(engine, mutex) PRINT_DEBUG(engine, LOCKS, "Mutex Lock", "%p (%s)", mutex, #mutex) +#define LOG_MUTEX_UNLOCK(engine, mutex) PRINT_DEBUG(engine, LOCKS, "Mutex Unlock", "%p (%s)", mutex, #mutex) /* TODO: LOG_MEMORY */ /* TODO: LOG_STREAMING */ diff --git a/WickedEngine/Utility/FAudio/src/FAudio_internal_simd.c b/WickedEngine/Utility/FAudio/src/FAudio_internal_simd.c index 8746d358ca..7b7997b2ad 100644 --- a/WickedEngine/Utility/FAudio/src/FAudio_internal_simd.c +++ b/WickedEngine/Utility/FAudio/src/FAudio_internal_simd.c @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 @@ -32,23 +32,23 @@ * https://hg.icculus.org/icculus/mojoAL/file/default/mojoal.c */ -#if defined(__x86_64__) || defined(_M_X64) +#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm64ec__) || defined(_M_ARM64EC) /* Some platforms fail to define this... */ - #ifndef __SSE2__ - #define __SSE2__ 1 + #ifndef __ARM_NEON__ + #define __ARM_NEON__ 1 #endif - /* x86_64 guarantees SSE2. */ + /* AArch64 guarantees NEON. */ #define NEED_SCALAR_CONVERTER_FALLBACKS 0 -#elif defined(__aarch64__) || defined(_M_ARM64) +#elif defined(__x86_64__) || defined(_M_X64) /* Some platforms fail to define this... */ - #ifndef __ARM_NEON__ - #define __ARM_NEON__ 1 + #ifndef __SSE2__ + #define __SSE2__ 1 #endif - /* AArch64 guarantees NEON. */ + /* x86_64 guarantees SSE2. */ #define NEED_SCALAR_CONVERTER_FALLBACKS 0 -#elif __MACOSX__ +#elif __MACOSX__ && !defined(__POWERPC__) /* Some build systems may need to specify this. */ #if !defined(__SSE2__) && !defined(__ARM_NEON__) #error macOS does not have SSE2/NEON? Bad compiler? @@ -62,7 +62,7 @@ #endif /* Our NEON paths require AArch64, don't check __ARM_NEON__ here */ -#if defined(__aarch64__) || defined(_M_ARM64) +#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm64ec__) || defined(_M_ARM64EC) #include #define HAVE_NEON_INTRINSICS 1 #endif @@ -285,10 +285,10 @@ void FAudio_INTERNAL_Convert_U8_To_F32_NEON( const uint16x8_t uint16hi = vmovl_u8(vget_high_u8(bytes)); /* convert top 8 bytes to 8 uint16 */ const uint16x8_t uint16lo = vmovl_u8(vget_low_u8(bytes)); /* convert bottom 8 bytes to 8 uint16 */ /* split uint16 to two uint32, then convert to float, then multiply to normalize, subtract to adjust for sign, store. */ - vst1q_f32(dst, vmlaq_f32(negone, vcvtq_f32_u32(vmovl_u16(vget_low_u16(uint16hi))), divby128)); - vst1q_f32(dst+4, vmlaq_f32(negone, vcvtq_f32_u32(vmovl_u16(vget_high_u16(uint16hi))), divby128)); - vst1q_f32(dst+8, vmlaq_f32(negone, vcvtq_f32_u32(vmovl_u16(vget_low_u16(uint16lo))), divby128)); - vst1q_f32(dst+12, vmlaq_f32(negone, vcvtq_f32_u32(vmovl_u16(vget_high_u16(uint16lo))), divby128)); + vst1q_f32(dst, vmlaq_f32(negone, vcvtq_f32_u32(vmovl_u16(vget_low_u16(uint16lo))), divby128)); + vst1q_f32(dst+4, vmlaq_f32(negone, vcvtq_f32_u32(vmovl_u16(vget_high_u16(uint16lo))), divby128)); + vst1q_f32(dst+8, vmlaq_f32(negone, vcvtq_f32_u32(vmovl_u16(vget_low_u16(uint16hi))), divby128)); + vst1q_f32(dst+12, vmlaq_f32(negone, vcvtq_f32_u32(vmovl_u16(vget_high_u16(uint16hi))), divby128)); i -= 16; mmsrc -= 16; dst -= 16; } @@ -903,7 +903,7 @@ void FAudio_INTERNAL_ResampleMono_NEON( cur_frac = vdupq_n_s32( (uint32_t) (cur_scalar & FIXED_FRACTION_MASK) - DOUBLE_TO_FIXED(0.5) ); - int32_t __attribute__((aligned(16))) data[4] = + ALIGN(int32_t, 16) data[4] = { 0, (uint32_t) (resampleStep & FIXED_FRACTION_MASK), @@ -1077,7 +1077,7 @@ void FAudio_INTERNAL_ResampleStereo_NEON( cur_frac = vdupq_n_s32( (uint32_t) (cur_scalar & FIXED_FRACTION_MASK) - DOUBLE_TO_FIXED(0.5) ); - int32_t __attribute__((aligned(16))) data[4] = + ALIGN(int32_t, 16) data[4] = { 0, 0, diff --git a/WickedEngine/Utility/FAudio/src/FAudio_operationset.c b/WickedEngine/Utility/FAudio/src/FAudio_operationset.c index f17cd9270d..b5cae528e3 100644 --- a/WickedEngine/Utility/FAudio/src/FAudio_operationset.c +++ b/WickedEngine/Utility/FAudio/src/FAudio_operationset.c @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 @@ -70,12 +70,12 @@ struct FAudio_OPERATIONSET_Operation } SetEffectParameters; struct { - FAudioFilterParameters Parameters; + FAudioFilterParametersEXT Parameters; } SetFilterParameters; struct { FAudioVoice *pDestinationVoice; - FAudioFilterParameters Parameters; + FAudioFilterParametersEXT Parameters; } SetOutputFilterParameters; struct { @@ -169,7 +169,7 @@ static inline void ExecuteOperation(FAudio_OPERATIONSET_Operation *op) break; case FAUDIOOP_SETFILTERPARAMETERS: - FAudioVoice_SetFilterParameters( + FAudioVoice_SetFilterParametersEXT( op->Voice, &op->Data.SetFilterParameters.Parameters, FAUDIO_COMMIT_NOW @@ -177,7 +177,7 @@ static inline void ExecuteOperation(FAudio_OPERATIONSET_Operation *op) break; case FAUDIOOP_SETOUTPUTFILTERPARAMETERS: - FAudioVoice_SetOutputFilterParameters( + FAudioVoice_SetOutputFilterParametersEXT( op->Voice, op->Data.SetOutputFilterParameters.pDestinationVoice, &op->Data.SetOutputFilterParameters.Parameters, @@ -472,7 +472,7 @@ void FAudio_OPERATIONSET_QueueSetEffectParameters( void FAudio_OPERATIONSET_QueueSetFilterParameters( FAudioVoice *voice, - const FAudioFilterParameters *pParameters, + const FAudioFilterParametersEXT *pParameters, uint32_t OperationSet ) { FAudio_OPERATIONSET_Operation *op; @@ -489,7 +489,7 @@ void FAudio_OPERATIONSET_QueueSetFilterParameters( FAudio_memcpy( &op->Data.SetFilterParameters.Parameters, pParameters, - sizeof(FAudioFilterParameters) + sizeof(FAudioFilterParametersEXT) ); FAudio_PlatformUnlockMutex(voice->audio->operationLock); @@ -499,7 +499,7 @@ void FAudio_OPERATIONSET_QueueSetFilterParameters( void FAudio_OPERATIONSET_QueueSetOutputFilterParameters( FAudioVoice *voice, FAudioVoice *pDestinationVoice, - const FAudioFilterParameters *pParameters, + const FAudioFilterParametersEXT *pParameters, uint32_t OperationSet ) { FAudio_OPERATIONSET_Operation *op; @@ -517,7 +517,7 @@ void FAudio_OPERATIONSET_QueueSetOutputFilterParameters( FAudio_memcpy( &op->Data.SetOutputFilterParameters.Parameters, pParameters, - sizeof(FAudioFilterParameters) + sizeof(FAudioFilterParametersEXT) ); FAudio_PlatformUnlockMutex(voice->audio->operationLock); diff --git a/WickedEngine/Utility/FAudio/src/FAudio_platform_sdl2.c b/WickedEngine/Utility/FAudio/src/FAudio_platform_sdl2.c index 476d6eb3aa..9eaa157047 100644 --- a/WickedEngine/Utility/FAudio/src/FAudio_platform_sdl2.c +++ b/WickedEngine/Utility/FAudio/src/FAudio_platform_sdl2.c @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 @@ -24,14 +24,14 @@ * */ -#ifndef FAUDIO_WIN32_PLATFORM +#if !defined(FAUDIO_WIN32_PLATFORM) && !defined(FAUDIO_SDL3_PLATFORM) #include "FAudio_internal.h" #include -#if !SDL_VERSION_ATLEAST(2, 0, 9) -#error "SDL version older than 2.0.9" +#if !SDL_VERSION_ATLEAST(2, 24, 0) +#error "SDL version older than 2.24.0" #endif /* !SDL_VERSION_ATLEAST */ /* Mixer Thread */ @@ -52,8 +52,70 @@ static void FAudio_INTERNAL_MixCallback(void *userdata, Uint8 *stream, int len) /* Platform Functions */ +static void FAudio_INTERNAL_PrioritizeDirectSound() +{ + int numdrivers, i, wasapi, directsound; + void *dll, *proc; + + if (SDL_GetHint("SDL_AUDIODRIVER") != NULL) + { + /* Already forced to something, ignore */ + return; + } + + /* Windows 10+ decided to break version detection, so instead of doing + * it the right way we have to do something dumb like search for an + * export that's only in Windows 10 or newer. + * -flibit + */ + if (SDL_strcmp(SDL_GetPlatform(), "Windows") != 0) + { + return; + } + dll = SDL_LoadObject("USER32.DLL"); + if (dll == NULL) + { + return; + } + proc = SDL_LoadFunction(dll, "SetProcessDpiAwarenessContext"); + SDL_UnloadObject(dll); /* We aren't really using this, unload now */ + if (proc != NULL) + { + /* OS is new enough to trust WASAPI, bail */ + return; + } + + /* Check to see if we have both Windows drivers in the list */ + numdrivers = SDL_GetNumAudioDrivers(); + wasapi = -1; + directsound = -1; + for (i = 0; i < numdrivers; i += 1) + { + const char *driver = SDL_GetAudioDriver(i); + if (SDL_strcmp(driver, "wasapi") == 0) + { + wasapi = i; + } + else if (SDL_strcmp(driver, "directsound") == 0) + { + directsound = i; + } + } + + /* We force if and only if both drivers exist and wasapi is earlier */ + if ((wasapi > -1) && (directsound > -1)) + { + if (wasapi < directsound) + { + SDL_SetHint("SDL_AUDIODRIVER", "directsound"); + } + } +} + void FAudio_PlatformAddRef() { + FAudio_INTERNAL_PrioritizeDirectSound(); + /* SDL tracks ref counts for each subsystem */ if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { @@ -81,16 +143,15 @@ void FAudio_PlatformInit( ) { SDL_AudioDeviceID device; SDL_AudioSpec want, have; - const char *driver; - int changes = 0; FAudio_assert(mixFormat != NULL); FAudio_assert(updateSize != NULL); + FAudio_assert((mixFormat->Format.nChannels <= 255) && "mixFormat->Format.nChannels out of range!"); /* Build the device spec */ want.freq = mixFormat->Format.nSamplesPerSec; - want.format = AUDIO_F32; - want.channels = mixFormat->Format.nChannels; + want.format = AUDIO_F32SYS; + want.channels = (Uint8)(mixFormat->Format.nChannels); want.silence = 0; want.callback = FAudio_INTERNAL_MixCallback; want.userdata = audio; @@ -108,48 +169,6 @@ void FAudio_PlatformInit( want.samples = want.freq / 100; } - /* FIXME: SDL bug! - * The PulseAudio backend does this annoying thing where it halves the - * buffer size to prevent latency issues: - * - * https://hg.libsdl.org/SDL/file/df343364c6c5/src/audio/pulseaudio/SDL_pulseaudio.c#l577 - * - * To get the _actual_ quantum size we want, we just double the buffer - * size and allow SDL to set the quantum size back to normal. - * -flibit - */ - driver = SDL_GetCurrentAudioDriver(); - if (SDL_strcmp(driver, "pulseaudio") == 0) - { - want.samples *= 2; - changes = SDL_AUDIO_ALLOW_SAMPLES_CHANGE; - } - - /* FIXME: SDL bug! - * The most common backends support varying samples values, but many - * require a power-of-two value, which XAudio2 is not a fan of. - * Normally SDL creates an intermediary stream to handle this, but this - * has not been written yet: - * https://bugzilla.libsdl.org/show_bug.cgi?id=5136 - * -flibit - */ - else if ( SDL_strcmp(driver, "emscripten") == 0 || - SDL_strcmp(driver, "dsp") == 0 ) - { - want.samples -= 1; - want.samples |= want.samples >> 1; - want.samples |= want.samples >> 2; - want.samples |= want.samples >> 4; - want.samples |= want.samples >> 8; - want.samples |= want.samples >> 16; - want.samples += 1; - SDL_Log( - "Forcing FAudio quantum to a power-of-two.\n" - "You don't actually want this, it's technically a bug:\n" - "https://bugzilla.libsdl.org/show_bug.cgi?id=5136" - ); - } - /* Open the device (or at least try to) */ iosretry: device = SDL_OpenAudioDevice( @@ -157,7 +176,7 @@ void FAudio_PlatformInit( 0, &want, &have, - changes + 0 ); if (device == 0) { @@ -224,7 +243,7 @@ uint32_t FAudio_PlatformGetDeviceDetails( const char *name, *envvar; int channels, rate; SDL_AudioSpec spec; - uint32_t devcount, i; + uint32_t devcount; FAudio_zero(details, sizeof(FAudioDeviceDetails)); @@ -284,86 +303,27 @@ uint32_t FAudio_PlatformGetDeviceDetails( channels = 0; } -#if SDL_VERSION_ATLEAST(2, 0, 15) + /* Get the device format from the OS */ if (index == 0) { - /* Okay, so go grab something from the liquor cabinet and get - * ready, because this loop is a bit of a trip: - * - * We can't get the spec for the default device, because in - * audio land a "default device" is a completely foreign idea, - * some APIs support it but in reality you just have to pass - * NULL as a driver string and the sound server figures out the - * rest. In some psychotic universe the device can even be a - * network address. No, seriously. - * - * So what do we do? Well, at least in my experience shipping - * for the PC, the easiest thing to do is assume that the - * highest spec in the list is what you should target, even if - * it turns out that's not the default at the time you create - * your device. - * - * Consider a laptop that has built-in stereo speakers, but is - * connected to a home theater system with 5.1 audio. It may be - * the case that the stereo audio is active, but the user may - * at some point move audio to 5.1, at which point the server - * will simply move the endpoint from underneath us and move our - * output stream to the new device. At that point, you _really_ - * want to already be pushing out 5.1, because if not the user - * will be stuck recreating the whole program, which on many - * platforms is an instant cert failure. The tradeoff is that - * you're potentially downmixing a 5.1 stream to stereo, which - * is a bit wasteful, but presumably the hardware can handle it - * if they were able to use a 5.1 system to begin with. - * - * So, we just aim for the highest channel count on the system. - * We also do this with sample rate to a lesser degree; we try - * to use a single device spec at a time, so it may be that - * the sample rate you get isn't the highest from the list if - * another device had a higher channel count. - * - * Lastly, if you set SDL_AUDIO_CHANNELS but not - * SDL_AUDIO_FREQUENCY, we don't bother checking for a sample - * rate, we fall through to the hardcoded value at the bottom of - * this function. - * - * I'm so tired. - * - * -flibit - */ - if (channels <= 0) + /* TODO: Do we want to squeeze the name into the output? */ + if (SDL_GetDefaultAudioInfo(NULL, &spec, 0) < 0) { - const uint8_t setRate = (rate <= 0); - devcount -= 1; /* Subtracting the default index */ - for (i = 0; i < devcount; i += 1) - { - SDL_GetAudioDeviceSpec(i, 0, &spec); - if ( (spec.channels > channels) && - (spec.channels <= 8) ) - { - channels = spec.channels; - if (setRate) - { - /* May be 0! That's okay! */ - rate = spec.freq; - } - } - } + SDL_zero(spec); } } else { SDL_GetAudioDeviceSpec(index - 1, 0, &spec); - if ((spec.freq > 0) && (rate <= 0)) - { - rate = spec.freq; - } - if ((spec.channels > 0) && (channels <= 0)) - { - channels = spec.channels; - } } -#endif /* SDL >= 2.0.15 */ + if ((spec.freq > 0) && (rate <= 0)) + { + rate = spec.freq; + } + if ((spec.channels > 0) && (spec.channels < 9) && (channels <= 0)) + { + channels = spec.channels; + } /* If we make it all the way here with no format, hardcode a sane one */ if (rate <= 0) @@ -712,4 +672,4 @@ void FAudio_UTF8_To_UTF16(const char *src, uint16_t *dst, size_t len) extern int this_tu_is_empty; -#endif /* FAUDIO_WIN32_PLATFORM */ +#endif /* !defined(FAUDIO_WIN32_PLATFORM) && !defined(FAUDIO_SDL3_PLATFORM) */ diff --git a/WickedEngine/Utility/FAudio/src/FAudio_platform_sdl3.c b/WickedEngine/Utility/FAudio/src/FAudio_platform_sdl3.c new file mode 100644 index 0000000000..916fdf000f --- /dev/null +++ b/WickedEngine/Utility/FAudio/src/FAudio_platform_sdl3.c @@ -0,0 +1,734 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2024 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 + * + */ + +#ifdef FAUDIO_SDL3_PLATFORM + +#include "FAudio_internal.h" + +#include + +typedef struct SDLAudioDevice +{ + FAudio *audio; + SDL_AudioStream *stream; + float *stagingBuffer; + size_t stagingLen; +} SDLAudioDevice; + +/* Mixer Thread */ + +static void FAudio_INTERNAL_MixCallback( + void *userdata, + SDL_AudioStream *stream, + int additional_amount, + int total_amount +) { + SDLAudioDevice *dev = (SDLAudioDevice*) userdata; + + if (!dev->audio->active) + { + /* Nothing to do, SDL will fill in for us */ + return; + } + + while (additional_amount > 0) + { + FAudio_zero(dev->stagingBuffer, dev->stagingLen); + FAudio_INTERNAL_UpdateEngine(dev->audio, dev->stagingBuffer); + SDL_PutAudioStreamData( + stream, + dev->stagingBuffer, + dev->stagingLen + ); + additional_amount -= dev->stagingLen; + } +} + +/* Platform Functions */ + +static void FAudio_INTERNAL_PrioritizeDirectSound() +{ + int numdrivers, i, wasapi, directsound; + void *dll, *proc; + + if (SDL_GetHint("SDL_AUDIO_DRIVER") != NULL) + { + /* Already forced to something, ignore */ + return; + } + + /* Windows 10+ decided to break version detection, so instead of doing + * it the right way we have to do something dumb like search for an + * export that's only in Windows 10 or newer. + * -flibit + */ + if (SDL_strcmp(SDL_GetPlatform(), "Windows") != 0) + { + return; + } + dll = SDL_LoadObject("USER32.DLL"); + if (dll == NULL) + { + return; + } + proc = SDL_LoadFunction(dll, "SetProcessDpiAwarenessContext"); + SDL_UnloadObject(dll); /* We aren't really using this, unload now */ + if (proc != NULL) + { + /* OS is new enough to trust WASAPI, bail */ + return; + } + + /* Check to see if we have both Windows drivers in the list */ + numdrivers = SDL_GetNumAudioDrivers(); + wasapi = -1; + directsound = -1; + for (i = 0; i < numdrivers; i += 1) + { + const char *driver = SDL_GetAudioDriver(i); + if (SDL_strcmp(driver, "wasapi") == 0) + { + wasapi = i; + } + else if (SDL_strcmp(driver, "directsound") == 0) + { + directsound = i; + } + } + + /* We force if and only if both drivers exist and wasapi is earlier */ + if ((wasapi > -1) && (directsound > -1)) + { + if (wasapi < directsound) + { + SDL_SetHint("SDL_AUDIO_DRIVER", "directsound"); + } + } +} + +void FAudio_PlatformAddRef() +{ + FAudio_INTERNAL_PrioritizeDirectSound(); + + /* SDL tracks ref counts for each subsystem */ + if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) + { + SDL_Log("SDL_INIT_AUDIO failed: %s", SDL_GetError()); + } + FAudio_INTERNAL_InitSIMDFunctions( + SDL_HasSSE2(), + SDL_HasNEON() + ); +} + +void FAudio_PlatformRelease() +{ + /* SDL tracks ref counts for each subsystem */ + SDL_QuitSubSystem(SDL_INIT_AUDIO); +} + +void FAudio_PlatformInit( + FAudio *audio, + uint32_t flags, + uint32_t deviceIndex, + FAudioWaveFormatExtensible *mixFormat, + uint32_t *updateSize, + void** platformDevice +) { + SDLAudioDevice *result; + SDL_AudioDeviceID devID; + SDL_AudioSpec spec; + int wantSamples; + + FAudio_assert(mixFormat != NULL); + FAudio_assert(updateSize != NULL); + + /* Build the device spec */ + spec.freq = mixFormat->Format.nSamplesPerSec; + spec.format = SDL_AUDIO_F32; + spec.channels = mixFormat->Format.nChannels; + if (flags & FAUDIO_1024_QUANTUM) + { + /* Get the sample count for a 21.33ms frame. + * For 48KHz this should be 1024. + */ + wantSamples = (int) ( + spec.freq / (1000.0 / (64.0 / 3.0)) + ); + } + else + { + wantSamples = spec.freq / 100; + } + + if (deviceIndex == 0) + { + devID = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; + } + else + { + int devcount; + SDL_AudioDeviceID *devs = SDL_GetAudioPlaybackDevices(&devcount); + + /* Bounds checking is done before this function is called */ + devID = devs[deviceIndex - 1]; + + SDL_free(devs); + } + + result = (SDLAudioDevice*) SDL_malloc(sizeof(SDLAudioDevice)); + result->audio = audio; + result->stagingLen = wantSamples * spec.channels * sizeof(float); + result->stagingBuffer = (float*) SDL_malloc(result->stagingLen); + + /* Open the device (or at least try to) */ + result->stream = SDL_OpenAudioDeviceStream( + devID, + &spec, + FAudio_INTERNAL_MixCallback, + result + ); + + /* Write up the received format for the engine */ + WriteWaveFormatExtensible( + mixFormat, + spec.channels, + spec.freq, + &DATAFORMAT_SUBTYPE_IEEE_FLOAT + ); + *updateSize = wantSamples; + + /* SDL_AudioDeviceID is a Uint32, anybody using a 16-bit PC still? */ + *platformDevice = result; + + /* Start the thread! */ + SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(result->stream)); +} + +void FAudio_PlatformQuit(void* platformDevice) +{ + SDLAudioDevice *dev = (SDLAudioDevice*) platformDevice; + SDL_AudioDeviceID devID = SDL_GetAudioStreamDevice(dev->stream); + SDL_DestroyAudioStream(dev->stream); + SDL_CloseAudioDevice(devID); + SDL_free(dev->stagingBuffer); + SDL_free(dev); +} + +uint32_t FAudio_PlatformGetDeviceCount() +{ + int devcount; + SDL_free(SDL_GetAudioPlaybackDevices(&devcount)); + if (devcount == 0) + { + return 0; + } + SDL_assert(devcount > 0); + return devcount + 1; /* Add one for "Default Device" */ +} + +void FAudio_UTF8_To_UTF16(const char *src, uint16_t *dst, size_t len); + +uint32_t FAudio_PlatformGetDeviceDetails( + uint32_t index, + FAudioDeviceDetails *details +) { + const char *name, *envvar; + int channels, rate; + SDL_AudioSpec spec; + int devcount; + SDL_AudioDeviceID *devs; + + FAudio_zero(details, sizeof(FAudioDeviceDetails)); + + devs = SDL_GetAudioPlaybackDevices(&devcount); + if (index > devcount) + { + SDL_free(devs); + return FAUDIO_E_INVALID_CALL; + } + + details->DeviceID[0] = L'0' + index; + if (index == 0) + { + name = "Default Device"; + details->Role = FAudioGlobalDefaultDevice; + + /* This variable will look like a DSound GUID or WASAPI ID, i.e. + * "{0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B}" + */ + envvar = SDL_getenv("FAUDIO_FORCE_DEFAULT_DEVICEID"); + if (envvar != NULL) + { + FAudio_UTF8_To_UTF16( + envvar, + (uint16_t*) details->DeviceID, + sizeof(details->DeviceID) + ); + } + } + else + { + name = SDL_GetAudioDeviceName(devs[index - 1]); + details->Role = FAudioNotDefaultDevice; + } + FAudio_UTF8_To_UTF16( + name, + (uint16_t*) details->DisplayName, + sizeof(details->DisplayName) + ); + + /* Environment variables take precedence over all possible values */ + envvar = SDL_getenv("SDL_AUDIO_FREQUENCY"); + if (envvar != NULL) + { + rate = SDL_atoi(envvar); + } + else + { + rate = 0; + } + envvar = SDL_getenv("SDL_AUDIO_CHANNELS"); + if (envvar != NULL) + { + channels = SDL_atoi(envvar); + } + else + { + channels = 0; + } + + /* Get the device format from the OS */ + if (index == 0) + { + if (!SDL_GetAudioDeviceFormat(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, NULL)) + { + SDL_zero(spec); + } + } + else + { + if (!SDL_GetAudioDeviceFormat(devs[index - 1], &spec, NULL)) + { + SDL_zero(spec); + } + } + SDL_free(devs); + if ((spec.freq > 0) && (rate <= 0)) + { + rate = spec.freq; + } + if ((spec.channels > 0) && (spec.channels < 9) && (channels <= 0)) + { + channels = spec.channels; + } + + /* If we make it all the way here with no format, hardcode a sane one */ + if (rate <= 0) + { + rate = 48000; + } + if (channels <= 0) + { + channels = 2; + } + + /* Write the format, finally. */ + WriteWaveFormatExtensible( + &details->OutputFormat, + channels, + rate, + &DATAFORMAT_SUBTYPE_PCM + ); + return 0; +} + +/* Threading */ + +FAudioThread FAudio_PlatformCreateThread( + FAudioThreadFunc func, + const char *name, + void* data +) { + return (FAudioThread) SDL_CreateThread( + (SDL_ThreadFunction) func, + name, + data + ); +} + +void FAudio_PlatformWaitThread(FAudioThread thread, int32_t *retval) +{ + SDL_WaitThread((SDL_Thread*) thread, retval); +} + +void FAudio_PlatformThreadPriority(FAudioThreadPriority priority) +{ + SDL_SetCurrentThreadPriority((SDL_ThreadPriority) priority); +} + +uint64_t FAudio_PlatformGetThreadID(void) +{ + return (uint64_t) SDL_GetCurrentThreadID(); +} + +FAudioMutex FAudio_PlatformCreateMutex() +{ + return (FAudioMutex) SDL_CreateMutex(); +} + +void FAudio_PlatformDestroyMutex(FAudioMutex mutex) +{ + SDL_DestroyMutex((SDL_Mutex*) mutex); +} + +void FAudio_PlatformLockMutex(FAudioMutex mutex) +{ + SDL_LockMutex((SDL_Mutex*) mutex); +} + +void FAudio_PlatformUnlockMutex(FAudioMutex mutex) +{ + SDL_UnlockMutex((SDL_Mutex*) mutex); +} + +void FAudio_sleep(uint32_t ms) +{ + SDL_Delay(ms); +} + +/* Time */ + +uint32_t FAudio_timems() +{ + return SDL_GetTicks(); +} + +/* FAudio I/O */ + +static size_t FAUDIOCALL FAudio_INTERNAL_ioread( + void *data, + void *dst, + size_t size, + size_t count +) { + return SDL_ReadIO((SDL_IOStream*) data, dst, size * count) / size; +} + +static int64_t FAUDIOCALL FAudio_INTERNAL_ioseek( + void *data, + int64_t offset, + int whence +) { + return SDL_SeekIO((SDL_IOStream*) data, offset, whence); +} + +static int FAUDIOCALL FAudio_INTERNAL_ioclose( + void *data +) { + return SDL_CloseIO((SDL_IOStream*) data); +} + +FAudioIOStream* FAudio_fopen(const char *path) +{ + FAudioIOStream *io = (FAudioIOStream*) FAudio_malloc( + sizeof(FAudioIOStream) + ); + SDL_IOStream *stream = SDL_IOFromFile(path, "rb"); + io->data = stream; + io->read = FAudio_INTERNAL_ioread; + io->seek = FAudio_INTERNAL_ioseek; + io->close = FAudio_INTERNAL_ioclose; + io->lock = FAudio_PlatformCreateMutex(); + return io; +} + +FAudioIOStream* FAudio_memopen(void *mem, int len) +{ + FAudioIOStream *io = (FAudioIOStream*) FAudio_malloc( + sizeof(FAudioIOStream) + ); + SDL_IOStream *stream = SDL_IOFromMem(mem, len); + io->data = stream; + io->read = FAudio_INTERNAL_ioread; + io->seek = FAudio_INTERNAL_ioseek; + io->close = FAudio_INTERNAL_ioclose; + io->lock = FAudio_PlatformCreateMutex(); + return io; +} + +uint8_t* FAudio_memptr(FAudioIOStream *io, size_t offset) +{ + SDL_PropertiesID props = SDL_GetIOProperties((SDL_IOStream*) io->data); + FAudio_assert(SDL_HasProperty(props, SDL_PROP_IOSTREAM_MEMORY_POINTER)); + return ((uint8_t*) SDL_GetPointerProperty(props, SDL_PROP_IOSTREAM_MEMORY_POINTER, NULL)) + offset; +} + +void FAudio_close(FAudioIOStream *io) +{ + io->close(io->data); + FAudio_PlatformDestroyMutex((FAudioMutex) io->lock); + FAudio_free(io); +} + +#ifdef FAUDIO_DUMP_VOICES +static size_t FAUDIOCALL FAudio_INTERNAL_iowrite( + void *data, + const void *src, + size_t size, + size_t count +) { + SDL_WriteIO((SDL_IOStream*) data, src, size * count); +} + +static size_t FAUDIOCALL FAudio_INTERNAL_iosize( + void *data +) { + return SDL_GetIOSize((SDL_IOStream*) data); +} + +FAudioIOStreamOut* FAudio_fopen_out(const char *path, const char *mode) +{ + FAudioIOStreamOut *io = (FAudioIOStreamOut*) FAudio_malloc( + sizeof(FAudioIOStreamOut) + ); + SDL_IOStream *stream = SDL_IOFromFile(path, mode); + io->data = stream; + io->read = FAudio_INTERNAL_ioread; + io->write = FAudio_INTERNAL_iowrite; + io->seek = FAudio_INTERNAL_ioseek; + io->size = FAudio_INTERNAL_iosize; + io->close = FAudio_INTERNAL_ioclose; + io->lock = FAudio_PlatformCreateMutex(); + return io; +} + +void FAudio_close_out(FAudioIOStreamOut *io) +{ + io->close(io->data); + FAudio_PlatformDestroyMutex((FAudioMutex) io->lock); + FAudio_free(io); +} +#endif /* FAUDIO_DUMP_VOICES */ + +/* UTF8->UTF16 Conversion, taken from PhysicsFS */ + +#define UNICODE_BOGUS_CHAR_VALUE 0xFFFFFFFF +#define UNICODE_BOGUS_CHAR_CODEPOINT '?' + +static uint32_t FAudio_UTF8_CodePoint(const char **_str) +{ + const char *str = *_str; + uint32_t retval = 0; + uint32_t octet = (uint32_t) ((uint8_t) *str); + uint32_t octet2, octet3, octet4; + + if (octet == 0) /* null terminator, end of string. */ + return 0; + + else if (octet < 128) /* one octet char: 0 to 127 */ + { + (*_str)++; /* skip to next possible start of codepoint. */ + return octet; + } /* else if */ + + else if ((octet > 127) && (octet < 192)) /* bad (starts with 10xxxxxx). */ + { + /* + * Apparently each of these is supposed to be flagged as a bogus + * char, instead of just resyncing to the next valid codepoint. + */ + (*_str)++; /* skip to next possible start of codepoint. */ + return UNICODE_BOGUS_CHAR_VALUE; + } /* else if */ + + else if (octet < 224) /* two octets */ + { + (*_str)++; /* advance at least one byte in case of an error */ + octet -= (128+64); + octet2 = (uint32_t) ((uint8_t) *(++str)); + if ((octet2 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + *_str += 1; /* skip to next possible start of codepoint. */ + retval = ((octet << 6) | (octet2 - 128)); + if ((retval >= 0x80) && (retval <= 0x7FF)) + return retval; + } /* else if */ + + else if (octet < 240) /* three octets */ + { + (*_str)++; /* advance at least one byte in case of an error */ + octet -= (128+64+32); + octet2 = (uint32_t) ((uint8_t) *(++str)); + if ((octet2 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet3 = (uint32_t) ((uint8_t) *(++str)); + if ((octet3 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + *_str += 2; /* skip to next possible start of codepoint. */ + retval = ( ((octet << 12)) | ((octet2-128) << 6) | ((octet3-128)) ); + + /* There are seven "UTF-16 surrogates" that are illegal in UTF-8. */ + switch (retval) + { + case 0xD800: + case 0xDB7F: + case 0xDB80: + case 0xDBFF: + case 0xDC00: + case 0xDF80: + case 0xDFFF: + return UNICODE_BOGUS_CHAR_VALUE; + } /* switch */ + + /* 0xFFFE and 0xFFFF are illegal, too, so we check them at the edge. */ + if ((retval >= 0x800) && (retval <= 0xFFFD)) + return retval; + } /* else if */ + + else if (octet < 248) /* four octets */ + { + (*_str)++; /* advance at least one byte in case of an error */ + octet -= (128+64+32+16); + octet2 = (uint32_t) ((uint8_t) *(++str)); + if ((octet2 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet3 = (uint32_t) ((uint8_t) *(++str)); + if ((octet3 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet4 = (uint32_t) ((uint8_t) *(++str)); + if ((octet4 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + *_str += 3; /* skip to next possible start of codepoint. */ + retval = ( ((octet << 18)) | ((octet2 - 128) << 12) | + ((octet3 - 128) << 6) | ((octet4 - 128)) ); + if ((retval >= 0x10000) && (retval <= 0x10FFFF)) + return retval; + } /* else if */ + + /* + * Five and six octet sequences became illegal in rfc3629. + * We throw the codepoint away, but parse them to make sure we move + * ahead the right number of bytes and don't overflow the buffer. + */ + + else if (octet < 252) /* five octets */ + { + (*_str)++; /* advance at least one byte in case of an error */ + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + *_str += 4; /* skip to next possible start of codepoint. */ + return UNICODE_BOGUS_CHAR_VALUE; + } /* else if */ + + else /* six octets */ + { + (*_str)++; /* advance at least one byte in case of an error */ + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + *_str += 6; /* skip to next possible start of codepoint. */ + return UNICODE_BOGUS_CHAR_VALUE; + } /* else if */ + + return UNICODE_BOGUS_CHAR_VALUE; +} + +void FAudio_UTF8_To_UTF16(const char *src, uint16_t *dst, size_t len) +{ + len -= sizeof (uint16_t); /* save room for null char. */ + while (len >= sizeof (uint16_t)) + { + uint32_t cp = FAudio_UTF8_CodePoint(&src); + if (cp == 0) + break; + else if (cp == UNICODE_BOGUS_CHAR_VALUE) + cp = UNICODE_BOGUS_CHAR_CODEPOINT; + + if (cp > 0xFFFF) /* encode as surrogate pair */ + { + if (len < (sizeof (uint16_t) * 2)) + break; /* not enough room for the pair, stop now. */ + + cp -= 0x10000; /* Make this a 20-bit value */ + + *(dst++) = 0xD800 + ((cp >> 10) & 0x3FF); + len -= sizeof (uint16_t); + + cp = 0xDC00 + (cp & 0x3FF); + } /* if */ + + *(dst++) = cp; + len -= sizeof (uint16_t); + } /* while */ + + *dst = 0; +} + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ + +#else + +extern int this_tu_is_empty; + +#endif /* FAUDIO_SDL3_PLATFORM */ diff --git a/WickedEngine/Utility/FAudio/src/FAudio_platform_win32.c b/WickedEngine/Utility/FAudio/src/FAudio_platform_win32.c index e1c69bb9e6..9e90a21199 100644 --- a/WickedEngine/Utility/FAudio/src/FAudio_platform_win32.c +++ b/WickedEngine/Utility/FAudio/src/FAudio_platform_win32.c @@ -41,8 +41,17 @@ #include #include #include +#include DEFINE_GUID(CLSID_CWMADecMediaObject, 0x2eeb4adf, 0x4578, 0x4d10, 0xbc, 0xa7, 0xbb, 0x95, 0x5f, 0x56, 0x32, 0x0a); + +#ifdef _MSC_VER +DEFINE_GUID(IID_IAudioClient, 0x1CB9AD4C, 0xDBFA, 0x4c32, 0xB1, 0x78, 0xC2, 0xF5, 0x68, 0xA7, 0x03, 0xB2); +DEFINE_GUID(IID_IAudioRenderClient, 0xF294ACFC, 0x3146, 0x4483, 0xA7, 0xBF, 0xAD, 0xDC, 0xA7, 0xC2, 0x60, 0xE2); +DEFINE_GUID(IID_IMMDeviceEnumerator, 0xA95664D2, 0x9614, 0x4F35, 0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, 0x17, 0xE6); +DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xBCDE0395, 0xE52F, 0x467C, 0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E); +#endif + DEFINE_MEDIATYPE_GUID(MFAudioFormat_XMAudio2, FAUDIO_FORMAT_XMAUDIO2); static CRITICAL_SECTION faudio_cs = { NULL, -1, 0, 0, 0, 0 }; @@ -70,6 +79,44 @@ void FAudio_Log(char const *msg) OutputDebugStringA(msg); } +static HMODULE kernelbase = NULL; +static HRESULT (WINAPI *my_SetThreadDescription)(HANDLE, PCWSTR) = NULL; + +static void FAudio_resolve_SetThreadDescription(void) +{ + kernelbase = LoadLibraryA("kernelbase.dll"); + if (!kernelbase) + return; + + my_SetThreadDescription = (HRESULT (WINAPI *)(HANDLE, PCWSTR)) GetProcAddress(kernelbase, "SetThreadDescription"); + if (!my_SetThreadDescription) + { + FreeLibrary(kernelbase); + kernelbase = NULL; + } +} + +static void FAudio_set_thread_name(char const *name) +{ + int ret; + WCHAR *nameW; + + if (!my_SetThreadDescription) + return; + + ret = MultiByteToWideChar(CP_UTF8, 0, name, -1, NULL, 0); + + nameW = FAudio_malloc(ret * sizeof(WCHAR)); + if (!nameW) + return; + + ret = MultiByteToWideChar(CP_UTF8, 0, name, -1, nameW, ret); + if (ret) + my_SetThreadDescription(GetCurrentThread(), nameW); + + FAudio_free(nameW); +} + static HRESULT FAudio_FillAudioClientBuffer( struct FAudioAudioClientThreadArgs *args, IAudioRenderClient *client, @@ -121,6 +168,8 @@ static DWORD WINAPI FAudio_AudioClientThread(void *user) HRESULT hr = S_OK; UINT frames, padding = 0; + FAudio_set_thread_name(__func__); + hr = IAudioClient_GetService( args->client, &IID_IAudioRenderClient, @@ -140,6 +189,11 @@ static DWORD WINAPI FAudio_AudioClientThread(void *user) while (WaitForMultipleObjects(2, args->events, FALSE, INFINITE) == WAIT_OBJECT_0) { hr = IAudioClient_GetCurrentPadding(args->client, &padding); + if (hr == AUDCLNT_E_DEVICE_INVALIDATED) + { + /* Device was removed, just exit */ + break; + } FAudio_assert(!FAILED(hr) && "Failed to get IAudioClient current padding!"); hr = FAudio_FillAudioClientBuffer(args, render_client, frames, padding); @@ -154,6 +208,141 @@ static DWORD WINAPI FAudio_AudioClientThread(void *user) return 0; } +/* Sets `defaultDeviceIndex` to the default audio device index in + * `deviceCollection`. + * On failure, `defaultDeviceIndex` is not modified and the latest error is + * returned. */ +static HRESULT FAudio_DefaultDeviceIndex( + IMMDeviceCollection *deviceCollection, + uint32_t* defaultDeviceIndex +) { + IMMDevice *device; + HRESULT hr; + uint32_t i, count; + WCHAR *default_guid; + WCHAR *device_guid; + + /* Open the default device and get its GUID. */ + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint( + device_enumerator, + eRender, + eConsole, + &device + ); + if (FAILED(hr)) + { + return hr; + } + hr = IMMDevice_GetId(device, &default_guid); + if (FAILED(hr)) + { + IMMDevice_Release(device); + return hr; + } + + /* Free the default device. */ + IMMDevice_Release(device); + + hr = IMMDeviceCollection_GetCount(deviceCollection, &count); + if (FAILED(hr)) + { + CoTaskMemFree(default_guid); + return hr; + } + + for (i = 0; i < count; i += 1) + { + /* Open the device and get its GUID. */ + hr = IMMDeviceCollection_Item(deviceCollection, i, &device); + if (FAILED(hr)) { + CoTaskMemFree(default_guid); + return hr; + } + hr = IMMDevice_GetId(device, &device_guid); + if (FAILED(hr)) + { + CoTaskMemFree(default_guid); + IMMDevice_Release(device); + return hr; + } + + if (lstrcmpW(default_guid, device_guid) == 0) + { + /* Device found. */ + CoTaskMemFree(default_guid); + CoTaskMemFree(device_guid); + IMMDevice_Release(device); + *defaultDeviceIndex = i; + return S_OK; + } + + CoTaskMemFree(device_guid); + IMMDevice_Release(device); + } + + /* This should probably never happen. Just in case, set + * `defaultDeviceIndex` to 0 and return S_OK. */ + CoTaskMemFree(default_guid); + *defaultDeviceIndex = 0; + return S_OK; +} + +/* Open `device`, corresponding to `deviceIndex`. `deviceIndex` 0 always + * corresponds to the default device. XAudio reorders the devices so that the + * default device is always at index 0, so we mimick this behavior here by + * swapping the devices at indexes 0 and `defaultDeviceIndex`. + */ +static HRESULT FAudio_OpenDevice(uint32_t deviceIndex, IMMDevice **device) +{ + IMMDeviceCollection *deviceCollection; + HRESULT hr; + uint32_t defaultDeviceIndex; + uint32_t actualIndex; + + *device = NULL; + + hr = IMMDeviceEnumerator_EnumAudioEndpoints( + device_enumerator, + eRender, + DEVICE_STATE_ACTIVE, + &deviceCollection + ); + if (FAILED(hr)) + { + return hr; + } + + /* Get the default device index. */ + hr = FAudio_DefaultDeviceIndex(deviceCollection, &defaultDeviceIndex); + if (FAILED(hr)) + { + IMMDeviceCollection_Release(deviceCollection); + return hr; + } + + if (deviceIndex == 0) { + /* Default device. */ + actualIndex = defaultDeviceIndex; + } else if (deviceIndex == defaultDeviceIndex) { + /* Open the device at index 0 instead of the "correct" one. */ + actualIndex = 0; + } else { + /* Otherwise, just open the device. */ + actualIndex = deviceIndex; + + } + hr = IMMDeviceCollection_Item(deviceCollection, actualIndex, device); + if (FAILED(hr)) + { + IMMDeviceCollection_Release(deviceCollection); + return hr; + } + + IMMDeviceCollection_Release(deviceCollection); + + return hr; +} + void FAudio_PlatformInit( FAudio *audio, uint32_t flags, @@ -170,13 +359,19 @@ void FAudio_PlatformInit( HRESULT hr; HANDLE audioEvent = NULL; BOOL has_sse2 = IsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE); - - FAudio_INTERNAL_InitSIMDFunctions(has_sse2, FALSE); +#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm64ec__) || defined(_M_ARM64EC) + BOOL has_neon = TRUE; +#elif defined(__arm__) || defined(_M_ARM) + BOOL has_neon = IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE); +#else + BOOL has_neon = FALSE; +#endif + FAudio_INTERNAL_InitSIMDFunctions(has_sse2, has_neon); + FAudio_resolve_SetThreadDescription(); FAudio_PlatformAddRef(); *platformDevice = NULL; - if (deviceIndex > 0) return; args = FAudio_malloc(sizeof(*args)); FAudio_assert(!!args && "Failed to allocate FAudio thread args!"); @@ -210,13 +405,8 @@ void FAudio_PlatformInit( data->stopEvent = CreateEventW(NULL, FALSE, FALSE, NULL); FAudio_assert(!!data->stopEvent && "Failed to create FAudio thread stop event!"); - hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint( - device_enumerator, - eRender, - eConsole, - &device - ); - FAudio_assert(!FAILED(hr) && "Failed to get default audio endpoint!"); + hr = FAudio_OpenDevice(deviceIndex, &device); + FAudio_assert(!FAILED(hr) && "Failed to get audio device!"); hr = IMMDevice_Activate( device, @@ -228,8 +418,8 @@ void FAudio_PlatformInit( FAudio_assert(!FAILED(hr) && "Failed to create audio client!"); IMMDevice_Release(device); - if (flags & FAUDIO_1024_QUANTUM) duration = 21330; - else duration = 30000; + if (flags & FAUDIO_1024_QUANTUM) duration = 213333; + else duration = 100000; hr = IAudioClient_IsFormatSupported( data->client, @@ -250,7 +440,7 @@ void FAudio_PlatformInit( data->client, AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - duration, + duration * 3, 0, &args->format.Format, &GUID_NULL @@ -287,7 +477,8 @@ void FAudio_PlatformInit( args->events[0] = audioEvent; args->events[1] = data->stopEvent; args->audio = audio; - args->updateSize = args->format.Format.nSamplesPerSec / 100; + if (flags & FAUDIO_1024_QUANTUM) args->updateSize = args->format.Format.nSamplesPerSec / (1000.0 / (64.0 / 3.0)); + else args->updateSize = args->format.Format.nSamplesPerSec / 100; data->audioThread = CreateThread(NULL, 0, &FAudio_AudioClientThread, args, 0, NULL); FAudio_assert(!!data->audioThread && "Failed to create audio client thread!"); @@ -304,6 +495,12 @@ void FAudio_PlatformQuit(void* platformDevice) SetEvent(data->stopEvent); WaitForSingleObject(data->audioThread, INFINITE); if (data->client) IAudioClient_Release(data->client); + if (kernelbase) + { + my_SetThreadDescription = NULL; + FreeLibrary(kernelbase); + kernelbase = NULL; + } FAudio_PlatformRelease(); } @@ -340,57 +537,89 @@ void FAudio_PlatformRelease() uint32_t FAudio_PlatformGetDeviceCount(void) { - IMMDevice *device; + IMMDeviceCollection *device_collection; uint32_t count; HRESULT hr; FAudio_PlatformAddRef(); - hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint( + + hr = IMMDeviceEnumerator_EnumAudioEndpoints( device_enumerator, eRender, - eConsole, - &device + DEVICE_STATE_ACTIVE, + &device_collection ); - FAudio_assert(!FAILED(hr) && "Failed to get default audio endpoint!"); + if (FAILED(hr)) { + FAudio_PlatformRelease(); + return 0; + } + + hr = IMMDeviceCollection_GetCount(device_collection, &count); + if (FAILED(hr)) { + IMMDeviceCollection_Release(device_collection); + FAudio_PlatformRelease(); + return 0; + } + + IMMDeviceCollection_Release(device_collection); - IMMDevice_Release(device); FAudio_PlatformRelease(); - return 1; + return count; } uint32_t FAudio_PlatformGetDeviceDetails( uint32_t index, FAudioDeviceDetails *details ) { + WAVEFORMATEX *format, *obtained; WAVEFORMATEXTENSIBLE *ext; - WAVEFORMATEX *format; IAudioClient *client; IMMDevice *device; + IPropertyStore* properties; + PROPVARIANT deviceName; + uint32_t count = 0; uint32_t ret = 0; HRESULT hr; WCHAR *str; + GUID sub; FAudio_memset(details, 0, sizeof(FAudioDeviceDetails)); - if (index > 0) return FAUDIO_E_INVALID_CALL; FAudio_PlatformAddRef(); - hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint( - device_enumerator, - eRender, - eConsole, - &device - ); - FAudio_assert(!FAILED(hr) && "Failed to get default audio endpoint!"); + count = FAudio_PlatformGetDeviceCount(); + if (index >= count) + { + FAudio_PlatformRelease(); + return FAUDIO_E_INVALID_CALL; + } + + hr = FAudio_OpenDevice(index, &device); + FAudio_assert(!FAILED(hr) && "Failed to get audio endpoint!"); + + if (index == 0) + { + details->Role = FAudioGlobalDefaultDevice; + } + else + { + details->Role = FAudioNotDefaultDevice; + } - details->Role = FAudioGlobalDefaultDevice; + /* Set the Device Display Name */ + hr = IMMDevice_OpenPropertyStore(device, STGM_READ, &properties); + FAudio_assert(!FAILED(hr) && "Failed to open device property store!"); + hr = IPropertyStore_GetValue(properties, (PROPERTYKEY*)&DEVPKEY_Device_FriendlyName, &deviceName); + FAudio_assert(!FAILED(hr) && "Failed to get audio device friendly name!"); + lstrcpynW((LPWSTR)details->DisplayName, deviceName.pwszVal, ARRAYSIZE(details->DisplayName) - 1); + PropVariantClear(&deviceName); + IPropertyStore_Release(properties); + /* Set the Device ID */ hr = IMMDevice_GetId(device, &str); FAudio_assert(!FAILED(hr) && "Failed to get audio endpoint id!"); - - lstrcpynW(details->DeviceID, str, ARRAYSIZE(details->DeviceID) - 1); - lstrcpynW(details->DisplayName, str, ARRAYSIZE(details->DisplayName) - 1); + lstrcpynW((LPWSTR)details->DeviceID, str, ARRAYSIZE(details->DeviceID) - 1); CoTaskMemFree(str); hr = IMMDevice_Activate( @@ -405,6 +634,28 @@ uint32_t FAudio_PlatformGetDeviceDetails( hr = IAudioClient_GetMixFormat(client, &format); FAudio_assert(!FAILED(hr) && "Failed to get audio client mix format!"); + if (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) + { + ext = (WAVEFORMATEXTENSIBLE *)format; + sub = ext->SubFormat; + FAudio_memcpy( + &ext->SubFormat, + &DATAFORMAT_SUBTYPE_PCM, + sizeof(GUID) + ); + + hr = IAudioClient_IsFormatSupported(client, AUDCLNT_SHAREMODE_SHARED, format, &obtained); + if (FAILED(hr)) + { + ext->SubFormat = sub; + } + else if (obtained) + { + CoTaskMemFree(format); + format = obtained; + } + } + details->OutputFormat.Format.wFormatTag = format->wFormatTag; details->OutputFormat.Format.nChannels = format->nChannels; details->OutputFormat.Format.nSamplesPerSec = format->nSamplesPerSec; @@ -424,6 +675,12 @@ uint32_t FAudio_PlatformGetDeviceDetails( sizeof(GUID) ); } + else + { + details->OutputFormat.dwChannelMask = GetMask(format->nChannels); + } + + CoTaskMemFree(format); IAudioClient_Release(client); @@ -474,6 +731,7 @@ static DWORD WINAPI FaudioThreadWrapper(void *user) struct FAudioThreadArgs *args = user; DWORD ret; + FAudio_set_thread_name(args->name); ret = args->func(args->data); FAudio_free(args); @@ -498,7 +756,7 @@ FAudioThread FAudio_PlatformCreateThread( void FAudio_PlatformWaitThread(FAudioThread thread, int32_t *retval) { WaitForSingleObject(thread, INFINITE); - GetExitCodeThread(thread, (DWORD *)retval); + if (retval != NULL) GetExitCodeThread(thread, (DWORD *)retval); } void FAudio_PlatformThreadPriority(FAudioThreadPriority priority) @@ -622,6 +880,7 @@ static int FAUDIOCALL FAudio_mem_close(void *data) { if (!data) return 0; FAudio_free(data); + return 0; } FAudioIOStream* FAudio_memopen(void *mem, int len) @@ -962,7 +1221,7 @@ FAUDIOAPI uint32_t XNA_GetSongEnded() return 1; } FAudioSourceVoice_GetState(songVoice, &state, 0); - return state.BuffersQueued == 0; + return state.BuffersQueued == 0 && state.SamplesPlayed == 0; } FAUDIOAPI void XNA_EnableVisualization(uint32_t enable) @@ -998,7 +1257,7 @@ struct FAudioWMADEC size_t input_size; }; -static BOOL FAudio_WMAMF_ProcessInput( +static HRESULT FAudio_WMAMF_ProcessInput( FAudioVoice *voice, FAudioBuffer *buffer ) { @@ -1010,7 +1269,7 @@ static BOOL FAudio_WMAMF_ProcessInput( HRESULT hr; copy_size = min(buffer->AudioBytes - impl->input_pos, impl->input_size); - if (!copy_size) return FALSE; + if (!copy_size) return S_FALSE; LOG_INFO(voice->audio, "pushing %x bytes at %x", copy_size, impl->input_pos); hr = MFCreateSample(&sample); @@ -1036,15 +1295,18 @@ static BOOL FAudio_WMAMF_ProcessInput( hr = IMFTransform_ProcessInput(impl->decoder, 0, sample, 0); IMFSample_Release(sample); - if (hr == MF_E_NOTACCEPTING) return TRUE; - if (FAILED(hr)) LOG_ERROR(voice->audio, "IMFTransform_ProcessInput returned %#x", hr); - FAudio_assert(!FAILED(hr) || !"Failed to process input sample!"); + if (hr == MF_E_NOTACCEPTING) return S_OK; + if (FAILED(hr)) + { + LOG_ERROR(voice->audio, "IMFTransform_ProcessInput returned %#x", hr); + return hr; + } impl->input_pos += copy_size; - return TRUE; + return S_OK; }; -static BOOL FAudio_WMAMF_ProcessOutput( +static HRESULT FAudio_WMAMF_ProcessOutput( FAudioVoice *voice, FAudioBuffer *buffer ) { @@ -1060,9 +1322,12 @@ static BOOL FAudio_WMAMF_ProcessOutput( FAudio_memset(&output, 0, sizeof(output)); output.pSample = impl->output_sample; hr = IMFTransform_ProcessOutput(impl->decoder, 0, 1, &output, &status); - if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) return FALSE; - if (FAILED(hr)) LOG_ERROR(voice->audio, "IMFTransform_ProcessInput returned %#x", hr); - FAudio_assert(!FAILED(hr) && "Failed to process output sample!"); + if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) return S_FALSE; + if (FAILED(hr)) + { + LOG_ERROR(voice->audio, "IMFTransform_ProcessInput returned %#x", hr); + return hr; + } if (output.dwStatus & MFT_OUTPUT_DATA_BUFFER_NO_SAMPLE) continue; @@ -1100,7 +1365,7 @@ static BOOL FAudio_WMAMF_ProcessOutput( if (!impl->output_sample) IMFSample_Release(output.pSample); } - return TRUE; + return S_OK; }; static void FAudio_INTERNAL_DecodeWMAMF( @@ -1110,8 +1375,8 @@ static void FAudio_INTERNAL_DecodeWMAMF( uint32_t samples ) { const FAudioWaveFormatExtensible *wfx = (FAudioWaveFormatExtensible *)voice->src.format; + size_t samples_pos, samples_size, copy_size = 0; struct FAudioWMADEC *impl = voice->src.wmadec; - size_t samples_pos, samples_size, copy_size; HRESULT hr; LOG_FUNC_ENTER(voice->audio) @@ -1163,8 +1428,14 @@ static void FAudio_INTERNAL_DecodeWMAMF( while (impl->output_pos < samples_pos + samples_size) { - if (FAudio_WMAMF_ProcessOutput(voice, buffer)) continue; - if (FAudio_WMAMF_ProcessInput(voice, buffer)) continue; + hr = FAudio_WMAMF_ProcessOutput(voice, buffer); + if (FAILED(hr)) goto error; + if (hr == S_OK) continue; + + hr = FAudio_WMAMF_ProcessInput(voice, buffer); + if (FAILED(hr)) goto error; + if (hr == S_OK) continue; + if (!impl->input_size) break; LOG_INFO(voice->audio, "sending EOS to %p", impl->decoder); @@ -1177,8 +1448,12 @@ static void FAudio_INTERNAL_DecodeWMAMF( impl->input_size = 0; } - copy_size = FAudio_clamp(impl->output_pos - samples_pos, 0, samples_size); - FAudio_memcpy(decodeCache, impl->output_buf + samples_pos, copy_size); + if (impl->output_pos > samples_pos) + { + copy_size = FAudio_min(impl->output_pos - samples_pos, samples_size); + FAudio_memcpy(decodeCache, impl->output_buf + samples_pos, copy_size); + } + FAudio_zero(decodeCache + copy_size, samples_size - copy_size); LOG_INFO( voice->audio, "decoded %x / %x bytes, copied %x / %x bytes", @@ -1189,11 +1464,17 @@ static void FAudio_INTERNAL_DecodeWMAMF( ); LOG_FUNC_EXIT(voice->audio) + return; + +error: + FAudio_zero(decodeCache, samples * voice->src.format->nChannels * sizeof(float)); + LOG_FUNC_EXIT(voice->audio) } uint32_t FAudio_WMADEC_init(FAudioSourceVoice *voice, uint32_t type) { static const uint8_t fake_codec_data[16] = {0, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t fake_codec_data_wma3[18] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 0}; const FAudioWaveFormatExtensible *wfx = (FAudioWaveFormatExtensible *)voice->src.format; struct FAudioWMADEC *impl; MFT_OUTPUT_STREAM_INFO info = {0}; @@ -1206,7 +1487,7 @@ uint32_t FAudio_WMADEC_init(FAudioSourceVoice *voice, uint32_t type) LOG_FUNC_ENTER(voice->audio) - if (!(impl = voice->audio->pMalloc(sizeof(*impl)))) return 0; + if (!(impl = voice->audio->pMalloc(sizeof(*impl)))) return -1; FAudio_memset(impl, 0, sizeof(*impl)); hr = CoCreateInstance( @@ -1216,7 +1497,11 @@ uint32_t FAudio_WMADEC_init(FAudioSourceVoice *voice, uint32_t type) &IID_IMFTransform, (void **)&decoder ); - FAudio_assert(!FAILED(hr) && "Failed to create decoder!"); + if (FAILED(hr)) + { + voice->audio->pFree(impl->output_buf); + return -2; + } hr = MFCreateMediaType(&media_type); FAudio_assert(!FAILED(hr) && "Failed create media type!"); @@ -1251,11 +1536,17 @@ uint32_t FAudio_WMADEC_init(FAudioSourceVoice *voice, uint32_t type) FAudio_assert(!FAILED(hr) && "Failed set input block align!"); break; case FAUDIO_FORMAT_WMAUDIO3: + *(uint16_t *)fake_codec_data_wma3 = voice->src.format->wBitsPerSample; + for (i = 0; i < voice->src.format->nChannels; i++) + { + fake_codec_data_wma3[2] <<= 1; + fake_codec_data_wma3[2] |= 1; + } hr = IMFMediaType_SetBlob( media_type, &MF_MT_USER_DATA, - (void *)&wfx->Samples, - wfx->Format.cbSize + (void *)fake_codec_data_wma3, + sizeof(fake_codec_data_wma3) ); FAudio_assert(!FAILED(hr) && "Failed set codec private data!"); hr = IMFMediaType_SetGUID( diff --git a/WickedEngine/Utility/FAudio/src/XNA_Song.c b/WickedEngine/Utility/FAudio/src/XNA_Song.c index 51e178af24..73f9991056 100644 --- a/WickedEngine/Utility/FAudio/src/XNA_Song.c +++ b/WickedEngine/Utility/FAudio/src/XNA_Song.c @@ -1,6 +1,6 @@ /* FAudio - XAudio Reimplementation for FNA * - * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * Copyright (c) 2011-2024 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 @@ -33,14 +33,18 @@ #define malloc FAudio_malloc #define realloc FAudio_realloc #define free FAudio_free +#ifdef STB_MEMSET_OVERRIDE #ifdef memset /* Thanks, Apple! */ #undef memset #endif #define memset FAudio_memset +#endif /* STB_MEMSET_OVERRIDE */ +#ifdef STB_MEMCPY_OVERRIDE #ifdef memcpy /* Thanks, Apple! */ #undef memcpy #endif #define memcpy FAudio_memcpy +#endif /* STB_MEMCPY_OVERRIDE */ #define memcmp FAudio_memcmp #define pow FAudio_pow @@ -80,16 +84,27 @@ #define STB_VORBIS_NO_INTEGER_CONVERSION 1 #include "stb_vorbis.h" +#include "qoa_decoder.h" + /* Globals */ static float songVolume = 1.0f; static FAudio *songAudio = NULL; static FAudioMasteringVoice *songMaster = NULL; +static unsigned int songLength = 0; +static unsigned int songOffset = 0; static FAudioSourceVoice *songVoice = NULL; static FAudioVoiceCallback callbacks; -static stb_vorbis *activeSong = NULL; -static stb_vorbis_info activeSongInfo; +static stb_vorbis *activeVorbisSong = NULL; +static stb_vorbis_info activeVorbisSongInfo; + +static qoa *activeQoaSong = NULL; +static unsigned int qoaChannels = 0; +static unsigned int qoaSampleRate = 0; +static unsigned int qoaSamplesPerChannelPerFrame = 0; +static unsigned int qoaTotalSamplesPerChannel = 0; + static uint8_t *songCache; /* Internal Functions */ @@ -97,20 +112,35 @@ static uint8_t *songCache; static void XNA_SongSubmitBuffer(FAudioVoiceCallback *callback, void *pBufferContext) { FAudioBuffer buffer; - uint32_t decoded = stb_vorbis_get_samples_float_interleaved( - activeSong, - activeSongInfo.channels, - (float*) songCache, - activeSongInfo.sample_rate * activeSongInfo.channels - ); + uint32_t decoded = 0; + + if (activeVorbisSong != NULL) + { + decoded = stb_vorbis_get_samples_float_interleaved( + activeVorbisSong, + activeVorbisSongInfo.channels, + (float*) songCache, + activeVorbisSongInfo.sample_rate * activeVorbisSongInfo.channels + ); + buffer.AudioBytes = decoded * activeVorbisSongInfo.channels * sizeof(float); + } + else if (activeQoaSong != NULL) + { + /* TODO: decode multiple frames? */ + decoded = qoa_decode_next_frame( + activeQoaSong, + (short*) songCache + ); + buffer.AudioBytes = decoded * qoaChannels * sizeof(short); + } + if (decoded == 0) { return; } - buffer.Flags = (decoded < activeSongInfo.sample_rate) ? - FAUDIO_END_OF_STREAM : - 0; - buffer.AudioBytes = decoded * activeSongInfo.channels * sizeof(float); + + songOffset += decoded; + buffer.Flags = (songOffset >= songLength) ? FAUDIO_END_OF_STREAM : 0; buffer.pAudioData = songCache; buffer.PlayBegin = 0; buffer.PlayLength = decoded; @@ -138,10 +168,15 @@ static void XNA_SongKill() FAudio_free(songCache); songCache = NULL; } - if (activeSong != NULL) + if (activeVorbisSong != NULL) { - stb_vorbis_close(activeSong); - activeSong = NULL; + stb_vorbis_close(activeVorbisSong); + activeVorbisSong = NULL; + } + if (activeQoaSong != NULL) + { + qoa_close(activeQoaSong); + activeQoaSong = NULL; } } @@ -173,17 +208,45 @@ FAUDIOAPI float XNA_PlaySong(const char *name) FAudioWaveFormatEx format; XNA_SongKill(); - activeSong = stb_vorbis_open_filename(name, NULL, NULL); + activeVorbisSong = stb_vorbis_open_filename(name, NULL, NULL); - /* Set format info */ - activeSongInfo = stb_vorbis_get_info(activeSong); - format.wFormatTag = FAUDIO_FORMAT_IEEE_FLOAT; - format.nChannels = activeSongInfo.channels; - format.nSamplesPerSec = activeSongInfo.sample_rate; - format.wBitsPerSample = sizeof(float) * 8; - format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8; - format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; - format.cbSize = 0; + if (activeVorbisSong != NULL) + { + activeVorbisSongInfo = stb_vorbis_get_info(activeVorbisSong); + format.wFormatTag = FAUDIO_FORMAT_IEEE_FLOAT; + format.nChannels = activeVorbisSongInfo.channels; + format.nSamplesPerSec = activeVorbisSongInfo.sample_rate; + format.wBitsPerSample = sizeof(float) * 8; + format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8; + format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; + format.cbSize = 0; + + songOffset = 0; + songLength = stb_vorbis_stream_length_in_samples(activeVorbisSong); + } + else /* It's not vorbis, try qoa!*/ + { + activeQoaSong = qoa_open_from_filename(name); + + if (activeQoaSong == NULL) + { + /* It's neither vorbis nor qoa, time to bail */ + return 0; + } + + qoa_attributes(activeQoaSong, &qoaChannels, &qoaSampleRate, &qoaSamplesPerChannelPerFrame, &qoaTotalSamplesPerChannel); + + format.wFormatTag = FAUDIO_FORMAT_PCM; + format.nChannels = qoaChannels; + format.nSamplesPerSec = qoaSampleRate; + format.wBitsPerSample = 16; + format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8; + format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; + format.cbSize = 0; + + songOffset = 0; + songLength = qoaTotalSamplesPerChannel; + } /* Allocate decode cache */ songCache = (uint8_t*) FAudio_malloc(format.nAvgBytesPerSec); @@ -204,12 +267,30 @@ FAUDIOAPI float XNA_PlaySong(const char *name) FAudioVoice_SetVolume(songVoice, songVolume, 0); /* Okay, this song is decoding now */ - stb_vorbis_seek_start(activeSong); + if (activeVorbisSong != NULL) + { + stb_vorbis_seek_start(activeVorbisSong); + } + else if (activeQoaSong != NULL) + { + qoa_seek_frame(activeQoaSong, 0); + } + XNA_SongSubmitBuffer(NULL, NULL); /* Finally. */ FAudioSourceVoice_Start(songVoice, 0, 0); - return stb_vorbis_stream_length_in_seconds(activeSong); + + if (activeVorbisSong != NULL) + { + return stb_vorbis_stream_length_in_seconds(activeVorbisSong); + } + else if (activeQoaSong != NULL) + { + return qoaTotalSamplesPerChannel / (float) qoaSampleRate; + } + + return 0; } FAUDIOAPI void XNA_PauseSong() @@ -247,12 +328,12 @@ FAUDIOAPI void XNA_SetSongVolume(float volume) FAUDIOAPI uint32_t XNA_GetSongEnded() { FAudioVoiceState state; - if (songVoice == NULL || activeSong == NULL) + if (songVoice == NULL || (activeVorbisSong == NULL && activeQoaSong == NULL)) { return 1; } FAudioSourceVoice_GetState(songVoice, &state, 0); - return state.BuffersQueued == 0; + return state.BuffersQueued == 0 && state.SamplesPlayed == 0; } FAUDIOAPI void XNA_EnableVisualization(uint32_t enable) diff --git a/WickedEngine/Utility/FAudio/src/matrix_defaults.inl b/WickedEngine/Utility/FAudio/src/matrix_defaults.inl index 2262a6a074..6f540f289d 100644 --- a/WickedEngine/Utility/FAudio/src/matrix_defaults.inl +++ b/WickedEngine/Utility/FAudio/src/matrix_defaults.inl @@ -1,6 +1,68 @@ /* This was generated by making 8 sources and 8 submixes, then assigning each * submix to each voice and dumping the output matrix. Terrible, but it worked! */ +/* +int main(int argc, char **argv) +{ + CoInitialize(NULL); + + IXAudio2 *engine; + XAudio2Create(&engine); + + IXAudio2MasteringVoice *master; + engine->CreateMasteringVoice(&master); + + FILE *fileOut = fopen("matrix_defaults.inl", "w"); + for (int srcChans = 1; srcChans < 9; srcChans += 1) + { + fprintf(fileOut, "{\n"); + for (int dstChans = 1; dstChans < 9; dstChans += 1) + { + IXAudio2SubmixVoice *submix; + engine->CreateSubmixVoice(&submix, dstChans, 48000); + + XAUDIO2_SEND_DESCRIPTOR sendDesc; + sendDesc.Flags = 0; + sendDesc.pOutputVoice = submix; + + XAUDIO2_VOICE_SENDS sends; + sends.SendCount = 1; + sends.pSends = &sendDesc; + + WAVEFORMATEX fmt; + fmt.wFormatTag = 1; + fmt.nChannels = srcChans; + fmt.nSamplesPerSec = 48000; + fmt.wBitsPerSample = 16; + fmt.nBlockAlign = srcChans * (fmt.wBitsPerSample / 8); + fmt.nAvgBytesPerSec = fmt.nBlockAlign * fmt.nSamplesPerSec; + fmt.cbSize = 0; + + IXAudio2SourceVoice *source; + engine->CreateSourceVoice(&source, &fmt, 0, 2.0f, NULL, &sends); + + float matrix[8 * 8]; + source->GetOutputMatrix(submix, srcChans, dstChans, matrix); + fprintf(fileOut, "\t{ "); + for (int i = 0; i < srcChans * dstChans; i += 1) + { + fprintf(fileOut, "%.9f%s ", matrix[i], (i == ((srcChans * dstChans) - 1)) ? "" : ","); + } + fprintf(fileOut, "}%s\n", (dstChans == 8) ? "" : ","); + + source->DestroyVoice(); + submix->DestroyVoice(); + } + fprintf(fileOut, "}%s\n", (srcChans == 8) ? "" : ","); + } + fclose(fileOut); + + master->DestroyVoice(); + engine->Release(); + CoUninitialize(); + return 0; +} +*/ { /* 1 x 1 */ { 1.000000000f }, diff --git a/WickedEngine/Utility/FAudio/src/qoa_decoder.h b/WickedEngine/Utility/FAudio/src/qoa_decoder.h new file mode 100644 index 0000000000..a64936f2e5 --- /dev/null +++ b/WickedEngine/Utility/FAudio/src/qoa_decoder.h @@ -0,0 +1,536 @@ +/* QOA decoder API for FAudio + +Copyright (c) 2024 Ethan Lee. + +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 + +Original version: + +Copyright (c) 2024, Dominic Szablewski - https://phoboslab.org +SPDX-License-Identifier: MIT + +QOA - The "Quite OK Audio" format for fast, lossy audio compression + + +-- Data Format + +QOA encodes pulse-code modulated (PCM) audio data with up to 255 channels, +sample rates from 1 up to 16777215 hertz and a bit depth of 16 bits. + +The compression method employed in QOA is lossy; it discards some information +from the uncompressed PCM data. For many types of audio signals this compression +is "transparent", i.e. the difference from the original file is often not +audible. + +QOA encodes 20 samples of 16 bit PCM data into slices of 64 bits. A single +sample therefore requires 3.2 bits of storage space, resulting in a 5x +compression (16 / 3.2). + +A QOA file consists of an 8 byte file header, followed by a number of frames. +Each frame contains an 8 byte frame header, the current 16 byte en-/decoder +state per channel and 256 slices per channel. Each slice is 8 bytes wide and +encodes 20 samples of audio data. + +All values, including the slices, are big endian. The file layout is as follows: + +struct { + struct { + char magic[4]; // magic bytes "qoaf" + uint32_t samples; // samples per channel in this file + } file_header; + + struct { + struct { + uint8_t num_channels; // no. of channels + uint24_t samplerate; // samplerate in hz + uint16_t fsamples; // samples per channel in this frame + uint16_t fsize; // frame size (includes this header) + } frame_header; + + struct { + int16_t history[4]; // most recent last + int16_t weights[4]; // most recent last + } lms_state[num_channels]; + + qoa_slice_t slices[256][num_channels]; + + } frames[ceil(samples / (256 * 20))]; +} qoa_file_t; + +Each `qoa_slice_t` contains a quantized scalefactor `sf_quant` and 20 quantized +residuals `qrNN`: + +.- QOA_SLICE -- 64 bits, 20 samples --------------------------/ /------------. +| Byte[0] | Byte[1] | Byte[2] \ \ Byte[7] | +| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | 7 6 5 / / 2 1 0 | +|------------+--------+--------+--------+---------+---------+-\ \--+---------| +| sf_quant | qr00 | qr01 | qr02 | qr03 | qr04 | / / | qr19 | +`-------------------------------------------------------------\ \------------` + +Each frame except the last must contain exactly 256 slices per channel. The last +frame may contain between 1 .. 256 (inclusive) slices per channel. The last +slice (for each channel) in the last frame may contain less than 20 samples; the +slice still must be 8 bytes wide, with the unused samples zeroed out. + +Channels are interleaved per slice. E.g. for 2 channel stereo: +slice[0] = L, slice[1] = R, slice[2] = L, slice[3] = R ... + +A valid QOA file or stream must have at least one frame. Each frame must contain +at least one channel and one sample with a samplerate between 1 .. 16777215 +(inclusive). + +If the total number of samples is not known by the encoder, the samples in the +file header may be set to 0x00000000 to indicate that the encoder is +"streaming". In a streaming context, the samplerate and number of channels may +differ from frame to frame. For static files (those with samples set to a +non-zero value), each frame must have the same number of channels and same +samplerate. + +Note that this implementation of QOA only handles files with a known total +number of samples. + +A decoder should support at least 8 channels. The channel layout for channel +counts 1 .. 8 is: + + 1. Mono + 2. L, R + 3. L, R, C + 4. FL, FR, B/SL, B/SR + 5. FL, FR, C, B/SL, B/SR + 6. FL, FR, C, LFE, B/SL, B/SR + 7. FL, FR, C, LFE, B, SL, SR + 8. FL, FR, C, LFE, BL, BR, SL, SR + +QOA predicts each audio sample based on the previously decoded ones using a +"Sign-Sign Least Mean Squares Filter" (LMS). This prediction plus the +dequantized residual forms the final output sample. + +*/ + +#define QOA_MIN_FILESIZE 16 +#define QOA_MAX_CHANNELS 8 + +#define QOA_SLICE_LEN 20 +#define QOA_SLICES_PER_FRAME 256 +#define QOA_FRAME_LEN (QOA_SLICES_PER_FRAME * QOA_SLICE_LEN) +#define QOA_LMS_LEN 4 +#define QOA_MAGIC 0x716f6166 /* 'qoaf' */ + +#define QOA_FRAME_SIZE(channels, slices) \ + (8 + QOA_LMS_LEN * 4 * channels + 8 * slices * channels) + +typedef struct { + int history[QOA_LMS_LEN]; + int weights[QOA_LMS_LEN]; +} qoa_lms_t; + +typedef struct { + unsigned int channels; + unsigned int samplerate; + unsigned int samples; + qoa_lms_t lms[QOA_MAX_CHANNELS]; + #ifdef QOA_RECORD_TOTAL_ERROR + double error; + #endif +} qoa_desc; + +typedef struct { + unsigned char *bytes; + unsigned int size; + unsigned int frame_index; + unsigned int frame_size; + unsigned short samples_per_channel_per_frame; + int free_on_close; + qoa_desc qoa; +} qoa_data; + +typedef unsigned long long qoa_uint64_t; + +typedef struct qoa qoa; + +/* NOTE: this API only supports "static" type QOA files. "streaming" type files are not supported!! */ +FAUDIOAPI qoa *qoa_open_from_memory(unsigned char *bytes, unsigned int size, int free_on_close); +FAUDIOAPI qoa *qoa_open_from_filename(const char *filename); +FAUDIOAPI void qoa_attributes(qoa *qoa, unsigned int *channels, unsigned int *samplerate, unsigned int *samples_per_channel_per_frame, unsigned int *total_samples_per_channel); +FAUDIOAPI unsigned int qoa_decode_next_frame(qoa *qoa, short *sample_data); /* decode the next frame into a preallocated buffer */ +FAUDIOAPI void qoa_seek_frame(qoa *qoa, int frame_index); +FAUDIOAPI void qoa_decode_entire(qoa *qoa, short *sample_data); /* fill a buffer with the entire qoa data decoded */ +FAUDIOAPI void qoa_close(qoa *qoa); + +/* The quant_tab provides an index into the dequant_tab for residuals in the +range of -8 .. 8. It maps this range to just 3bits and becomes less accurate at +the higher end. Note that the residual zero is identical to the lowest positive +value. This is mostly fine, since the qoa_div() function always rounds away +from zero. */ + +static const int qoa_quant_tab[17] = { + 7, 7, 7, 5, 5, 3, 3, 1, /* -8..-1 */ + 0, /* 0 */ + 0, 2, 2, 4, 4, 6, 6, 6 /* 1.. 8 */ +}; + + +/* We have 16 different scalefactors. Like the quantized residuals these become +less accurate at the higher end. In theory, the highest scalefactor that we +would need to encode the highest 16bit residual is (2**16)/8 = 8192. However we +rely on the LMS filter to predict samples accurately enough that a maximum +residual of one quarter of the 16 bit range is sufficient. I.e. with the +scalefactor 2048 times the quant range of 8 we can encode residuals up to 2**14. + +The scalefactor values are computed as: +scalefactor_tab[s] <- round(pow(s + 1, 2.75)) */ + +static const int qoa_scalefactor_tab[16] = { + 1, 7, 21, 45, 84, 138, 211, 304, 421, 562, 731, 928, 1157, 1419, 1715, 2048 +}; + + +/* The reciprocal_tab maps each of the 16 scalefactors to their rounded +reciprocals 1/scalefactor. This allows us to calculate the scaled residuals in +the encoder with just one multiplication instead of an expensive division. We +do this in .16 fixed point with integers, instead of floats. + +The reciprocal_tab is computed as: +reciprocal_tab[s] <- ((1<<16) + scalefactor_tab[s] - 1) / scalefactor_tab[s] */ + +static const int qoa_reciprocal_tab[16] = { + 65536, 9363, 3121, 1457, 781, 475, 311, 216, 156, 117, 90, 71, 57, 47, 39, 32 +}; + + +/* The dequant_tab maps each of the scalefactors and quantized residuals to +their unscaled & dequantized version. + +Since qoa_div rounds away from the zero, the smallest entries are mapped to 3/4 +instead of 1. The dequant_tab assumes the following dequantized values for each +of the quant_tab indices and is computed as: +float dqt[8] = {0.75, -0.75, 2.5, -2.5, 4.5, -4.5, 7, -7}; +dequant_tab[s][q] <- round_ties_away_from_zero(scalefactor_tab[s] * dqt[q]) + +The rounding employed here is "to nearest, ties away from zero", i.e. positive +and negative values are treated symmetrically. +*/ + +static const int qoa_dequant_tab[16][8] = { + { 1, -1, 3, -3, 5, -5, 7, -7}, + { 5, -5, 18, -18, 32, -32, 49, -49}, + { 16, -16, 53, -53, 95, -95, 147, -147}, + { 34, -34, 113, -113, 203, -203, 315, -315}, + { 63, -63, 210, -210, 378, -378, 588, -588}, + { 104, -104, 345, -345, 621, -621, 966, -966}, + { 158, -158, 528, -528, 950, -950, 1477, -1477}, + { 228, -228, 760, -760, 1368, -1368, 2128, -2128}, + { 316, -316, 1053, -1053, 1895, -1895, 2947, -2947}, + { 422, -422, 1405, -1405, 2529, -2529, 3934, -3934}, + { 548, -548, 1828, -1828, 3290, -3290, 5117, -5117}, + { 696, -696, 2320, -2320, 4176, -4176, 6496, -6496}, + { 868, -868, 2893, -2893, 5207, -5207, 8099, -8099}, + {1064, -1064, 3548, -3548, 6386, -6386, 9933, -9933}, + {1286, -1286, 4288, -4288, 7718, -7718, 12005, -12005}, + {1536, -1536, 5120, -5120, 9216, -9216, 14336, -14336}, +}; + + +/* The Least Mean Squares Filter is the heart of QOA. It predicts the next +sample based on the previous 4 reconstructed samples. It does so by continuously +adjusting 4 weights based on the residual of the previous prediction. + +The next sample is predicted as the sum of (weight[i] * history[i]). + +The adjustment of the weights is done with a "Sign-Sign-LMS" that adds or +subtracts the residual to each weight, based on the corresponding sample from +the history. This, surprisingly, is sufficient to get worthwhile predictions. + +This is all done with fixed point integers. Hence the right-shifts when updating +the weights and calculating the prediction. */ + +static int qoa_lms_predict(qoa_lms_t *lms) { + int prediction = 0; + int i; + for (i = 0; i < QOA_LMS_LEN; i++) { + prediction += lms->weights[i] * lms->history[i]; + } + return prediction >> 13; +} + +static void qoa_lms_update(qoa_lms_t *lms, int sample, int residual) { + int delta = residual >> 4; + int i; + for (i = 0; i < QOA_LMS_LEN; i++) { + lms->weights[i] += lms->history[i] < 0 ? -delta : delta; + } + + for (i = 0; i < QOA_LMS_LEN-1; i++) { + lms->history[i] = lms->history[i+1]; + } + lms->history[QOA_LMS_LEN-1] = sample; +} + + +/* qoa_div() implements a rounding division, but avoids rounding to zero for +small numbers. E.g. 0.1 will be rounded to 1. Note that 0 itself still +returns as 0, which is handled in the qoa_quant_tab[]. +qoa_div() takes an index into the .16 fixed point qoa_reciprocal_tab as an +argument, so it can do the division with a cheaper integer multiplication. */ + +static inline int qoa_div(int v, int scalefactor) { + int reciprocal = qoa_reciprocal_tab[scalefactor]; + int n = (v * reciprocal + (1 << 15)) >> 16; + n = n + ((v > 0) - (v < 0)) - ((n > 0) - (n < 0)); /* round away from 0 */ + return n; +} + +static inline int qoa_clamp(int v, int min, int max) { + if (v < min) { return min; } + if (v > max) { return max; } + return v; +} + +/* This specialized clamp function for the signed 16 bit range improves decode +performance quite a bit. The extra if() statement works nicely with the CPUs +branch prediction as this branch is rarely taken. */ + +static inline int qoa_clamp_s16(int v) { + if ((unsigned int)(v + 32768) > 65535) { + if (v < -32768) { return -32768; } + if (v > 32767) { return 32767; } + } + return v; +} + +static inline qoa_uint64_t qoa_read_u64(const unsigned char *bytes, unsigned int *p) { + bytes += *p; + *p += 8; + return + ((qoa_uint64_t)(bytes[0]) << 56) | ((qoa_uint64_t)(bytes[1]) << 48) | + ((qoa_uint64_t)(bytes[2]) << 40) | ((qoa_uint64_t)(bytes[3]) << 32) | + ((qoa_uint64_t)(bytes[4]) << 24) | ((qoa_uint64_t)(bytes[5]) << 16) | + ((qoa_uint64_t)(bytes[6]) << 8) | ((qoa_uint64_t)(bytes[7]) << 0); +} + +/* ----------------------------------------------------------------------------- + Decoder */ + +static unsigned int qoa_decode_header(qoa_data *data) { + unsigned int p = 0; + qoa_uint64_t file_header, frame_header; + if (data->size < QOA_MIN_FILESIZE) { + return 0; + } + + /* Read the file header, verify the magic number ('qoaf') and read the + total number of samples. */ + file_header = qoa_read_u64(data->bytes, &p); + + if ((file_header >> 32) != QOA_MAGIC) { + return 0; + } + + data->qoa.samples = file_header & 0xffffffff; + if (!data->qoa.samples) { + return 0; + } + + /* Peek into the first frame header to get the number of channels and + the samplerate. */ + frame_header = qoa_read_u64(data->bytes, &p); + data->qoa.channels = (frame_header >> 56) & 0x0000ff; + data->qoa.samplerate = (frame_header >> 32) & 0xffffff; + data->samples_per_channel_per_frame = (frame_header >> 16) & 0x00ffff; + + if (data->qoa.channels == 0 || data->qoa.channels == 0 || data->qoa.samplerate == 0) { + return 0; + } + + return 8; +} + +qoa *qoa_open_from_memory(unsigned char *bytes, unsigned int size, int free_on_close) +{ + qoa_data *data = (qoa_data*) malloc(sizeof(qoa_data)); + data->bytes = bytes; + data->size = size; + data->frame_index = 0; + data->free_on_close = free_on_close; + if (qoa_decode_header(data) == 0) + { + qoa_close((qoa*) data); + return NULL; + } + data->frame_size = QOA_FRAME_SIZE(data->qoa.channels, QOA_SLICES_PER_FRAME); + return (qoa*) data; +} + +static qoa *qoa_open_from_file(FILE *file, int free_on_close) +{ + unsigned char *bytes; + unsigned int len, start; + start = (unsigned int) ftell(file); + fseek(file, 0, SEEK_END); + len = (unsigned int) (ftell(file) - start); + fseek(file, start, SEEK_SET); + + bytes = malloc(len); + fread(bytes, 1, len, file); + fclose(file); + + return qoa_open_from_memory(bytes, len, free_on_close); +} + +qoa *qoa_open_from_filename(const char *filename) +{ + FILE *f; + f = fopen(filename, "rb"); + + if (f) + return qoa_open_from_file(f, TRUE); + + return NULL; +} + +void qoa_attributes( + qoa *qoa, + unsigned int *channels, + unsigned int *samplerate, + unsigned int *samples_per_channel_per_frame, + unsigned int *total_samples_per_channel +) { + qoa_data *data = (qoa_data*) qoa; + *channels = data->qoa.channels; + *samplerate = data->qoa.samplerate; + *samples_per_channel_per_frame = data->samples_per_channel_per_frame; + *total_samples_per_channel = data->qoa.samples; +} + +static unsigned int qoa_frame_start(qoa_data *data, int frame_index) { + return 8 + (data->frame_size * frame_index); +} + +unsigned int qoa_decode_next_frame(qoa *qoa, short *sample_data) { + qoa_data *data = (qoa_data*) qoa; + unsigned int channels, samplerate, samples, frame_size, data_size, + num_slices, max_total_samples, sample_index, c, p; + int scalefactor, slice_start, slice_end, predicted, quantized, + dequantized, reconstructed, si, i; + qoa_uint64_t frame_header, history, weights, slice; + + p = qoa_frame_start(data, data->frame_index); + + /* Reached the end */ + if (p >= data->size) + { + return 0; + } + + /* Read and verify the frame header */ + frame_header = qoa_read_u64(data->bytes, &p); + channels = (frame_header >> 56) & 0x0000ff; + samplerate = (frame_header >> 32) & 0xffffff; + samples = (frame_header >> 16) & 0x00ffff; + frame_size = (frame_header ) & 0x00ffff; + + data_size = frame_size - 8 - QOA_LMS_LEN * 4 * channels; + num_slices = data_size / 8; + max_total_samples = num_slices * QOA_SLICE_LEN; + + if ( + channels != data->qoa.channels || + samplerate != data->qoa.samplerate || + frame_size > data->size || + samples * channels > max_total_samples + ) { + return 0; + } + + /* Read the LMS state: 4 x 2 bytes history, 4 x 2 bytes weights per channel */ + for (c = 0; c < channels; c++) { + history = qoa_read_u64(data->bytes, &p); + weights = qoa_read_u64(data->bytes, &p); + for (i = 0; i < QOA_LMS_LEN; i++) { + data->qoa.lms[c].history[i] = ((signed short)(history >> 48)); + history <<= 16; + data->qoa.lms[c].weights[i] = ((signed short)(weights >> 48)); + weights <<= 16; + } + } + + /* Decode all slices for all channels in this frame */ + for (sample_index = 0; sample_index < samples; sample_index += QOA_SLICE_LEN) { + for (c = 0; c < channels; c++) { + slice = qoa_read_u64(data->bytes, &p); + + scalefactor = (slice >> 60) & 0xf; + slice_start = sample_index * channels + c; + slice_end = qoa_clamp(sample_index + QOA_SLICE_LEN, 0, samples) * channels + c; + + for (si = slice_start; si < slice_end; si += channels) { + predicted = qoa_lms_predict(&data->qoa.lms[c]); + quantized = (slice >> 57) & 0x7; + dequantized = qoa_dequant_tab[scalefactor][quantized]; + reconstructed = qoa_clamp_s16(predicted + dequantized); + + sample_data[si] = reconstructed; + slice <<= 3; + + qoa_lms_update(&data->qoa.lms[c], reconstructed, dequantized); + } + } + } + + data->frame_index += 1; + return samples; +} + +void qoa_seek_frame(qoa *qoa, int frame_index) { + qoa_data* data = (qoa_data*) qoa; + data->frame_index = frame_index; +} + +void qoa_decode_entire(qoa *qoa, short *sample_data) { + qoa_data* data = (qoa_data *) qoa; + unsigned int frame_count, sample_index, sample_count; + int total_samples; + short *sample_ptr; + uint32_t i; + + /* Calculate the required size of the sample buffer and allocate */ + total_samples = data->qoa.samples * data->qoa.channels; + + frame_count = (data->size - 64) / data->frame_size; + sample_index = 0; + + for (i = 0; i < frame_count; i += 1) + { + sample_ptr = sample_data + sample_index * data->qoa.channels; + sample_count = qoa_decode_next_frame(qoa, sample_ptr); + sample_index += sample_count; + } +} + +void qoa_close(qoa *qoa) +{ + qoa_data *data = (qoa_data*) qoa; + if (data->free_on_close) + { + free(data->bytes); + } + free(qoa); +} diff --git a/WickedEngine/Utility/FAudio/src/stb.h b/WickedEngine/Utility/FAudio/src/stb.h index 5f1724a736..f412e0f8ed 100644 --- a/WickedEngine/Utility/FAudio/src/stb.h +++ b/WickedEngine/Utility/FAudio/src/stb.h @@ -207,7 +207,7 @@ CREDITS * binding various stdlib functions stb.h uses to FAudio's stdlib. * -flibit */ -#ifndef FAUDIO_WIN32_PLATFORM +#ifdef STB_MEMCPY_OVERRIDE #ifdef memcpy /* Thanks Apple! */ #undef memcpy #endif diff --git a/WickedEngine/Utility/FAudio/src/stb_vorbis.h b/WickedEngine/Utility/FAudio/src/stb_vorbis.h index 3a6e009ba5..bba90fe477 100644 --- a/WickedEngine/Utility/FAudio/src/stb_vorbis.h +++ b/WickedEngine/Utility/FAudio/src/stb_vorbis.h @@ -1382,7 +1382,7 @@ static void skip(vorb *z, int n) } #ifndef STB_VORBIS_NO_STDIO { - long x = ftell(z->f); + int64_t x = ftell(z->f); fseek(z->f, x+n, SEEK_SET); } #endif diff --git a/WickedEngine/wiAudio.cpp b/WickedEngine/wiAudio.cpp index 1332dd7de2..32fc91a027 100644 --- a/WickedEngine/wiAudio.cpp +++ b/WickedEngine/wiAudio.cpp @@ -857,7 +857,9 @@ namespace wi::audio termination_mark.AudioBytes = sizeof(termination_data); success = true; - wilog("wi::audio Initialized [FAudio] (%d ms)", (int)std::round(timer.elapsed())); + wilog("wi::audio Initialized [FAudio %d.%d.%d] (%d ms)", + FAUDIO_MAJOR_VERSION, FAUDIO_MINOR_VERSION, FAUDIO_PATCH_VERSION, + (int)std::round(timer.elapsed())); } ~AudioInternal(){ if(reverbSubmix != nullptr)