From 6b45777121fcd50d9b7d5b649c6710063b549892 Mon Sep 17 00:00:00 2001 From: Alexandre Julliard Date: Thu, 21 Oct 2021 11:45:51 +0200 Subject: [PATCH] libs: Import code from upstream FAudio 21.10. Signed-off-by: Alexandre Julliard --- configure | 26 + configure.ac | 2 + libs/faudio/LICENSE | 25 + libs/faudio/Makefile.in | 22 + libs/faudio/include/F3DAudio.h | 262 + libs/faudio/include/FACT.h | 814 +++ libs/faudio/include/FACT3D.h | 127 + libs/faudio/include/FAPO.h | 207 + libs/faudio/include/FAPOBase.h | 264 + libs/faudio/include/FAPOFX.h | 178 + libs/faudio/include/FAudio.h | 1322 +++++ libs/faudio/include/FAudioFX.h | 308 ++ libs/faudio/src/F3DAudio.c | 1563 ++++++ libs/faudio/src/FACT.c | 3021 +++++++++++ libs/faudio/src/FACT3D.c | 172 + libs/faudio/src/FACT_internal.c | 3336 ++++++++++++ libs/faudio/src/FACT_internal.h | 644 +++ libs/faudio/src/FAPOBase.c | 451 ++ libs/faudio/src/FAPOFX.c | 89 + libs/faudio/src/FAPOFX_echo.c | 248 + libs/faudio/src/FAPOFX_eq.c | 257 + libs/faudio/src/FAPOFX_masteringlimiter.c | 247 + libs/faudio/src/FAPOFX_reverb.c | 247 + libs/faudio/src/FAudio.c | 3201 ++++++++++++ libs/faudio/src/FAudioFX_reverb.c | 1969 ++++++++ libs/faudio/src/FAudioFX_volumemeter.c | 281 ++ libs/faudio/src/FAudio_internal.c | 1967 ++++++++ libs/faudio/src/FAudio_internal.h | 913 ++++ libs/faudio/src/FAudio_internal_simd.c | 1616 ++++++ libs/faudio/src/FAudio_operationset.c | 777 +++ libs/faudio/src/FAudio_platform_win32.c | 1583 ++++++ libs/faudio/src/matrix_defaults.inl | 147 + libs/faudio/src/stb.h | 406 ++ libs/faudio/src/stb_vorbis.h | 5572 +++++++++++++++++++++ 34 files changed, 32264 insertions(+) create mode 100644 libs/faudio/LICENSE create mode 100644 libs/faudio/Makefile.in create mode 100644 libs/faudio/include/F3DAudio.h create mode 100644 libs/faudio/include/FACT.h create mode 100644 libs/faudio/include/FACT3D.h create mode 100644 libs/faudio/include/FAPO.h create mode 100644 libs/faudio/include/FAPOBase.h create mode 100644 libs/faudio/include/FAPOFX.h create mode 100644 libs/faudio/include/FAudio.h create mode 100644 libs/faudio/include/FAudioFX.h create mode 100644 libs/faudio/src/F3DAudio.c create mode 100644 libs/faudio/src/FACT.c create mode 100644 libs/faudio/src/FACT3D.c create mode 100644 libs/faudio/src/FACT_internal.c create mode 100644 libs/faudio/src/FACT_internal.h create mode 100644 libs/faudio/src/FAPOBase.c create mode 100644 libs/faudio/src/FAPOFX.c create mode 100644 libs/faudio/src/FAPOFX_echo.c create mode 100644 libs/faudio/src/FAPOFX_eq.c create mode 100644 libs/faudio/src/FAPOFX_masteringlimiter.c create mode 100644 libs/faudio/src/FAPOFX_reverb.c create mode 100644 libs/faudio/src/FAudio.c create mode 100644 libs/faudio/src/FAudioFX_reverb.c create mode 100644 libs/faudio/src/FAudioFX_volumemeter.c create mode 100644 libs/faudio/src/FAudio_internal.c create mode 100644 libs/faudio/src/FAudio_internal.h create mode 100644 libs/faudio/src/FAudio_internal_simd.c create mode 100644 libs/faudio/src/FAudio_operationset.c create mode 100644 libs/faudio/src/FAudio_platform_win32.c create mode 100644 libs/faudio/src/matrix_defaults.inl create mode 100644 libs/faudio/src/stb.h create mode 100644 libs/faudio/src/stb_vorbis.h diff --git a/configure b/configure index 8d4ca1ac653..a76934d48ab 100755 --- a/configure +++ b/configure @@ -711,6 +711,8 @@ JPEG_PE_LIBS JPEG_PE_CFLAGS GSM_PE_LIBS GSM_PE_CFLAGS +FAUDIO_PE_LIBS +FAUDIO_PE_CFLAGS EXCESS_PRECISION_CFLAGS CROSSDEBUG DELAYLOADFLAG @@ -1780,6 +1782,7 @@ enable_dmoguids enable_dxerr8 enable_dxerr9 enable_dxguid +enable_faudio enable_gsm enable_jpeg enable_jxr @@ -1926,6 +1929,8 @@ CPP OBJC OBJCFLAGS OBJCPP +FAUDIO_PE_CFLAGS +FAUDIO_PE_LIBS GSM_PE_CFLAGS GSM_PE_LIBS JPEG_PE_CFLAGS @@ -2708,6 +2713,11 @@ Some influential environment variables: OBJC Objective C compiler command OBJCFLAGS Objective C compiler flags OBJCPP Objective C preprocessor + FAUDIO_PE_CFLAGS + C compiler flags for the PE faudio, overriding the bundled + version + FAUDIO_PE_LIBS + Linker flags for the PE faudio, overriding the bundled version GSM_PE_CFLAGS C compiler flags for the PE gsm, overriding the bundled version GSM_PE_LIBS Linker flags for the PE gsm, overriding the bundled version @@ -10680,6 +10690,19 @@ esac fi +if ${FAUDIO_PE_CFLAGS:+false} :; then : + FAUDIO_PE_CFLAGS="-I\$(top_srcdir)/libs/faudio/include" +else + enable_faudio=no +fi +if ${FAUDIO_PE_LIBS:+false} :; then : + FAUDIO_PE_LIBS="faudio mfplat mfreadwrite mfuuid propsys" +else + enable_faudio=no +fi +$as_echo "$as_me:${as_lineno-$LINENO}: faudio cflags: $FAUDIO_PE_CFLAGS" >&5 +$as_echo "$as_me:${as_lineno-$LINENO}: faudio libs: $FAUDIO_PE_LIBS" >&5 + if ${GSM_PE_CFLAGS:+false} :; then : GSM_PE_CFLAGS="-I\$(top_srcdir)/libs/gsm/inc" else @@ -18754,6 +18777,8 @@ QUICKTIME_LIBS = $QUICKTIME_LIBS CARBON_LIBS = $CARBON_LIBS METAL_LIBS = $METAL_LIBS EXCESS_PRECISION_CFLAGS = $EXCESS_PRECISION_CFLAGS +FAUDIO_PE_CFLAGS = $FAUDIO_PE_CFLAGS +FAUDIO_PE_LIBS = $FAUDIO_PE_LIBS GSM_PE_CFLAGS = $GSM_PE_CFLAGS GSM_PE_LIBS = $GSM_PE_LIBS JPEG_PE_CFLAGS = $JPEG_PE_CFLAGS @@ -20052,6 +20077,7 @@ wine_fn_config_makefile libs/dmoguids enable_dmoguids wine_fn_config_makefile libs/dxerr8 enable_dxerr8 wine_fn_config_makefile libs/dxerr9 enable_dxerr9 wine_fn_config_makefile libs/dxguid enable_dxguid +wine_fn_config_makefile libs/faudio enable_faudio wine_fn_config_makefile libs/gsm enable_gsm wine_fn_config_makefile libs/jpeg enable_jpeg wine_fn_config_makefile libs/jxr enable_jxr diff --git a/configure.ac b/configure.ac index 63bf4f25162..f1f52fe69dc 100644 --- a/configure.ac +++ b/configure.ac @@ -1052,6 +1052,7 @@ WINE_NOTICE_WITH(mingw,[test "x$CROSSTARGET" = "x"], dnl **** External libraries **** +WINE_EXTLIB_FLAGS(FAUDIO, faudio, "faudio mfplat mfreadwrite mfuuid propsys", "-I\$(top_srcdir)/libs/faudio/include") WINE_EXTLIB_FLAGS(GSM, gsm, gsm, "-I\$(top_srcdir)/libs/gsm/inc") WINE_EXTLIB_FLAGS(JPEG, jpeg, jpeg, "-I\$(top_srcdir)/libs/jpeg") WINE_EXTLIB_FLAGS(JXR, jxr, jxr, "-I\$(top_srcdir)/libs/jxr/jxrgluelib -I\$(top_srcdir)/libs/jxr/image/sys") @@ -3670,6 +3671,7 @@ WINE_CONFIG_MAKEFILE(libs/dmoguids) WINE_CONFIG_MAKEFILE(libs/dxerr8) WINE_CONFIG_MAKEFILE(libs/dxerr9) WINE_CONFIG_MAKEFILE(libs/dxguid) +WINE_CONFIG_MAKEFILE(libs/faudio) WINE_CONFIG_MAKEFILE(libs/gsm) WINE_CONFIG_MAKEFILE(libs/jpeg) WINE_CONFIG_MAKEFILE(libs/jxr) diff --git a/libs/faudio/LICENSE b/libs/faudio/LICENSE new file mode 100644 index 00000000000..4877a26753f --- /dev/null +++ b/libs/faudio/LICENSE @@ -0,0 +1,25 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 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 + * + */ diff --git a/libs/faudio/Makefile.in b/libs/faudio/Makefile.in new file mode 100644 index 00000000000..9058cac6543 --- /dev/null +++ b/libs/faudio/Makefile.in @@ -0,0 +1,22 @@ +EXTLIB = libfaudio.a +EXTRAINCL = -I$(srcdir)/include +EXTRADEFS = -DFAUDIO_WIN32_PLATFORM -DHAVE_WMADEC + +C_SRCS = \ + src/F3DAudio.c \ + src/FACT.c \ + src/FACT3D.c \ + src/FACT_internal.c \ + src/FAPOBase.c \ + src/FAPOFX.c \ + src/FAPOFX_echo.c \ + src/FAPOFX_eq.c \ + src/FAPOFX_masteringlimiter.c \ + src/FAPOFX_reverb.c \ + src/FAudio.c \ + src/FAudioFX_reverb.c \ + src/FAudioFX_volumemeter.c \ + src/FAudio_internal.c \ + src/FAudio_internal_simd.c \ + src/FAudio_operationset.c \ + src/FAudio_platform_win32.c diff --git a/libs/faudio/include/F3DAudio.h b/libs/faudio/include/F3DAudio.h new file mode 100644 index 00000000000..c8cdd3f9dda --- /dev/null +++ b/libs/faudio/include/F3DAudio.h @@ -0,0 +1,262 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 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 + * + */ + +/* This file has no documentation since the MSDN docs are still perfectly fine: + * https://docs.microsoft.com/en-us/windows/desktop/api/x3daudio/ + */ + +#ifndef F3DAUDIO_H +#define F3DAUDIO_H + +#ifdef _WIN32 +#define F3DAUDIOAPI +#else +#define F3DAUDIOAPI +#endif + +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Constants */ + +#ifndef _SPEAKER_POSITIONS_ +#define SPEAKER_FRONT_LEFT 0x00000001 +#define SPEAKER_FRONT_RIGHT 0x00000002 +#define SPEAKER_FRONT_CENTER 0x00000004 +#define SPEAKER_LOW_FREQUENCY 0x00000008 +#define SPEAKER_BACK_LEFT 0x00000010 +#define SPEAKER_BACK_RIGHT 0x00000020 +#define SPEAKER_FRONT_LEFT_OF_CENTER 0x00000040 +#define SPEAKER_FRONT_RIGHT_OF_CENTER 0x00000080 +#define SPEAKER_BACK_CENTER 0x00000100 +#define SPEAKER_SIDE_LEFT 0x00000200 +#define SPEAKER_SIDE_RIGHT 0x00000400 +#define SPEAKER_TOP_CENTER 0x00000800 +#define SPEAKER_TOP_FRONT_LEFT 0x00001000 +#define SPEAKER_TOP_FRONT_CENTER 0x00002000 +#define SPEAKER_TOP_FRONT_RIGHT 0x00004000 +#define SPEAKER_TOP_BACK_LEFT 0x00008000 +#define SPEAKER_TOP_BACK_CENTER 0x00010000 +#define SPEAKER_TOP_BACK_RIGHT 0x00020000 +#define _SPEAKER_POSITIONS_ +#endif + +#ifndef SPEAKER_MONO +#define SPEAKER_MONO SPEAKER_FRONT_CENTER +#define SPEAKER_STEREO (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT) +#define SPEAKER_2POINT1 \ + ( SPEAKER_FRONT_LEFT | \ + SPEAKER_FRONT_RIGHT | \ + SPEAKER_LOW_FREQUENCY ) +#define SPEAKER_SURROUND \ + ( SPEAKER_FRONT_LEFT | \ + SPEAKER_FRONT_RIGHT | \ + SPEAKER_FRONT_CENTER | \ + SPEAKER_BACK_CENTER ) +#define SPEAKER_QUAD \ + ( SPEAKER_FRONT_LEFT | \ + SPEAKER_FRONT_RIGHT | \ + SPEAKER_BACK_LEFT | \ + SPEAKER_BACK_RIGHT ) +#define SPEAKER_4POINT1 \ + ( SPEAKER_FRONT_LEFT | \ + SPEAKER_FRONT_RIGHT | \ + SPEAKER_LOW_FREQUENCY | \ + SPEAKER_BACK_LEFT | \ + SPEAKER_BACK_RIGHT ) +#define SPEAKER_5POINT1 \ + ( SPEAKER_FRONT_LEFT | \ + SPEAKER_FRONT_RIGHT | \ + SPEAKER_FRONT_CENTER | \ + SPEAKER_LOW_FREQUENCY | \ + SPEAKER_BACK_LEFT | \ + SPEAKER_BACK_RIGHT ) +#define SPEAKER_7POINT1 \ + ( SPEAKER_FRONT_LEFT | \ + SPEAKER_FRONT_RIGHT | \ + SPEAKER_FRONT_CENTER | \ + SPEAKER_LOW_FREQUENCY | \ + SPEAKER_BACK_LEFT | \ + SPEAKER_BACK_RIGHT | \ + SPEAKER_FRONT_LEFT_OF_CENTER | \ + SPEAKER_FRONT_RIGHT_OF_CENTER ) +#define SPEAKER_5POINT1_SURROUND \ + ( SPEAKER_FRONT_LEFT | \ + SPEAKER_FRONT_RIGHT | \ + SPEAKER_FRONT_CENTER | \ + SPEAKER_LOW_FREQUENCY | \ + SPEAKER_SIDE_LEFT | \ + SPEAKER_SIDE_RIGHT ) +#define SPEAKER_7POINT1_SURROUND \ + ( SPEAKER_FRONT_LEFT | \ + SPEAKER_FRONT_RIGHT | \ + SPEAKER_FRONT_CENTER | \ + SPEAKER_LOW_FREQUENCY | \ + SPEAKER_BACK_LEFT | \ + SPEAKER_BACK_RIGHT | \ + SPEAKER_SIDE_LEFT | \ + SPEAKER_SIDE_RIGHT ) +#define SPEAKER_XBOX SPEAKER_5POINT1 +#endif + +#define F3DAUDIO_PI 3.141592654f +#define F3DAUDIO_2PI 6.283185307f + +#define F3DAUDIO_CALCULATE_MATRIX 0x00000001 +#define F3DAUDIO_CALCULATE_DELAY 0x00000002 +#define F3DAUDIO_CALCULATE_LPF_DIRECT 0x00000004 +#define F3DAUDIO_CALCULATE_LPF_REVERB 0x00000008 +#define F3DAUDIO_CALCULATE_REVERB 0x00000010 +#define F3DAUDIO_CALCULATE_DOPPLER 0x00000020 +#define F3DAUDIO_CALCULATE_EMITTER_ANGLE 0x00000040 +#define F3DAUDIO_CALCULATE_ZEROCENTER 0x00010000 +#define F3DAUDIO_CALCULATE_REDIRECT_TO_LFE 0x00020000 + +/* Type Declarations */ + +#define F3DAUDIO_HANDLE_BYTESIZE 20 +typedef uint8_t F3DAUDIO_HANDLE[F3DAUDIO_HANDLE_BYTESIZE]; + +/* Structures */ + +#pragma pack(push, 1) + +typedef struct F3DAUDIO_VECTOR +{ + float x; + float y; + float z; +} F3DAUDIO_VECTOR; + +typedef struct F3DAUDIO_DISTANCE_CURVE_POINT +{ + float Distance; + float DSPSetting; +} F3DAUDIO_DISTANCE_CURVE_POINT; + +typedef struct F3DAUDIO_DISTANCE_CURVE +{ + F3DAUDIO_DISTANCE_CURVE_POINT *pPoints; + uint32_t PointCount; +} F3DAUDIO_DISTANCE_CURVE; + +typedef struct F3DAUDIO_CONE +{ + float InnerAngle; + float OuterAngle; + float InnerVolume; + float OuterVolume; + float InnerLPF; + float OuterLPF; + float InnerReverb; + float OuterReverb; +} F3DAUDIO_CONE; + +typedef struct F3DAUDIO_LISTENER +{ + F3DAUDIO_VECTOR OrientFront; + F3DAUDIO_VECTOR OrientTop; + F3DAUDIO_VECTOR Position; + F3DAUDIO_VECTOR Velocity; + F3DAUDIO_CONE *pCone; +} F3DAUDIO_LISTENER; + +typedef struct F3DAUDIO_EMITTER +{ + F3DAUDIO_CONE *pCone; + F3DAUDIO_VECTOR OrientFront; + F3DAUDIO_VECTOR OrientTop; + F3DAUDIO_VECTOR Position; + F3DAUDIO_VECTOR Velocity; + float InnerRadius; + float InnerRadiusAngle; + uint32_t ChannelCount; + float ChannelRadius; + float *pChannelAzimuths; + F3DAUDIO_DISTANCE_CURVE *pVolumeCurve; + F3DAUDIO_DISTANCE_CURVE *pLFECurve; + F3DAUDIO_DISTANCE_CURVE *pLPFDirectCurve; + F3DAUDIO_DISTANCE_CURVE *pLPFReverbCurve; + F3DAUDIO_DISTANCE_CURVE *pReverbCurve; + float CurveDistanceScaler; + float DopplerScaler; +} F3DAUDIO_EMITTER; + +#ifndef F3DAUDIO_DSP_SETTINGS_DECL +#define F3DAUDIO_DSP_SETTINGS_DECL +typedef struct F3DAUDIO_DSP_SETTINGS F3DAUDIO_DSP_SETTINGS; +#endif /* F3DAUDIO_DSP_SETTINGS_DECL */ + +struct F3DAUDIO_DSP_SETTINGS +{ + float *pMatrixCoefficients; + float *pDelayTimes; + uint32_t SrcChannelCount; + uint32_t DstChannelCount; + float LPFDirectCoefficient; + float LPFReverbCoefficient; + float ReverbLevel; + float DopplerFactor; + float EmitterToListenerAngle; + float EmitterToListenerDistance; + float EmitterVelocityComponent; + float ListenerVelocityComponent; +}; + +#pragma pack(pop) + +/* Functions */ + +F3DAUDIOAPI void F3DAudioInitialize( + uint32_t SpeakerChannelMask, + float SpeedOfSound, + F3DAUDIO_HANDLE Instance +); + +F3DAUDIOAPI uint32_t F3DAudioInitialize8( + uint32_t SpeakerChannelMask, + float SpeedOfSound, + F3DAUDIO_HANDLE Instance +); + +F3DAUDIOAPI void F3DAudioCalculate( + const F3DAUDIO_HANDLE Instance, + const F3DAUDIO_LISTENER *pListener, + const F3DAUDIO_EMITTER *pEmitter, + uint32_t Flags, + F3DAUDIO_DSP_SETTINGS *pDSPSettings +); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* F3DAUDIO_H */ + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/include/FACT.h b/libs/faudio/include/FACT.h new file mode 100644 index 00000000000..579b7168812 --- /dev/null +++ b/libs/faudio/include/FACT.h @@ -0,0 +1,814 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 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 + * + */ + +/* This file has no documentation since you are expected to already know how + * XACT works if you are still using these APIs! + */ + +#ifndef FACT_H +#define FACT_H + +#include "FAudio.h" + +#define FACTAPI FAUDIOAPI +#ifdef _WIN32 +#define FACTCALL __stdcall +#else +#define FACTCALL +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Type Declarations */ + +typedef struct FACTAudioEngine FACTAudioEngine; +typedef struct FACTSoundBank FACTSoundBank; +typedef struct FACTWaveBank FACTWaveBank; +typedef struct FACTWave FACTWave; +typedef struct FACTCue FACTCue; +typedef struct FACTNotification FACTNotification; + +typedef struct FACTRendererDetails +{ + int16_t rendererID[0xFF]; /* Win32 wchar_t */ + int16_t displayName[0xFF]; /* Win32 wchar_t */ + int32_t defaultDevice; +} FACTRendererDetails; + +typedef struct FACTOverlapped +{ + void *Internal; /* ULONG_PTR */ + void *InternalHigh; /* ULONG_PTR */ + FAUDIONAMELESS union + { + FAUDIONAMELESS struct + { + uint32_t Offset; + uint32_t OffsetHigh; + }; + void *Pointer; + }; + void *hEvent; +} FACTOverlapped; + +typedef int32_t (FACTCALL * FACTReadFileCallback)( + void *hFile, + void *buffer, + uint32_t nNumberOfBytesToRead, + uint32_t *lpNumberOfBytesRead, + FACTOverlapped *lpOverlapped +); + +typedef int32_t (FACTCALL * FACTGetOverlappedResultCallback)( + void *hFile, + FACTOverlapped *lpOverlapped, + uint32_t *lpNumberOfBytesTransferred, + int32_t bWait +); + +typedef struct FACTFileIOCallbacks +{ + FACTReadFileCallback readFileCallback; + FACTGetOverlappedResultCallback getOverlappedResultCallback; +} FACTFileIOCallbacks; + +typedef void (FACTCALL * FACTNotificationCallback)( + const FACTNotification *pNotification +); + +/* FIXME: ABI bug! This should be pack(1) explicitly. Do not memcpy this! */ +typedef struct FACTRuntimeParameters +{ + uint32_t lookAheadTime; + void *pGlobalSettingsBuffer; + uint32_t globalSettingsBufferSize; + uint32_t globalSettingsFlags; + uint32_t globalSettingsAllocAttributes; + FACTFileIOCallbacks fileIOCallbacks; + FACTNotificationCallback fnNotificationCallback; + int16_t *pRendererID; /* Win32 wchar_t* */ + FAudio *pXAudio2; + FAudioMasteringVoice *pMasteringVoice; +} FACTRuntimeParameters; + +typedef struct FACTStreamingParameters +{ + void *file; + uint32_t offset; + uint32_t flags; + uint16_t packetSize; /* Measured in DVD sectors, or 2048 bytes */ +} FACTStreamingParameters; + +#define FACT_WAVEBANK_TYPE_BUFFER 0x00000000 +#define FACT_WAVEBANK_TYPE_STREAMING 0x00000001 +#define FACT_WAVEBANK_TYPE_MASK 0x00000001 + +#define FACT_WAVEBANK_FLAGS_ENTRYNAMES 0x00010000 +#define FACT_WAVEBANK_FLAGS_COMPACT 0x00020000 +#define FACT_WAVEBANK_FLAGS_SYNC_DISABLED 0x00040000 +#define FACT_WAVEBANK_FLAGS_SEEKTABLES 0x00080000 +#define FACT_WAVEBANK_FLAGS_MASK 0x000F0000 + +typedef enum FACTWaveBankSegIdx +{ + FACT_WAVEBANK_SEGIDX_BANKDATA = 0, + FACT_WAVEBANK_SEGIDX_ENTRYMETADATA, + FACT_WAVEBANK_SEGIDX_SEEKTABLES, + FACT_WAVEBANK_SEGIDX_ENTRYNAMES, + FACT_WAVEBANK_SEGIDX_ENTRYWAVEDATA, + FACT_WAVEBANK_SEGIDX_COUNT +} FACTWaveBankSegIdx; + +#pragma pack(push, 1) + +typedef struct FACTWaveBankRegion +{ + uint32_t dwOffset; + uint32_t dwLength; +} FACTWaveBankRegion; + +typedef struct FACTWaveBankSampleRegion +{ + uint32_t dwStartSample; + uint32_t dwTotalSamples; +} FACTWaveBankSampleRegion; + +typedef struct FACTWaveBankHeader +{ + uint32_t dwSignature; + uint32_t dwVersion; + uint32_t dwHeaderVersion; + FACTWaveBankRegion Segments[FACT_WAVEBANK_SEGIDX_COUNT]; +} FACTWaveBankHeader; + +typedef union FACTWaveBankMiniWaveFormat +{ + FAUDIONAMELESS struct + { + uint32_t wFormatTag : 2; + uint32_t nChannels : 3; + uint32_t nSamplesPerSec : 18; + uint32_t wBlockAlign : 8; + uint32_t wBitsPerSample : 1; + }; + uint32_t dwValue; +} FACTWaveBankMiniWaveFormat; + +typedef struct FACTWaveBankEntry +{ + FAUDIONAMELESS union + { + FAUDIONAMELESS struct + { + uint32_t dwFlags : 4; + uint32_t Duration : 28; + }; + uint32_t dwFlagsAndDuration; + }; + FACTWaveBankMiniWaveFormat Format; + FACTWaveBankRegion PlayRegion; + FACTWaveBankSampleRegion LoopRegion; +} FACTWaveBankEntry; + +typedef struct FACTWaveBankEntryCompact +{ + uint32_t dwOffset : 21; + uint32_t dwLengthDeviation : 11; +} FACTWaveBankEntryCompact; + +typedef struct FACTWaveBankData +{ + uint32_t dwFlags; + uint32_t dwEntryCount; + char szBankName[64]; + uint32_t dwEntryMetaDataElementSize; + uint32_t dwEntryNameElementSize; + uint32_t dwAlignment; + FACTWaveBankMiniWaveFormat CompactFormat; + uint64_t BuildTime; +} FACTWaveBankData; + +#pragma pack(pop) + +typedef struct FACTWaveProperties +{ + char friendlyName[64]; + FACTWaveBankMiniWaveFormat format; + uint32_t durationInSamples; + FACTWaveBankSampleRegion loopRegion; + int32_t streaming; +} FACTWaveProperties; + +typedef struct FACTWaveInstanceProperties +{ + FACTWaveProperties properties; + int32_t backgroundMusic; +} FACTWaveInstanceProperties; + +typedef struct FACTCueProperties +{ + char friendlyName[0xFF]; + int32_t interactive; + uint16_t iaVariableIndex; + uint16_t numVariations; + uint8_t maxInstances; + uint8_t currentInstances; +} FACTCueProperties; + +typedef struct FACTTrackProperties +{ + uint32_t duration; + uint16_t numVariations; + uint8_t numChannels; + uint16_t waveVariation; + uint8_t loopCount; +} FACTTrackProperties; + +typedef struct FACTVariationProperties +{ + uint16_t index; + uint8_t weight; + float iaVariableMin; + float iaVariableMax; + int32_t linger; +} FACTVariationProperties; + +typedef struct FACTSoundProperties +{ + uint16_t category; + uint8_t priority; + int16_t pitch; + float volume; + uint16_t numTracks; + FACTTrackProperties arrTrackProperties[1]; +} FACTSoundProperties; + +typedef struct FACTSoundVariationProperties +{ + FACTVariationProperties variationProperties; + FACTSoundProperties soundProperties; +} FACTSoundVariationProperties; + +typedef struct FACTCueInstanceProperties +{ + uint32_t allocAttributes; + FACTCueProperties cueProperties; + FACTSoundVariationProperties activeVariationProperties; +} FACTCueInstanceProperties; + +#pragma pack(push, 1) + +typedef struct FACTNotificationDescription +{ + uint8_t type; + uint8_t flags; + FACTSoundBank *pSoundBank; + FACTWaveBank *pWaveBank; + FACTCue *pCue; + FACTWave *pWave; + uint16_t cueIndex; + uint16_t waveIndex; + void* pvContext; +} FACTNotificationDescription; + +typedef struct FACTNotificationCue +{ + uint16_t cueIndex; + FACTSoundBank *pSoundBank; + FACTCue *pCue; +} FACTNotificationCue; + +typedef struct FACTNotificationMarker +{ + uint16_t cueIndex; + FACTSoundBank *pSoundBank; + FACTCue *pCue; + uint32_t marker; +} FACTNotificationMarker; + +typedef struct FACTNotificationSoundBank +{ + FACTSoundBank *pSoundBank; +} FACTNotificationSoundBank; + +typedef struct FACTNotificationWaveBank +{ + FACTWaveBank *pWaveBank; +} FACTNotificationWaveBank; + +typedef struct FACTNotificationVariable +{ + uint16_t cueIndex; + FACTSoundBank *pSoundBank; + FACTCue *pCue; + uint16_t variableIndex; + float variableValue; + int32_t local; +} FACTNotificationVariable; + +typedef struct FACTNotificationGUI +{ + uint32_t reserved; +} FACTNotificationGUI; + +typedef struct FACTNotificationWave +{ + FACTWaveBank *pWaveBank; + uint16_t waveIndex; + uint16_t cueIndex; + FACTSoundBank *pSoundBank; + FACTCue *pCue; + FACTWave *pWave; +} FACTNotificationWave; + +struct FACTNotification +{ + uint8_t type; + int32_t timeStamp; + void *pvContext; + FAUDIONAMELESS union + { + FACTNotificationCue cue; + FACTNotificationMarker marker; + FACTNotificationSoundBank soundBank; + FACTNotificationWaveBank waveBank; + FACTNotificationVariable variable; + FACTNotificationGUI gui; + FACTNotificationWave wave; + }; +}; + +#pragma pack(pop) + +/* Constants */ + +#define FACT_CONTENT_VERSION 46 + +static const uint32_t FACT_FLAG_MANAGEDATA = 0x00000001; + +static const uint32_t FACT_FLAG_STOP_RELEASE = 0x00000000; +static const uint32_t FACT_FLAG_STOP_IMMEDIATE = 0x00000001; + +static const uint32_t FACT_FLAG_BACKGROUND_MUSIC = 0x00000002; +static const uint32_t FACT_FLAG_UNITS_MS = 0x00000004; +static const uint32_t FACT_FLAG_UNITS_SAMPLES = 0x00000008; + +static const uint32_t FACT_STATE_CREATED = 0x00000001; +static const uint32_t FACT_STATE_PREPARING = 0x00000002; +static const uint32_t FACT_STATE_PREPARED = 0x00000004; +static const uint32_t FACT_STATE_PLAYING = 0x00000008; +static const uint32_t FACT_STATE_STOPPING = 0x00000010; +static const uint32_t FACT_STATE_STOPPED = 0x00000020; +static const uint32_t FACT_STATE_PAUSED = 0x00000040; +static const uint32_t FACT_STATE_INUSE = 0x00000080; +static const uint32_t FACT_STATE_PREPAREFAILED = 0x80000000; + +static const int16_t FACTPITCH_MIN = -1200; +static const int16_t FACTPITCH_MAX = 1200; +static const int16_t FACTPITCH_MIN_TOTAL = -2400; +static const int16_t FACTPITCH_MAX_TOTAL = 2400; + +static const float FACTVOLUME_MIN = 0.0f; +static const float FACTVOLUME_MAX = 16777216.0f; + +static const uint16_t FACTINDEX_INVALID = 0xFFFF; +static const uint16_t FACTVARIABLEINDEX_INVALID = 0xFFFF; +static const uint16_t FACTCATEGORY_INVALID = 0xFFFF; + +static const uint8_t FACTNOTIFICATIONTYPE_CUEPREPARED = 1; +static const uint8_t FACTNOTIFICATIONTYPE_CUEPLAY = 2; +static const uint8_t FACTNOTIFICATIONTYPE_CUESTOP = 3; +static const uint8_t FACTNOTIFICATIONTYPE_CUEDESTROYED = 4; +static const uint8_t FACTNOTIFICATIONTYPE_MARKER = 5; +static const uint8_t FACTNOTIFICATIONTYPE_SOUNDBANKDESTROYED = 6; +static const uint8_t FACTNOTIFICATIONTYPE_WAVEBANKDESTROYED = 7; +static const uint8_t FACTNOTIFICATIONTYPE_LOCALVARIABLECHANGED = 8; +static const uint8_t FACTNOTIFICATIONTYPE_GLOBALVARIABLECHANGED = 9; +static const uint8_t FACTNOTIFICATIONTYPE_GUICONNECTED = 10; +static const uint8_t FACTNOTIFICATIONTYPE_GUIDISCONNECTED = 11; +static const uint8_t FACTNOTIFICATIONTYPE_WAVEPREPARED = 12; +static const uint8_t FACTNOTIFICATIONTYPE_WAVEPLAY = 13; +static const uint8_t FACTNOTIFICATIONTYPE_WAVESTOP = 14; +static const uint8_t FACTNOTIFICATIONTYPE_WAVELOOPED = 15; +static const uint8_t FACTNOTIFICATIONTYPE_WAVEDESTROYED = 16; +static const uint8_t FACTNOTIFICATIONTYPE_WAVEBANKPREPARED = 17; +static const uint8_t FACTNOTIFICATIONTYPE_WAVEBANKSTREAMING_INVALIDCONTENT = 18; + +static const uint8_t FACT_FLAG_NOTIFICATION_PERSIST = 0x01; + +#define FACT_ENGINE_LOOKAHEAD_DEFAULT 250 + +#define FACT_MAX_WMA_AVG_BYTES_PER_SEC_ENTRIES 7 +static const uint32_t aWMAAvgBytesPerSec[] = +{ + 12000, + 24000, + 4000, + 6000, + 8000, + 20000, + 2500 +}; + +#define FACT_MAX_WMA_BLOCK_ALIGN_ENTRIES 17 +static const uint32_t aWMABlockAlign[] = +{ + 929, + 1487, + 1280, + 2230, + 8917, + 8192, + 4459, + 5945, + 2304, + 1536, + 1485, + 1008, + 2731, + 4096, + 6827, + 5462, + 1280 +}; + +/* AudioEngine Interface */ + +FACTAPI uint32_t FACTCreateEngine( + uint32_t dwCreationFlags, + FACTAudioEngine **ppEngine +); + +/* See "extensions/CustomAllocatorEXT.txt" for more details. */ +FACTAPI uint32_t FACTCreateEngineWithCustomAllocatorEXT( + uint32_t dwCreationFlags, + FACTAudioEngine **ppEngine, + FAudioMallocFunc customMalloc, + FAudioFreeFunc customFree, + FAudioReallocFunc customRealloc +); + +FACTAPI uint32_t FACTAudioEngine_AddRef(FACTAudioEngine *pEngine); + +FACTAPI uint32_t FACTAudioEngine_Release(FACTAudioEngine *pEngine); + +/* FIXME: QueryInterface? Or just ignore COM garbage... -flibit */ + +FACTAPI uint32_t FACTAudioEngine_GetRendererCount( + FACTAudioEngine *pEngine, + uint16_t *pnRendererCount +); + +FACTAPI uint32_t FACTAudioEngine_GetRendererDetails( + FACTAudioEngine *pEngine, + uint16_t nRendererIndex, + FACTRendererDetails *pRendererDetails +); + +FACTAPI uint32_t FACTAudioEngine_GetFinalMixFormat( + FACTAudioEngine *pEngine, + FAudioWaveFormatExtensible *pFinalMixFormat +); + +FACTAPI uint32_t FACTAudioEngine_Initialize( + FACTAudioEngine *pEngine, + const FACTRuntimeParameters *pParams +); + +FACTAPI uint32_t FACTAudioEngine_ShutDown(FACTAudioEngine *pEngine); + +FACTAPI uint32_t FACTAudioEngine_DoWork(FACTAudioEngine *pEngine); + +FACTAPI uint32_t FACTAudioEngine_CreateSoundBank( + FACTAudioEngine *pEngine, + const void *pvBuffer, + uint32_t dwSize, + uint32_t dwFlags, + uint32_t dwAllocAttributes, + FACTSoundBank **ppSoundBank +); + +FACTAPI uint32_t FACTAudioEngine_CreateInMemoryWaveBank( + FACTAudioEngine *pEngine, + const void *pvBuffer, + uint32_t dwSize, + uint32_t dwFlags, + uint32_t dwAllocAttributes, + FACTWaveBank **ppWaveBank +); + +FACTAPI uint32_t FACTAudioEngine_CreateStreamingWaveBank( + FACTAudioEngine *pEngine, + const FACTStreamingParameters *pParms, + FACTWaveBank **ppWaveBank +); + +FACTAPI uint32_t FACTAudioEngine_PrepareWave( + FACTAudioEngine *pEngine, + uint32_t dwFlags, + const char *szWavePath, + uint32_t wStreamingPacketSize, + uint32_t dwAlignment, + uint32_t dwPlayOffset, + uint8_t nLoopCount, + FACTWave **ppWave +); + +FACTAPI uint32_t FACTAudioEngine_PrepareInMemoryWave( + FACTAudioEngine *pEngine, + uint32_t dwFlags, + FACTWaveBankEntry entry, + uint32_t *pdwSeekTable, /* Optional! */ + uint8_t *pbWaveData, + uint32_t dwPlayOffset, + uint8_t nLoopCount, + FACTWave **ppWave +); + +FACTAPI uint32_t FACTAudioEngine_PrepareStreamingWave( + FACTAudioEngine *pEngine, + uint32_t dwFlags, + FACTWaveBankEntry entry, + FACTStreamingParameters streamingParams, + uint32_t dwAlignment, + uint32_t *pdwSeekTable, /* Optional! */ + uint8_t *pbWaveData, /* ABI bug, do not use! */ + uint32_t dwPlayOffset, + uint8_t nLoopCount, + FACTWave **ppWave +); + +FACTAPI uint32_t FACTAudioEngine_RegisterNotification( + FACTAudioEngine *pEngine, + const FACTNotificationDescription *pNotificationDescription +); + +FACTAPI uint32_t FACTAudioEngine_UnRegisterNotification( + FACTAudioEngine *pEngine, + const FACTNotificationDescription *pNotificationDescription +); + +FACTAPI uint16_t FACTAudioEngine_GetCategory( + FACTAudioEngine *pEngine, + const char *szFriendlyName +); + +FACTAPI uint32_t FACTAudioEngine_Stop( + FACTAudioEngine *pEngine, + uint16_t nCategory, + uint32_t dwFlags +); + +FACTAPI uint32_t FACTAudioEngine_SetVolume( + FACTAudioEngine *pEngine, + uint16_t nCategory, + float volume +); + +FACTAPI uint32_t FACTAudioEngine_Pause( + FACTAudioEngine *pEngine, + uint16_t nCategory, + int32_t fPause +); + +FACTAPI uint16_t FACTAudioEngine_GetGlobalVariableIndex( + FACTAudioEngine *pEngine, + const char *szFriendlyName +); + +FACTAPI uint32_t FACTAudioEngine_SetGlobalVariable( + FACTAudioEngine *pEngine, + uint16_t nIndex, + float nValue +); + +FACTAPI uint32_t FACTAudioEngine_GetGlobalVariable( + FACTAudioEngine *pEngine, + uint16_t nIndex, + float *pnValue +); + +/* SoundBank Interface */ + +FACTAPI uint16_t FACTSoundBank_GetCueIndex( + FACTSoundBank *pSoundBank, + const char *szFriendlyName +); + +FACTAPI uint32_t FACTSoundBank_GetNumCues( + FACTSoundBank *pSoundBank, + uint16_t *pnNumCues +); + +FACTAPI uint32_t FACTSoundBank_GetCueProperties( + FACTSoundBank *pSoundBank, + uint16_t nCueIndex, + FACTCueProperties *pProperties +); + +FACTAPI uint32_t FACTSoundBank_Prepare( + FACTSoundBank *pSoundBank, + uint16_t nCueIndex, + uint32_t dwFlags, + int32_t timeOffset, + FACTCue** ppCue +); + +FACTAPI uint32_t FACTSoundBank_Play( + FACTSoundBank *pSoundBank, + uint16_t nCueIndex, + uint32_t dwFlags, + int32_t timeOffset, + FACTCue** ppCue /* Optional! */ +); + +#ifndef F3DAUDIO_DSP_SETTINGS_DECL +#define F3DAUDIO_DSP_SETTINGS_DECL +typedef struct F3DAUDIO_DSP_SETTINGS F3DAUDIO_DSP_SETTINGS; +#endif /* F3DAUDIO_DSP_SETTINGS_DECL */ + +FACTAPI uint32_t FACTSoundBank_Play3D( + FACTSoundBank *pSoundBank, + uint16_t nCueIndex, + uint32_t dwFlags, + int32_t timeOffset, + F3DAUDIO_DSP_SETTINGS *pDSPSettings, + FACTCue** ppCue /* Optional! */ +); + +FACTAPI uint32_t FACTSoundBank_Stop( + FACTSoundBank *pSoundBank, + uint16_t nCueIndex, + uint32_t dwFlags +); + +FACTAPI uint32_t FACTSoundBank_Destroy(FACTSoundBank *pSoundBank); + +FACTAPI uint32_t FACTSoundBank_GetState( + FACTSoundBank *pSoundBank, + uint32_t *pdwState +); + +/* WaveBank Interface */ + +FACTAPI uint32_t FACTWaveBank_Destroy(FACTWaveBank *pWaveBank); + +FACTAPI uint32_t FACTWaveBank_GetState( + FACTWaveBank *pWaveBank, + uint32_t *pdwState +); + +FACTAPI uint32_t FACTWaveBank_GetNumWaves( + FACTWaveBank *pWaveBank, + uint16_t *pnNumWaves +); + +FACTAPI uint16_t FACTWaveBank_GetWaveIndex( + FACTWaveBank *pWaveBank, + const char *szFriendlyName +); + +FACTAPI uint32_t FACTWaveBank_GetWaveProperties( + FACTWaveBank *pWaveBank, + uint16_t nWaveIndex, + FACTWaveProperties *pWaveProperties +); + +FACTAPI uint32_t FACTWaveBank_Prepare( + FACTWaveBank *pWaveBank, + uint16_t nWaveIndex, + uint32_t dwFlags, + uint32_t dwPlayOffset, + uint8_t nLoopCount, + FACTWave **ppWave +); + +FACTAPI uint32_t FACTWaveBank_Play( + FACTWaveBank *pWaveBank, + uint16_t nWaveIndex, + uint32_t dwFlags, + uint32_t dwPlayOffset, + uint8_t nLoopCount, + FACTWave **ppWave +); + +FACTAPI uint32_t FACTWaveBank_Stop( + FACTWaveBank *pWaveBank, + uint16_t nWaveIndex, + uint32_t dwFlags +); + +/* Wave Interface */ + +FACTAPI uint32_t FACTWave_Destroy(FACTWave *pWave); + +FACTAPI uint32_t FACTWave_Play(FACTWave *pWave); + +FACTAPI uint32_t FACTWave_Stop(FACTWave *pWave, uint32_t dwFlags); + +FACTAPI uint32_t FACTWave_Pause(FACTWave *pWave, int32_t fPause); + +FACTAPI uint32_t FACTWave_GetState(FACTWave *pWave, uint32_t *pdwState); + +FACTAPI uint32_t FACTWave_SetPitch(FACTWave *pWave, int16_t pitch); + +FACTAPI uint32_t FACTWave_SetVolume(FACTWave *pWave, float volume); + +FACTAPI uint32_t FACTWave_SetMatrixCoefficients( + FACTWave *pWave, + uint32_t uSrcChannelCount, + uint32_t uDstChannelCount, + float *pMatrixCoefficients +); + +FACTAPI uint32_t FACTWave_GetProperties( + FACTWave *pWave, + FACTWaveInstanceProperties *pProperties +); + +/* Cue Interface */ + +FACTAPI uint32_t FACTCue_Destroy(FACTCue *pCue); + +FACTAPI uint32_t FACTCue_Play(FACTCue *pCue); + +FACTAPI uint32_t FACTCue_Stop(FACTCue *pCue, uint32_t dwFlags); + +FACTAPI uint32_t FACTCue_GetState(FACTCue *pCue, uint32_t *pdwState); + +FACTAPI uint32_t FACTCue_SetMatrixCoefficients( + FACTCue *pCue, + uint32_t uSrcChannelCount, + uint32_t uDstChannelCount, + float *pMatrixCoefficients +); + +FACTAPI uint16_t FACTCue_GetVariableIndex( + FACTCue *pCue, + const char *szFriendlyName +); + +FACTAPI uint32_t FACTCue_SetVariable( + FACTCue *pCue, + uint16_t nIndex, + float nValue +); + +FACTAPI uint32_t FACTCue_GetVariable( + FACTCue *pCue, + uint16_t nIndex, + float *nValue +); + +FACTAPI uint32_t FACTCue_Pause(FACTCue *pCue, int32_t fPause); + +FACTAPI uint32_t FACTCue_GetProperties( + FACTCue *pCue, + FACTCueInstanceProperties **ppProperties +); + +FACTAPI uint32_t FACTCue_SetOutputVoices( + FACTCue *pCue, + const FAudioVoiceSends *pSendList /* Optional! */ +); + +FACTAPI uint32_t FACTCue_SetOutputVoiceMatrix( + FACTCue *pCue, + const FAudioVoice *pDestinationVoice, /* Optional! */ + uint32_t SourceChannels, + uint32_t DestinationChannels, + const float *pLevelMatrix /* SourceChannels * DestinationChannels */ +); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* FACT_H */ + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/include/FACT3D.h b/libs/faudio/include/FACT3D.h new file mode 100644 index 00000000000..6ac02a82986 --- /dev/null +++ b/libs/faudio/include/FACT3D.h @@ -0,0 +1,127 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 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 + * + */ + +/* This file has no documentation since you are expected to already know how + * XACT works if you are still using these APIs! + */ + +#ifndef FACT3D_H +#define FACT3D_H + +#include "F3DAudio.h" +#include "FACT.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Constants */ + +#define LEFT_AZIMUTH (3.0f * F3DAUDIO_PI / 2.0f) +#define RIGHT_AZIMUTH (F3DAUDIO_PI / 2.0f) +#define FRONT_LEFT_AZIMUTH (7.0f * F3DAUDIO_PI / 4.0f) +#define FRONT_RIGHT_AZIMUTH (F3DAUDIO_PI / 4.0f) +#define FRONT_CENTER_AZIMUTH 0.0f +#define LOW_FREQUENCY_AZIMUTH F3DAUDIO_2PI +#define BACK_LEFT_AZIMUTH (5.0f * F3DAUDIO_PI / 4.0f) +#define BACK_RIGHT_AZIMUTH (3.0f * F3DAUDIO_PI / 4.0f) +#define BACK_CENTER_AZIMUTH F3DAUDIO_PI +#define FRONT_LEFT_OF_CENTER_AZIMUTH (15.0f * F3DAUDIO_PI / 8.0f) +#define FRONT_RIGHT_OF_CENTER_AZIMUTH (F3DAUDIO_PI / 8.0f) + +static const float aStereoLayout[] = +{ + LEFT_AZIMUTH, + RIGHT_AZIMUTH +}; +static const float a2Point1Layout[] = +{ + LEFT_AZIMUTH, + RIGHT_AZIMUTH, + LOW_FREQUENCY_AZIMUTH +}; +static const float aQuadLayout[] = +{ + FRONT_LEFT_AZIMUTH, + FRONT_RIGHT_AZIMUTH, + BACK_LEFT_AZIMUTH, + BACK_RIGHT_AZIMUTH +}; +static const float a4Point1Layout[] = +{ + FRONT_LEFT_AZIMUTH, + FRONT_RIGHT_AZIMUTH, + LOW_FREQUENCY_AZIMUTH, + BACK_LEFT_AZIMUTH, + BACK_RIGHT_AZIMUTH +}; +static const float a5Point1Layout[] = +{ + FRONT_LEFT_AZIMUTH, + FRONT_RIGHT_AZIMUTH, + FRONT_CENTER_AZIMUTH, + LOW_FREQUENCY_AZIMUTH, + BACK_LEFT_AZIMUTH, + BACK_RIGHT_AZIMUTH +}; +static const float a7Point1Layout[] = +{ + FRONT_LEFT_AZIMUTH, + FRONT_RIGHT_AZIMUTH, + FRONT_CENTER_AZIMUTH, + LOW_FREQUENCY_AZIMUTH, + BACK_LEFT_AZIMUTH, + BACK_RIGHT_AZIMUTH, + LEFT_AZIMUTH, + RIGHT_AZIMUTH +}; + +/* Functions */ + +FACTAPI uint32_t FACT3DInitialize( + FACTAudioEngine *pEngine, + F3DAUDIO_HANDLE F3DInstance +); + +FACTAPI uint32_t FACT3DCalculate( + F3DAUDIO_HANDLE F3DInstance, + const F3DAUDIO_LISTENER *pListener, + F3DAUDIO_EMITTER *pEmitter, + F3DAUDIO_DSP_SETTINGS *pDSPSettings +); + +FACTAPI uint32_t FACT3DApply( + F3DAUDIO_DSP_SETTINGS *pDSPSettings, + FACTCue *pCue +); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* FACT3D_H */ + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/include/FAPO.h b/libs/faudio/include/FAPO.h new file mode 100644 index 00000000000..0609770047f --- /dev/null +++ b/libs/faudio/include/FAPO.h @@ -0,0 +1,207 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 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 + * + */ + +/* This file has no documentation since the MSDN docs are still perfectly fine: + * https://docs.microsoft.com/en-us/windows/desktop/api/xapo/ + * + * Of course, the APIs aren't exactly the same since XAPO is super dependent on + * C++. Instead, we use a struct full of functions to mimic a vtable. + * + * The only serious difference is that our FAPO (yes, really) always has the + * Get/SetParameters function pointers, for simplicity. You can ignore these if + * your effect does not have parameters, as they will never get called unless + * it is explicitly requested by the application. + */ + +#ifndef FAPO_H +#define FAPO_H + +#include "FAudio.h" + +#define FAPOAPI FAUDIOAPI +#define FAPOCALL FAUDIOCALL + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Enumerations */ + +typedef enum FAPOBufferFlags +{ + FAPO_BUFFER_SILENT, + FAPO_BUFFER_VALID +} FAPOBufferFlags; + +/* Structures */ + +#pragma pack(push, 1) + +typedef struct FAPORegistrationProperties +{ + FAudioGUID clsid; + int16_t FriendlyName[256]; /* Win32 wchar_t */ + int16_t CopyrightInfo[256]; /* Win32 wchar_t */ + uint32_t MajorVersion; + uint32_t MinorVersion; + uint32_t Flags; + uint32_t MinInputBufferCount; + uint32_t MaxInputBufferCount; + uint32_t MinOutputBufferCount; + uint32_t MaxOutputBufferCount; +} FAPORegistrationProperties; + +typedef struct FAPOLockForProcessBufferParameters +{ + const FAudioWaveFormatEx *pFormat; + uint32_t MaxFrameCount; +} FAPOLockForProcessBufferParameters; + +typedef struct FAPOProcessBufferParameters +{ + void* pBuffer; + FAPOBufferFlags BufferFlags; + uint32_t ValidFrameCount; +} FAPOProcessBufferParameters; + +#pragma pack(pop) + +/* Constants */ + +#define FAPO_MIN_CHANNELS 1 +#define FAPO_MAX_CHANNELS 64 + +#define FAPO_MIN_FRAMERATE 1000 +#define FAPO_MAX_FRAMERATE 200000 + +#define FAPO_REGISTRATION_STRING_LENGTH 256 + +#define FAPO_FLAG_CHANNELS_MUST_MATCH 0x00000001 +#define FAPO_FLAG_FRAMERATE_MUST_MATCH 0x00000002 +#define FAPO_FLAG_BITSPERSAMPLE_MUST_MATCH 0x00000004 +#define FAPO_FLAG_BUFFERCOUNT_MUST_MATCH 0x00000008 +#define FAPO_FLAG_INPLACE_REQUIRED 0x00000020 +#define FAPO_FLAG_INPLACE_SUPPORTED 0x00000010 + +/* FAPO Interface */ + +#ifndef FAPO_DECL +#define FAPO_DECL +typedef struct FAPO FAPO; +#endif /* FAPO_DECL */ + +typedef int32_t (FAPOCALL * AddRefFunc)( + void *fapo +); +typedef int32_t (FAPOCALL * ReleaseFunc)( + void *fapo +); +typedef uint32_t (FAPOCALL * GetRegistrationPropertiesFunc)( + void* fapo, + FAPORegistrationProperties **ppRegistrationProperties +); +typedef uint32_t (FAPOCALL * IsInputFormatSupportedFunc)( + void* fapo, + const FAudioWaveFormatEx *pOutputFormat, + const FAudioWaveFormatEx *pRequestedInputFormat, + FAudioWaveFormatEx **ppSupportedInputFormat +); +typedef uint32_t (FAPOCALL * IsOutputFormatSupportedFunc)( + void* fapo, + const FAudioWaveFormatEx *pInputFormat, + const FAudioWaveFormatEx *pRequestedOutputFormat, + FAudioWaveFormatEx **ppSupportedOutputFormat +); +typedef uint32_t (FAPOCALL * InitializeFunc)( + void* fapo, + const void* pData, + uint32_t DataByteSize +); +typedef void (FAPOCALL * ResetFunc)( + void* fapo +); +typedef uint32_t (FAPOCALL * LockForProcessFunc)( + void* fapo, + uint32_t InputLockedParameterCount, + const FAPOLockForProcessBufferParameters *pInputLockedParameters, + uint32_t OutputLockedParameterCount, + const FAPOLockForProcessBufferParameters *pOutputLockedParameters +); +typedef void (FAPOCALL * UnlockForProcessFunc)( + void* fapo +); +typedef void (FAPOCALL * ProcessFunc)( + void* fapo, + uint32_t InputProcessParameterCount, + const FAPOProcessBufferParameters* pInputProcessParameters, + uint32_t OutputProcessParameterCount, + FAPOProcessBufferParameters* pOutputProcessParameters, + int32_t IsEnabled +); +typedef uint32_t (FAPOCALL * CalcInputFramesFunc)( + void* fapo, + uint32_t OutputFrameCount +); +typedef uint32_t (FAPOCALL * CalcOutputFramesFunc)( + void* fapo, + uint32_t InputFrameCount +); +typedef void (FAPOCALL * SetParametersFunc)( + void* fapo, + const void* pParameters, + uint32_t ParameterByteSize +); +typedef void (FAPOCALL * GetParametersFunc)( + void* fapo, + void* pParameters, + uint32_t ParameterByteSize +); + +struct FAPO +{ + AddRefFunc AddRef; + ReleaseFunc Release; + GetRegistrationPropertiesFunc GetRegistrationProperties; + IsInputFormatSupportedFunc IsInputFormatSupported; + IsOutputFormatSupportedFunc IsOutputFormatSupported; + InitializeFunc Initialize; + ResetFunc Reset; + LockForProcessFunc LockForProcess; + UnlockForProcessFunc UnlockForProcess; + ProcessFunc Process; + CalcInputFramesFunc CalcInputFrames; + CalcOutputFramesFunc CalcOutputFrames; + SetParametersFunc SetParameters; + GetParametersFunc GetParameters; +}; + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* FAPO_H */ + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/include/FAPOBase.h b/libs/faudio/include/FAPOBase.h new file mode 100644 index 00000000000..c2fcdd2981c --- /dev/null +++ b/libs/faudio/include/FAPOBase.h @@ -0,0 +1,264 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 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 + * + */ + +/* This file has no documentation since the MSDN docs are still perfectly fine: + * https://docs.microsoft.com/en-us/windows/desktop/api/xapobase/ + * + * Of course, the APIs aren't exactly the same since XAPO is super dependent on + * C++. Instead, we use a struct full of functions to mimic a vtable. + * + * To mimic the CXAPOParametersBase experience, initialize the object like this: + * + * extern FAPORegistrationProperties MyFAPOProperties; + * extern int32_t producer; + * typedef struct MyFAPOParams + * { + * uint32_t something; + * } MyFAPOParams; + * typedef struct MyFAPO + * { + * FAPOBase base; + * uint32_t somethingElse; + * } MyFAPO; + * void MyFAPO_Free(void* fapo) + * { + * MyFAPO *mine = (MyFAPO*) fapo; + * mine->base.pFree(mine->base.m_pParameterBlocks); + * mine->base.pFree(fapo); + * } + * + * MyFAPO *result = (MyFAPO*) SDL_malloc(sizeof(MyFAPO)); + * uint8_t *params = (uint8_t*) SDL_malloc(sizeof(MyFAPOParams) * 3); + * CreateFAPOBase( + * &result->base, + * &MyFAPOProperties, + * params, + * sizeof(MyFAPOParams), + * producer + * ); + * result->base.base.Initialize = (InitializeFunc) MyFAPO_Initialize; + * result->base.base.Process = (ProcessFunc) MyFAPO_Process; + * result->base.Destructor = MyFAPO_Free; + */ + +#ifndef FAPOBASE_H +#define FAPOBASE_H + +#include "FAPO.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Constants */ + +#define FAPOBASE_DEFAULT_FORMAT_TAG FAUDIO_FORMAT_IEEE_FLOAT +#define FAPOBASE_DEFAULT_FORMAT_MIN_CHANNELS FAPO_MIN_CHANNELS +#define FAPOBASE_DEFAULT_FORMAT_MAX_CHANNELS FAPO_MAX_CHANNELS +#define FAPOBASE_DEFAULT_FORMAT_MIN_FRAMERATE FAPO_MIN_FRAMERATE +#define FAPOBASE_DEFAULT_FORMAT_MAX_FRAMERATE FAPO_MAX_FRAMERATE +#define FAPOBASE_DEFAULT_FORMAT_BITSPERSAMPLE 32 + +#define FAPOBASE_DEFAULT_FLAG ( \ + FAPO_FLAG_CHANNELS_MUST_MATCH | \ + FAPO_FLAG_FRAMERATE_MUST_MATCH | \ + FAPO_FLAG_BITSPERSAMPLE_MUST_MATCH | \ + FAPO_FLAG_BUFFERCOUNT_MUST_MATCH | \ + FAPO_FLAG_INPLACE_SUPPORTED \ +) + +#define FAPOBASE_DEFAULT_BUFFER_COUNT 1 + +/* FAPOBase Interface */ + +typedef struct FAPOBase FAPOBase; + +typedef void (FAPOCALL * OnSetParametersFunc)( + FAPOBase *fapo, + const void* parameters, + uint32_t parametersSize +); + +#pragma pack(push, 8) +struct FAPOBase +{ + /* Base Classes/Interfaces */ + FAPO base; + void (FAPOCALL *Destructor)(void*); + + /* Public Virtual Functions */ + OnSetParametersFunc OnSetParameters; + + /* Private Variables */ + const FAPORegistrationProperties *m_pRegistrationProperties; + void* m_pfnMatrixMixFunction; + float *m_pfl32MatrixCoefficients; + uint32_t m_nSrcFormatType; + uint8_t m_fIsScalarMatrix; + uint8_t m_fIsLocked; + uint8_t *m_pParameterBlocks; + uint8_t *m_pCurrentParameters; + uint8_t *m_pCurrentParametersInternal; + uint32_t m_uCurrentParametersIndex; + uint32_t m_uParameterBlockByteSize; + uint8_t m_fNewerResultsReady; + uint8_t m_fProducer; + + /* Protected Variables */ + int32_t m_lReferenceCount; /* LONG */ + + /* Allocator callbacks, NOT part of XAPOBase spec! */ + FAudioMallocFunc pMalloc; + FAudioFreeFunc pFree; + FAudioReallocFunc pRealloc; +}; +#pragma pack(pop) + +FAPOAPI void CreateFAPOBase( + FAPOBase *fapo, + const FAPORegistrationProperties *pRegistrationProperties, + uint8_t *pParameterBlocks, + uint32_t uParameterBlockByteSize, + uint8_t fProducer +); + +/* See "extensions/CustomAllocatorEXT.txt" for more information. */ +FAPOAPI void CreateFAPOBaseWithCustomAllocatorEXT( + FAPOBase *fapo, + const FAPORegistrationProperties *pRegistrationProperties, + uint8_t *pParameterBlocks, + uint32_t uParameterBlockByteSize, + uint8_t fProducer, + FAudioMallocFunc customMalloc, + FAudioFreeFunc customFree, + FAudioReallocFunc customRealloc +); + +FAPOAPI int32_t FAPOBase_AddRef(FAPOBase *fapo); + +FAPOAPI int32_t FAPOBase_Release(FAPOBase *fapo); + +FAPOAPI uint32_t FAPOBase_GetRegistrationProperties( + FAPOBase *fapo, + FAPORegistrationProperties **ppRegistrationProperties +); + +FAPOAPI uint32_t FAPOBase_IsInputFormatSupported( + FAPOBase *fapo, + const FAudioWaveFormatEx *pOutputFormat, + const FAudioWaveFormatEx *pRequestedInputFormat, + FAudioWaveFormatEx **ppSupportedInputFormat +); + +FAPOAPI uint32_t FAPOBase_IsOutputFormatSupported( + FAPOBase *fapo, + const FAudioWaveFormatEx *pInputFormat, + const FAudioWaveFormatEx *pRequestedOutputFormat, + FAudioWaveFormatEx **ppSupportedOutputFormat +); + +FAPOAPI uint32_t FAPOBase_Initialize( + FAPOBase *fapo, + const void* pData, + uint32_t DataByteSize +); + +FAPOAPI void FAPOBase_Reset(FAPOBase *fapo); + +FAPOAPI uint32_t FAPOBase_LockForProcess( + FAPOBase *fapo, + uint32_t InputLockedParameterCount, + const FAPOLockForProcessBufferParameters *pInputLockedParameters, + uint32_t OutputLockedParameterCount, + const FAPOLockForProcessBufferParameters *pOutputLockedParameters +); + +FAPOAPI void FAPOBase_UnlockForProcess(FAPOBase *fapo); + +FAPOAPI uint32_t FAPOBase_CalcInputFrames( + FAPOBase *fapo, + uint32_t OutputFrameCount +); + +FAPOAPI uint32_t FAPOBase_CalcOutputFrames( + FAPOBase *fapo, + uint32_t InputFrameCount +); + +FAPOAPI uint32_t FAPOBase_ValidateFormatDefault( + FAPOBase *fapo, + FAudioWaveFormatEx *pFormat, + uint8_t fOverwrite +); + +FAPOAPI uint32_t FAPOBase_ValidateFormatPair( + FAPOBase *fapo, + const FAudioWaveFormatEx *pSupportedFormat, + FAudioWaveFormatEx *pRequestedFormat, + uint8_t fOverwrite +); + +FAPOAPI void FAPOBase_ProcessThru( + FAPOBase *fapo, + void* pInputBuffer, + float *pOutputBuffer, + uint32_t FrameCount, + uint16_t InputChannelCount, + uint16_t OutputChannelCount, + uint8_t MixWithOutput +); + +FAPOAPI void FAPOBase_SetParameters( + FAPOBase *fapo, + const void* pParameters, + uint32_t ParameterByteSize +); + +FAPOAPI void FAPOBase_GetParameters( + FAPOBase *fapo, + void* pParameters, + uint32_t ParameterByteSize +); + +FAPOAPI void FAPOBase_OnSetParameters( + FAPOBase *fapo, + const void* parameters, + uint32_t parametersSize +); + +FAPOAPI uint8_t FAPOBase_ParametersChanged(FAPOBase *fapo); + +FAPOAPI uint8_t* FAPOBase_BeginProcess(FAPOBase *fapo); + +FAPOAPI void FAPOBase_EndProcess(FAPOBase *fapo); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* FAPOBASE_H */ + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/include/FAPOFX.h b/libs/faudio/include/FAPOFX.h new file mode 100644 index 00000000000..5aae441b2cc --- /dev/null +++ b/libs/faudio/include/FAPOFX.h @@ -0,0 +1,178 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 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 + * + */ + +#ifndef FAPOFX_H +#define FAPOFX_H + +#include "FAPO.h" + +#define FAPOFXAPI FAUDIOAPI + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* GUIDs */ + +/* "Legacy" GUIDs are from XAPOFX <= 1.5. They were removed in XAudio 2.8 and later. */ +extern const FAudioGUID FAPOFX_CLSID_FXEQ, FAPOFX_CLSID_FXEQ_LEGACY; +extern const FAudioGUID FAPOFX_CLSID_FXMasteringLimiter, FAPOFX_CLSID_FXMasteringLimiter_LEGACY; +extern const FAudioGUID FAPOFX_CLSID_FXReverb, FAPOFX_CLSID_FXReverb_LEGACY; +extern const FAudioGUID FAPOFX_CLSID_FXEcho, FAPOFX_CLSID_FXEcho_LEGACY; + +/* Structures */ + +#pragma pack(push, 1) + +/* See FAPOFXEQ_* constants below. + * FrequencyCenter is in Hz, Gain is amplitude ratio, Bandwidth is Q factor. + */ +typedef struct FAPOFXEQParameters +{ + float FrequencyCenter0; + float Gain0; + float Bandwidth0; + float FrequencyCenter1; + float Gain1; + float Bandwidth1; + float FrequencyCenter2; + float Gain2; + float Bandwidth2; + float FrequencyCenter3; + float Gain3; + float Bandwidth3; +} FAPOFXEQParameters; + +/* See FAPOFXMASTERINGLIMITER_* constants below. */ +typedef struct FAPOFXMasteringLimiterParameters +{ + uint32_t Release; /* In milliseconds */ + uint32_t Loudness; /* In... uh, MSDN doesn't actually say what. */ +} FAPOFXMasteringLimiterParameters; + +/* See FAPOFXREVERB_* constants below. + * Both parameters are arbitrary and should be treated subjectively. + */ +typedef struct FAPOFXReverbParameters +{ + float Diffusion; + float RoomSize; +} FAPOFXReverbParameters; + +/* See FAPOFXECHO_* constants below. */ +typedef struct FAPOFXEchoParameters +{ + float WetDryMix; /* Percentage of processed signal vs original */ + float Feedback; /* Percentage to feed back into input */ + float Delay; /* In milliseconds */ +} FAPOFXEchoParameters; + +#pragma pack(pop) + +/* Constants */ + +#define FAPOFXEQ_MIN_FRAMERATE 22000 +#define FAPOFXEQ_MAX_FRAMERATE 48000 + +#define FAPOFXEQ_MIN_FREQUENCY_CENTER 20.0f +#define FAPOFXEQ_MAX_FREQUENCY_CENTER 20000.0f +#define FAPOFXEQ_DEFAULT_FREQUENCY_CENTER_0 100.0f +#define FAPOFXEQ_DEFAULT_FREQUENCY_CENTER_1 800.0f +#define FAPOFXEQ_DEFAULT_FREQUENCY_CENTER_2 2000.0f +#define FAPOFXEQ_DEFAULT_FREQUENCY_CENTER_3 10000.0f + +#define FAPOFXEQ_MIN_GAIN 0.126f +#define FAPOFXEQ_MAX_GAIN 7.94f +#define FAPOFXEQ_DEFAULT_GAIN 1.0f + +#define FAPOFXEQ_MIN_BANDWIDTH 0.1f +#define FAPOFXEQ_MAX_BANDWIDTH 2.0f +#define FAPOFXEQ_DEFAULT_BANDWIDTH 1.0f + +#define FAPOFXMASTERINGLIMITER_MIN_RELEASE 1 +#define FAPOFXMASTERINGLIMITER_MAX_RELEASE 20 +#define FAPOFXMASTERINGLIMITER_DEFAULT_RELEASE 6 + +#define FAPOFXMASTERINGLIMITER_MIN_LOUDNESS 1 +#define FAPOFXMASTERINGLIMITER_MAX_LOUDNESS 1800 +#define FAPOFXMASTERINGLIMITER_DEFAULT_LOUDNESS 1000 + +#define FAPOFXREVERB_MIN_DIFFUSION 0.0f +#define FAPOFXREVERB_MAX_DIFFUSION 1.0f +#define FAPOFXREVERB_DEFAULT_DIFFUSION 0.9f + +#define FAPOFXREVERB_MIN_ROOMSIZE 0.0001f +#define FAPOFXREVERB_MAX_ROOMSIZE 1.0f +#define FAPOFXREVERB_DEFAULT_ROOMSIZE 0.6f + +#define FAPOFXECHO_MIN_WETDRYMIX 0.0f +#define FAPOFXECHO_MAX_WETDRYMIX 1.0f +#define FAPOFXECHO_DEFAULT_WETDRYMIX 0.5f + +#define FAPOFXECHO_MIN_FEEDBACK 0.0f +#define FAPOFXECHO_MAX_FEEDBACK 1.0f +#define FAPOFXECHO_DEFAULT_FEEDBACK 0.5f + +#define FAPOFXECHO_MIN_DELAY 1.0f +#define FAPOFXECHO_MAX_DELAY 2000.0f +#define FAPOFXECHO_DEFAULT_DELAY 500.0f + +/* Functions */ + +/* Creates an effect from the pre-made FAPOFX effect library. + * + * clsid: A reference to one of the FAPOFX_CLSID_* GUIDs + * pEffect: Filled with the resulting FAPO object + * pInitData: Starting parameters, pass NULL to use the default values + * InitDataByteSize: Parameter struct size, pass 0 if pInitData is NULL + * + * Returns 0 on success. + */ +FAPOFXAPI uint32_t FAPOFX_CreateFX( + const FAudioGUID *clsid, + FAPO **pEffect, + const void *pInitData, + uint32_t InitDataByteSize +); + +/* See "extensions/CustomAllocatorEXT.txt" for more details. */ +FAPOFXAPI uint32_t FAPOFX_CreateFXWithCustomAllocatorEXT( + const FAudioGUID *clsid, + FAPO **pEffect, + const void *pInitData, + uint32_t InitDataByteSize, + FAudioMallocFunc customMalloc, + FAudioFreeFunc customFree, + FAudioReallocFunc customRealloc +); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* FAPOFX_H */ + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/include/FAudio.h b/libs/faudio/include/FAudio.h new file mode 100644 index 00000000000..046e7f4098c --- /dev/null +++ b/libs/faudio/include/FAudio.h @@ -0,0 +1,1322 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 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 + * + */ + +#ifndef FAUDIO_H +#define FAUDIO_H + +#ifdef _WIN32 +#define FAUDIOAPI +#define FAUDIOCALL +#else +#define FAUDIOAPI +#define FAUDIOCALL +#endif + +#ifdef _MSC_VER +#define FAUDIODEPRECATED(msg) __declspec(deprecated(msg)) +#else +#define FAUDIODEPRECATED(msg) __attribute__((deprecated(msg))) +#endif + +/* -Wpedantic nameless union/struct silencing */ +#ifndef FAUDIONAMELESS +#ifdef __GNUC__ +#define FAUDIONAMELESS __extension__ +#else +#define FAUDIONAMELESS +#endif /* __GNUC__ */ +#endif /* FAUDIONAMELESS */ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Type Declarations */ + +typedef struct FAudio FAudio; +typedef struct FAudioVoice FAudioVoice; +typedef FAudioVoice FAudioSourceVoice; +typedef FAudioVoice FAudioSubmixVoice; +typedef FAudioVoice FAudioMasteringVoice; +typedef struct FAudioEngineCallback FAudioEngineCallback; +typedef struct FAudioVoiceCallback FAudioVoiceCallback; + +/* Enumerations */ + +typedef enum FAudioDeviceRole +{ + FAudioNotDefaultDevice = 0x0, + FAudioDefaultConsoleDevice = 0x1, + FAudioDefaultMultimediaDevice = 0x2, + FAudioDefaultCommunicationsDevice = 0x4, + FAudioDefaultGameDevice = 0x8, + FAudioGlobalDefaultDevice = 0xF, + FAudioInvalidDeviceRole = ~FAudioGlobalDefaultDevice +} FAudioDeviceRole; + +typedef enum FAudioFilterType +{ + FAudioLowPassFilter, + FAudioBandPassFilter, + FAudioHighPassFilter, + FAudioNotchFilter +} FAudioFilterType; + +typedef enum FAudioStreamCategory +{ + FAudioStreamCategory_Other, + FAudioStreamCategory_ForegroundOnlyMedia, + FAudioStreamCategory_BackgroundCapableMedia, + FAudioStreamCategory_Communications, + FAudioStreamCategory_Alerts, + FAudioStreamCategory_SoundEffects, + FAudioStreamCategory_GameEffects, + FAudioStreamCategory_GameMedia, + FAudioStreamCategory_GameChat, + FAudioStreamCategory_Speech, + FAudioStreamCategory_Movie, + FAudioStreamCategory_Media +} FAudioStreamCategory; + +/* FIXME: The original enum violates ISO C and is platform specific anyway... */ +typedef uint32_t FAudioProcessor; +#define FAUDIO_DEFAULT_PROCESSOR 0xFFFFFFFF + +/* Structures */ + +#pragma pack(push, 1) + +typedef struct FAudioGUID +{ + uint32_t Data1; + uint16_t Data2; + uint16_t Data3; + uint8_t Data4[8]; +} FAudioGUID; + +/* See MSDN: + * https://msdn.microsoft.com/en-us/library/windows/desktop/dd390970%28v=vs.85%29.aspx + */ +typedef struct FAudioWaveFormatEx +{ + uint16_t wFormatTag; + uint16_t nChannels; + uint32_t nSamplesPerSec; + uint32_t nAvgBytesPerSec; + uint16_t nBlockAlign; + uint16_t wBitsPerSample; + uint16_t cbSize; +} FAudioWaveFormatEx; + +/* See MSDN: + * https://msdn.microsoft.com/en-us/library/windows/desktop/dd390971(v=vs.85).aspx + */ +typedef struct FAudioWaveFormatExtensible +{ + FAudioWaveFormatEx Format; + union + { + uint16_t wValidBitsPerSample; + uint16_t wSamplesPerBlock; + uint16_t wReserved; + } Samples; + uint32_t dwChannelMask; + FAudioGUID SubFormat; +} FAudioWaveFormatExtensible; + +typedef struct FAudioADPCMCoefSet +{ + int16_t iCoef1; + int16_t iCoef2; +} FAudioADPCMCoefSet; + +typedef struct FAudioADPCMWaveFormat +{ + FAudioWaveFormatEx wfx; + uint16_t wSamplesPerBlock; + uint16_t wNumCoef; + + /* MSVC warns on empty arrays in structs */ + #ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable: 4200) + #endif + + FAudioADPCMCoefSet aCoef[]; + /* MSADPCM has 7 coefficient pairs: + * { + * { 256, 0 }, + * { 512, -256 }, + * { 0, 0 }, + * { 192, 64 }, + * { 240, 0 }, + * { 460, -208 }, + * { 392, -232 } + * } + */ + + #ifdef _MSC_VER + #pragma warning(pop) + #endif +} FAudioADPCMWaveFormat; + +typedef struct FAudioDeviceDetails +{ + int16_t DeviceID[256]; /* Win32 wchar_t */ + int16_t DisplayName[256]; /* Win32 wchar_t */ + FAudioDeviceRole Role; + FAudioWaveFormatExtensible OutputFormat; +} FAudioDeviceDetails; + +typedef struct FAudioVoiceDetails +{ + uint32_t CreationFlags; + uint32_t ActiveFlags; + uint32_t InputChannels; + uint32_t InputSampleRate; +} FAudioVoiceDetails; + +typedef struct FAudioSendDescriptor +{ + uint32_t Flags; /* 0 or FAUDIO_SEND_USEFILTER */ + FAudioVoice *pOutputVoice; +} FAudioSendDescriptor; + +typedef struct FAudioVoiceSends +{ + uint32_t SendCount; + FAudioSendDescriptor *pSends; +} FAudioVoiceSends; + +#ifndef FAPO_DECL +#define FAPO_DECL +typedef struct FAPO FAPO; +#endif /* FAPO_DECL */ + +typedef struct FAudioEffectDescriptor +{ + FAPO *pEffect; + int32_t InitialState; /* 1 - Enabled, 0 - Disabled */ + uint32_t OutputChannels; +} FAudioEffectDescriptor; + +typedef struct FAudioEffectChain +{ + uint32_t EffectCount; + FAudioEffectDescriptor *pEffectDescriptors; +} FAudioEffectChain; + +typedef struct FAudioFilterParameters +{ + FAudioFilterType Type; + float Frequency; /* [0, FAUDIO_MAX_FILTER_FREQUENCY] */ + float OneOverQ; /* [0, FAUDIO_MAX_FILTER_ONEOVERQ] */ +} FAudioFilterParameters; + +typedef struct FAudioBuffer +{ + /* Either 0 or FAUDIO_END_OF_STREAM */ + uint32_t Flags; + /* Pointer to wave data, memory block size. + * Note that pAudioData is not copied; FAudio reads directly from your + * pointer! This pointer must be valid until FAudio has finished using + * it, at which point an OnBufferEnd callback will be generated. + */ + uint32_t AudioBytes; + const uint8_t *pAudioData; + /* Play region, in sample frames. */ + uint32_t PlayBegin; + uint32_t PlayLength; + /* Loop region, in sample frames. + * This can be used to loop a subregion of the wave instead of looping + * the whole thing, i.e. if you have an intro/outro you can set these + * to loop the middle sections instead. If you don't need this, set both + * values to 0. + */ + uint32_t LoopBegin; + uint32_t LoopLength; + /* [0, FAUDIO_LOOP_INFINITE] */ + uint32_t LoopCount; + /* This is sent to callbacks as pBufferContext */ + void *pContext; +} FAudioBuffer; + +typedef struct FAudioBufferWMA +{ + const uint32_t *pDecodedPacketCumulativeBytes; + uint32_t PacketCount; +} FAudioBufferWMA; + +typedef struct FAudioVoiceState +{ + void *pCurrentBufferContext; + uint32_t BuffersQueued; + uint64_t SamplesPlayed; +} FAudioVoiceState; + +typedef struct FAudioPerformanceData +{ + uint64_t AudioCyclesSinceLastQuery; + uint64_t TotalCyclesSinceLastQuery; + uint32_t MinimumCyclesPerQuantum; + uint32_t MaximumCyclesPerQuantum; + uint32_t MemoryUsageInBytes; + uint32_t CurrentLatencyInSamples; + uint32_t GlitchesSinceEngineStarted; + uint32_t ActiveSourceVoiceCount; + uint32_t TotalSourceVoiceCount; + uint32_t ActiveSubmixVoiceCount; + uint32_t ActiveResamplerCount; + uint32_t ActiveMatrixMixCount; + uint32_t ActiveXmaSourceVoices; + uint32_t ActiveXmaStreams; +} FAudioPerformanceData; + +typedef struct FAudioDebugConfiguration +{ + /* See FAUDIO_LOG_* */ + uint32_t TraceMask; + uint32_t BreakMask; + /* 0 or 1 */ + int32_t LogThreadID; + int32_t LogFileline; + int32_t LogFunctionName; + int32_t LogTiming; +} FAudioDebugConfiguration; + +#pragma pack(pop) + +/* This ISN'T packed. Strictly speaking it wouldn't have mattered anyway but eh. + * See https://github.com/microsoft/DirectXTK/issues/256 + */ +typedef struct FAudioXMA2WaveFormatEx +{ + FAudioWaveFormatEx wfx; + uint16_t wNumStreams; + uint32_t dwChannelMask; + uint32_t dwSamplesEncoded; + uint32_t dwBytesPerBlock; + uint32_t dwPlayBegin; + uint32_t dwPlayLength; + uint32_t dwLoopBegin; + uint32_t dwLoopLength; + uint8_t bLoopCount; + uint8_t bEncoderVersion; + uint16_t wBlockCount; +} FAudioXMA2WaveFormat; + +/* Constants */ + +#define FAUDIO_E_OUT_OF_MEMORY 0x8007000e +#define FAUDIO_E_INVALID_ARG 0x80070057 +#define FAUDIO_E_UNSUPPORTED_FORMAT 0x88890008 +#define FAUDIO_E_INVALID_CALL 0x88960001 +#define FAUDIO_E_DEVICE_INVALIDATED 0x88960004 +#define FAPO_E_FORMAT_UNSUPPORTED 0x88970001 + +#define FAUDIO_MAX_BUFFER_BYTES 0x80000000 +#define FAUDIO_MAX_QUEUED_BUFFERS 64 +#define FAUDIO_MAX_AUDIO_CHANNELS 64 +#define FAUDIO_MIN_SAMPLE_RATE 1000 +#define FAUDIO_MAX_SAMPLE_RATE 200000 +#define FAUDIO_MAX_VOLUME_LEVEL 16777216.0f +#define FAUDIO_MIN_FREQ_RATIO (1.0f / 1024.0f) +#define FAUDIO_MAX_FREQ_RATIO 1024.0f +#define FAUDIO_DEFAULT_FREQ_RATIO 2.0f +#define FAUDIO_MAX_FILTER_ONEOVERQ 1.5f +#define FAUDIO_MAX_FILTER_FREQUENCY 1.0f +#define FAUDIO_MAX_LOOP_COUNT 254 + +#define FAUDIO_COMMIT_NOW 0 +#define FAUDIO_COMMIT_ALL 0 +#define FAUDIO_INVALID_OPSET (uint32_t) (-1) +#define FAUDIO_NO_LOOP_REGION 0 +#define FAUDIO_LOOP_INFINITE 255 +#define FAUDIO_DEFAULT_CHANNELS 0 +#define FAUDIO_DEFAULT_SAMPLERATE 0 + +#define FAUDIO_DEBUG_ENGINE 0x0001 +#define FAUDIO_VOICE_NOPITCH 0x0002 +#define FAUDIO_VOICE_NOSRC 0x0004 +#define FAUDIO_VOICE_USEFILTER 0x0008 +#define FAUDIO_VOICE_MUSIC 0x0010 +#define FAUDIO_PLAY_TAILS 0x0020 +#define FAUDIO_END_OF_STREAM 0x0040 +#define FAUDIO_SEND_USEFILTER 0x0080 +#define FAUDIO_VOICE_NOSAMPLESPLAYED 0x0100 +#define FAUDIO_1024_QUANTUM 0x8000 + +#define FAUDIO_DEFAULT_FILTER_TYPE FAudioLowPassFilter +#define FAUDIO_DEFAULT_FILTER_FREQUENCY FAUDIO_MAX_FILTER_FREQUENCY +#define FAUDIO_DEFAULT_FILTER_ONEOVERQ 1.0f + +#define FAUDIO_LOG_ERRORS 0x0001 +#define FAUDIO_LOG_WARNINGS 0x0002 +#define FAUDIO_LOG_INFO 0x0004 +#define FAUDIO_LOG_DETAIL 0x0008 +#define FAUDIO_LOG_API_CALLS 0x0010 +#define FAUDIO_LOG_FUNC_CALLS 0x0020 +#define FAUDIO_LOG_TIMING 0x0040 +#define FAUDIO_LOG_LOCKS 0x0080 +#define FAUDIO_LOG_MEMORY 0x0100 +#define FAUDIO_LOG_STREAMING 0x1000 + +#ifndef _SPEAKER_POSITIONS_ +#define SPEAKER_FRONT_LEFT 0x00000001 +#define SPEAKER_FRONT_RIGHT 0x00000002 +#define SPEAKER_FRONT_CENTER 0x00000004 +#define SPEAKER_LOW_FREQUENCY 0x00000008 +#define SPEAKER_BACK_LEFT 0x00000010 +#define SPEAKER_BACK_RIGHT 0x00000020 +#define SPEAKER_FRONT_LEFT_OF_CENTER 0x00000040 +#define SPEAKER_FRONT_RIGHT_OF_CENTER 0x00000080 +#define SPEAKER_BACK_CENTER 0x00000100 +#define SPEAKER_SIDE_LEFT 0x00000200 +#define SPEAKER_SIDE_RIGHT 0x00000400 +#define SPEAKER_TOP_CENTER 0x00000800 +#define SPEAKER_TOP_FRONT_LEFT 0x00001000 +#define SPEAKER_TOP_FRONT_CENTER 0x00002000 +#define SPEAKER_TOP_FRONT_RIGHT 0x00004000 +#define SPEAKER_TOP_BACK_LEFT 0x00008000 +#define SPEAKER_TOP_BACK_CENTER 0x00010000 +#define SPEAKER_TOP_BACK_RIGHT 0x00020000 +#define _SPEAKER_POSITIONS_ +#endif + +#ifndef SPEAKER_MONO +#define SPEAKER_MONO SPEAKER_FRONT_CENTER +#define SPEAKER_STEREO (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT) +#define SPEAKER_2POINT1 \ + ( SPEAKER_FRONT_LEFT | \ + SPEAKER_FRONT_RIGHT | \ + SPEAKER_LOW_FREQUENCY ) +#define SPEAKER_SURROUND \ + ( SPEAKER_FRONT_LEFT | \ + SPEAKER_FRONT_RIGHT | \ + SPEAKER_FRONT_CENTER | \ + SPEAKER_BACK_CENTER ) +#define SPEAKER_QUAD \ + ( SPEAKER_FRONT_LEFT | \ + SPEAKER_FRONT_RIGHT | \ + SPEAKER_BACK_LEFT | \ + SPEAKER_BACK_RIGHT ) +#define SPEAKER_4POINT1 \ + ( SPEAKER_FRONT_LEFT | \ + SPEAKER_FRONT_RIGHT | \ + SPEAKER_LOW_FREQUENCY | \ + SPEAKER_BACK_LEFT | \ + SPEAKER_BACK_RIGHT ) +#define SPEAKER_5POINT1 \ + ( SPEAKER_FRONT_LEFT | \ + SPEAKER_FRONT_RIGHT | \ + SPEAKER_FRONT_CENTER | \ + SPEAKER_LOW_FREQUENCY | \ + SPEAKER_BACK_LEFT | \ + SPEAKER_BACK_RIGHT ) +#define SPEAKER_7POINT1 \ + ( SPEAKER_FRONT_LEFT | \ + SPEAKER_FRONT_RIGHT | \ + SPEAKER_FRONT_CENTER | \ + SPEAKER_LOW_FREQUENCY | \ + SPEAKER_BACK_LEFT | \ + SPEAKER_BACK_RIGHT | \ + SPEAKER_FRONT_LEFT_OF_CENTER | \ + SPEAKER_FRONT_RIGHT_OF_CENTER ) +#define SPEAKER_5POINT1_SURROUND \ + ( SPEAKER_FRONT_LEFT | \ + SPEAKER_FRONT_RIGHT | \ + SPEAKER_FRONT_CENTER | \ + SPEAKER_LOW_FREQUENCY | \ + SPEAKER_SIDE_LEFT | \ + SPEAKER_SIDE_RIGHT ) +#define SPEAKER_7POINT1_SURROUND \ + ( SPEAKER_FRONT_LEFT | \ + SPEAKER_FRONT_RIGHT | \ + SPEAKER_FRONT_CENTER | \ + SPEAKER_LOW_FREQUENCY | \ + SPEAKER_BACK_LEFT | \ + SPEAKER_BACK_RIGHT | \ + SPEAKER_SIDE_LEFT | \ + SPEAKER_SIDE_RIGHT ) +#define SPEAKER_XBOX SPEAKER_5POINT1 +#endif + +#define FAUDIO_FORMAT_PCM 1 +#define FAUDIO_FORMAT_MSADPCM 2 +#define FAUDIO_FORMAT_IEEE_FLOAT 3 +#define FAUDIO_FORMAT_WMAUDIO2 0x0161 +#define FAUDIO_FORMAT_WMAUDIO3 0x0162 +#define FAUDIO_FORMAT_WMAUDIO_LOSSLESS 0x0163 +#define FAUDIO_FORMAT_XMAUDIO2 0x0166 +#define FAUDIO_FORMAT_EXTENSIBLE 0xFFFE + +extern FAudioGUID DATAFORMAT_SUBTYPE_PCM; +extern FAudioGUID DATAFORMAT_SUBTYPE_IEEE_FLOAT; + +/* FAudio Version API */ + +#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 10 +#define FAUDIO_PATCH_VERSION 0 + +#define FAUDIO_COMPILED_VERSION ( \ + (FAUDIO_ABI_VERSION * 100 * 100 * 100) + \ + (FAUDIO_MAJOR_VERSION * 100 * 100) + \ + (FAUDIO_MINOR_VERSION * 100) + \ + (FAUDIO_PATCH_VERSION) \ +) + +FAUDIOAPI uint32_t FAudioLinkedVersion(void); + +/* FAudio Interface */ + +/* This should be your first FAudio call. + * + * ppFAudio: Filled with the FAudio core context. + * Flags: Can be 0 or FAUDIO_DEBUG_ENGINE. + * XAudio2Processor: Set this to FAUDIO_DEFAULT_PROCESSOR. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudioCreate( + FAudio **ppFAudio, + uint32_t Flags, + FAudioProcessor XAudio2Processor +); + +/* See "extensions/COMConstructEXT.txt" for more details */ +FAUDIOAPI uint32_t FAudioCOMConstructEXT(FAudio **ppFAudio, uint8_t version); + +/* Increments a reference counter. When counter is 0, audio is freed. + * Returns the reference count after incrementing. + */ +FAUDIOAPI uint32_t FAudio_AddRef(FAudio *audio); + +/* Decrements a reference counter. When counter is 0, audio is freed. + * Returns the reference count after decrementing. + */ +FAUDIOAPI uint32_t FAudio_Release(FAudio *audio); + +/* Queries the number of sound devices available for use. + * + * pCount: Filled with the number of available sound devices. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudio_GetDeviceCount(FAudio *audio, uint32_t *pCount); + +/* Gets basic information about a sound device. + * + * Index: Can be between 0 and the result of GetDeviceCount. + * pDeviceDetails: Filled with the device information. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudio_GetDeviceDetails( + FAudio *audio, + uint32_t Index, + FAudioDeviceDetails *pDeviceDetails +); + +/* You don't actually have to call this, unless you're using the COM APIs. + * See the FAudioCreate API for parameter information. + */ +FAUDIOAPI uint32_t FAudio_Initialize( + FAudio *audio, + uint32_t Flags, + FAudioProcessor XAudio2Processor +); + +/* Register a new set of engine callbacks. + * There is no limit to the number of sets, but expect performance to degrade + * if you have a whole bunch of these. You most likely only need one. + * + * pCallback: The completely-initialized FAudioEngineCallback structure. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudio_RegisterForCallbacks( + FAudio *audio, + FAudioEngineCallback *pCallback +); + +/* Remove an active set of engine callbacks. + * This checks the pointer value, NOT the callback values! + * + * pCallback: An FAudioEngineCallback structure previously sent to Register. + * + * Returns 0 on success. + */ +FAUDIOAPI void FAudio_UnregisterForCallbacks( + FAudio *audio, + FAudioEngineCallback *pCallback +); + +/* Creates a "source" voice, used to play back wavedata. + * + * ppSourceVoice: Filled with the source voice pointer. + * pSourceFormat: The input wavedata format, see the documentation for + * FAudioWaveFormatEx. + * Flags: Can be 0 or a mix of the following FAUDIO_VOICE_* flags: + * NOPITCH/NOSRC: Resampling is disabled. If you set this, + * the source format sample rate MUST match + * the output voices' input sample rates. + * Also, SetFrequencyRatio will fail. + * USEFILTER: Enables the use of SetFilterParameters. + * MUSIC: Unsupported. + * MaxFrequencyRatio: AKA your max pitch. This allows us to optimize the size + * of the decode/resample cache sizes. For example, if you + * only expect to raise pitch by a single octave, you can + * set this value to 2.0f. 2.0f is the default value. + * Bounds: [FAUDIO_MIN_FREQ_RATIO, FAUDIO_MAX_FREQ_RATIO]. + * pCallback: Voice callbacks, see FAudioVoiceCallback documentation. + * pSendList: List of output voices. If NULL, defaults to master. + * All output voices must have the same sample rate! + * pEffectChain: List of FAPO effects. This value can be NULL. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudio_CreateSourceVoice( + FAudio *audio, + FAudioSourceVoice **ppSourceVoice, + const FAudioWaveFormatEx *pSourceFormat, + uint32_t Flags, + float MaxFrequencyRatio, + FAudioVoiceCallback *pCallback, + const FAudioVoiceSends *pSendList, + const FAudioEffectChain *pEffectChain +); + +/* Creates a "submix" voice, used to mix/process input voices. + * The typical use case for this is to perform CPU-intensive tasks on large + * groups of voices all at once. Examples include resampling and FAPO effects. + * + * ppSubmixVoice: Filled with the submix voice pointer. + * InputChannels: Input voices will convert to this channel count. + * InputSampleRate: Input voices will convert to this sample rate. + * Flags: Can be 0 or FAUDIO_VOICE_USEFILTER. + * ProcessingStage: If you have multiple submixes that depend on a specific + * order of processing, you can sort them by setting this + * value to prioritize them. For example, submixes with + * stage 0 will process first, then stage 1, 2, and so on. + * pSendList: List of output voices. If NULL, defaults to master. + * All output voices must have the same sample rate! + * pEffectChain: List of FAPO effects. This value can be NULL. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudio_CreateSubmixVoice( + FAudio *audio, + FAudioSubmixVoice **ppSubmixVoice, + uint32_t InputChannels, + uint32_t InputSampleRate, + uint32_t Flags, + uint32_t ProcessingStage, + const FAudioVoiceSends *pSendList, + const FAudioEffectChain *pEffectChain +); + +/* This should be your second FAudio call, unless you care about which device + * you want to use. In that case, see GetDeviceDetails. + * + * ppMasteringVoice: Filled with the mastering voice pointer. + * InputChannels: Device channel count. Can be FAUDIO_DEFAULT_CHANNELS. + * InputSampleRate: Device sample rate. Can be FAUDIO_DEFAULT_SAMPLERATE. + * Flags: This value must be 0. + * DeviceIndex: 0 for the default device. See GetDeviceCount. + * pEffectChain: List of FAPO effects. This value can be NULL. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudio_CreateMasteringVoice( + FAudio *audio, + FAudioMasteringVoice **ppMasteringVoice, + uint32_t InputChannels, + uint32_t InputSampleRate, + uint32_t Flags, + uint32_t DeviceIndex, + const FAudioEffectChain *pEffectChain +); + +/* This is the XAudio 2.8+ version of CreateMasteringVoice. + * Right now this doesn't do anything. Don't use this function. + */ +FAUDIOAPI uint32_t FAudio_CreateMasteringVoice8( + FAudio *audio, + FAudioMasteringVoice **ppMasteringVoice, + uint32_t InputChannels, + uint32_t InputSampleRate, + uint32_t Flags, + uint16_t *szDeviceId, + const FAudioEffectChain *pEffectChain, + FAudioStreamCategory StreamCategory +); + +/* Starts the engine, begins processing the audio graph. + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudio_StartEngine(FAudio *audio); + +/* Stops the engine and halts all processing. + * The audio device will continue to run, but will produce silence. + * The graph will be frozen until you call StartEngine, where it will then + * resume all processing exactly as it would have had this never been called. + */ +FAUDIOAPI void FAudio_StopEngine(FAudio *audio); + +/* Flushes a batch of FAudio calls compiled with a given "OperationSet" tag. + * This function is based on IXAudio2::CommitChanges from the XAudio2 spec. + * This is useful for pushing calls that need to be done perfectly in sync. For + * example, if you want to play two separate sources at the exact same time, you + * can call FAudioSourceVoice_Start with an OperationSet value of your choice, + * then call CommitChanges with that same value to start the sources together. + * + * OperationSet: Either a value known by you or FAUDIO_COMMIT_ALL + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudio_CommitOperationSet( + FAudio *audio, + uint32_t OperationSet +); + +/* DO NOT USE THIS FUNCTION OR I SWEAR TO GOD */ +FAUDIODEPRECATED("This function will break your program! Use FAudio_CommitOperationSet instead!") +FAUDIOAPI uint32_t FAudio_CommitChanges(FAudio *audio); + +/* Requests various bits of performance information from the engine. + * + * pPerfData: Filled with the data. See FAudioPerformanceData for details. + */ +FAUDIOAPI void FAudio_GetPerformanceData( + FAudio *audio, + FAudioPerformanceData *pPerfData +); + +/* When using a Debug binary, this lets you configure what information gets + * logged to output. Be careful, this can spit out a LOT of text. + * + * pDebugConfiguration: See FAudioDebugConfiguration for details. + * pReserved: Set this to NULL. + */ +FAUDIOAPI void FAudio_SetDebugConfiguration( + FAudio *audio, + FAudioDebugConfiguration *pDebugConfiguration, + void* pReserved +); + +/* Requests the values that determine's the engine's update size. + * For example, a 48KHz engine with a 1024-sample update period would return + * 1024 for the numerator and 48000 for the denominator. With this information, + * you can determine the precise update size in milliseconds. + * + * quantumNumerator - The engine's update size, in sample frames. + * quantumDenominator - The engine's sample rate, in Hz + */ +FAUDIOAPI void FAudio_GetProcessingQuantum( + FAudio *audio, + uint32_t *quantumNumerator, + uint32_t *quantumDenominator +); + +/* FAudioVoice Interface */ + +/* Requests basic information about a voice. + * + * pVoiceDetails: See FAudioVoiceDetails for details. + */ +FAUDIOAPI void FAudioVoice_GetVoiceDetails( + FAudioVoice *voice, + FAudioVoiceDetails *pVoiceDetails +); + +/* Change the output voices for this voice. + * This function is invalid for mastering voices. + * + * pSendList: List of output voices. If NULL, defaults to master. + * All output voices must have the same sample rate! + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudioVoice_SetOutputVoices( + FAudioVoice *voice, + const FAudioVoiceSends *pSendList +); + +/* Change/Remove the effect chain for this voice. + * + * pEffectChain: List of FAPO effects. This value can be NULL. + * Note that the final channel counts for this chain MUST + * match the input/output channel count that was + * determined at voice creation time! + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudioVoice_SetEffectChain( + FAudioVoice *voice, + const FAudioEffectChain *pEffectChain +); + +/* Enables an effect in the effect chain. + * + * EffectIndex: The index of the effect (based on the chain order). + * OperationSet: See CommitChanges. Default is FAUDIO_COMMIT_NOW. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudioVoice_EnableEffect( + FAudioVoice *voice, + uint32_t EffectIndex, + uint32_t OperationSet +); + +/* Disables an effect in the effect chain. + * + * EffectIndex: The index of the effect (based on the chain order). + * OperationSet: See CommitChanges. Default is FAUDIO_COMMIT_NOW. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudioVoice_DisableEffect( + FAudioVoice *voice, + uint32_t EffectIndex, + uint32_t OperationSet +); + +/* Queries the enabled/disabled state of an effect in the effect chain. + * + * EffectIndex: The index of the effect (based on the chain order). + * pEnabled: Filled with either 1 (Enabled) or 0 (Disabled). + * + * Returns 0 on success. + */ +FAUDIOAPI void FAudioVoice_GetEffectState( + FAudioVoice *voice, + uint32_t EffectIndex, + int32_t *pEnabled +); + +/* Submits a block of memory to be sent to FAPO::SetParameters. + * + * EffectIndex: The index of the effect (based on the chain order). + * pParameters: The values to be copied and submitted to the FAPO. + * ParametersByteSize: This should match what the FAPO expects! + * OperationSet: See CommitChanges. Default is FAUDIO_COMMIT_NOW. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudioVoice_SetEffectParameters( + FAudioVoice *voice, + uint32_t EffectIndex, + const void *pParameters, + uint32_t ParametersByteSize, + uint32_t OperationSet +); + +/* Requests the latest parameters from FAPO::GetParameters. + * + * EffectIndex: The index of the effect (based on the chain order). + * pParameters: Filled with the latest parameter values from the FAPO. + * ParametersByteSize: This should match what the FAPO expects! + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudioVoice_GetEffectParameters( + FAudioVoice *voice, + uint32_t EffectIndex, + void *pParameters, + uint32_t ParametersByteSize +); + +/* Sets the filter variables for a voice. + * This is only valid on voices with the USEFILTER flag. + * + * pParameters: See FAudioFilterParameters for details. + * OperationSet: See CommitChanges. Default is FAUDIO_COMMIT_NOW. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudioVoice_SetFilterParameters( + FAudioVoice *voice, + const FAudioFilterParameters *pParameters, + uint32_t OperationSet +); + +/* Requests the filter variables for a voice. + * This is only valid on voices with the USEFILTER flag. + * + * pParameters: See FAudioFilterParameters for details. + */ +FAUDIOAPI void FAudioVoice_GetFilterParameters( + FAudioVoice *voice, + FAudioFilterParameters *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 FAudioFilterParameters for details. + * OperationSet: See CommitChanges. Default is FAUDIO_COMMIT_NOW. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudioVoice_SetOutputFilterParameters( + FAudioVoice *voice, + FAudioVoice *pDestinationVoice, + const FAudioFilterParameters *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 FAudioFilterParameters for details. + */ +FAUDIOAPI void FAudioVoice_GetOutputFilterParameters( + FAudioVoice *voice, + FAudioVoice *pDestinationVoice, + FAudioFilterParameters *pParameters +); + +/* Sets the global volume of a voice. + * + * Volume: Amplitude ratio. 1.0f is default, 0.0f is silence. + * Note that you can actually set volume < 0.0f! + * Bounds: [-FAUDIO_MAX_VOLUME_LEVEL, FAUDIO_MAX_VOLUME_LEVEL] + * OperationSet: See CommitChanges. Default is FAUDIO_COMMIT_NOW. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudioVoice_SetVolume( + FAudioVoice *voice, + float Volume, + uint32_t OperationSet +); + +/* Requests the global volume of a voice. + * + * pVolume: Filled with the current voice amplitude ratio. + */ +FAUDIOAPI void FAudioVoice_GetVolume( + FAudioVoice *voice, + float *pVolume +); + +/* Sets the per-channel volumes of a voice. + * + * Channels: Must match the channel count of this voice! + * pVolumes: Amplitude ratios for each channel. Same as SetVolume. + * OperationSet: See CommitChanges. Default is FAUDIO_COMMIT_NOW. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudioVoice_SetChannelVolumes( + FAudioVoice *voice, + uint32_t Channels, + const float *pVolumes, + uint32_t OperationSet +); + +/* Requests the per-channel volumes of a voice. + * + * Channels: Must match the channel count of this voice! + * pVolumes: Filled with the current channel amplitude ratios. + */ +FAUDIOAPI void FAudioVoice_GetChannelVolumes( + FAudioVoice *voice, + uint32_t Channels, + float *pVolumes +); + +/* Sets the volumes of a send's output channels. The matrix is based on the + * voice's input channels. For example, the default matrix for a 2-channel + * source and a 2-channel output voice is as follows: + * [0] = 1.0f; <- Left input, left output + * [1] = 0.0f; <- Right input, left output + * [2] = 0.0f; <- Left input, right output + * [3] = 1.0f; <- Right input, right output + * This is typically only used for panning or 3D sound (via F3DAudio). + * + * pDestinationVoice: An output voice from the voice's send list. + * SourceChannels: Must match the voice's input channel count! + * DestinationChannels: Must match the destination's input channel count! + * pLevelMatrix: A float[SourceChannels * DestinationChannels]. + * OperationSet: See CommitChanges. Default is FAUDIO_COMMIT_NOW. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudioVoice_SetOutputMatrix( + FAudioVoice *voice, + FAudioVoice *pDestinationVoice, + uint32_t SourceChannels, + uint32_t DestinationChannels, + const float *pLevelMatrix, + uint32_t OperationSet +); + +/* Gets the volumes of a send's output channels. See SetOutputMatrix. + * + * pDestinationVoice: An output voice from the voice's send list. + * SourceChannels: Must match the voice's input channel count! + * DestinationChannels: Must match the voice's output channel count! + * pLevelMatrix: A float[SourceChannels * DestinationChannels]. + */ +FAUDIOAPI void FAudioVoice_GetOutputMatrix( + FAudioVoice *voice, + FAudioVoice *pDestinationVoice, + uint32_t SourceChannels, + uint32_t DestinationChannels, + float *pLevelMatrix +); + +/* Removes this voice from the audio graph and frees memory. */ +FAUDIOAPI void FAudioVoice_DestroyVoice(FAudioVoice *voice); + +/* FAudioSourceVoice Interface */ + +/* Starts processing for a source voice. + * + * Flags: Must be 0. + * OperationSet: See CommitChanges. Default is FAUDIO_COMMIT_NOW. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudioSourceVoice_Start( + FAudioSourceVoice *voice, + uint32_t Flags, + uint32_t OperationSet +); + +/* Pauses processing for a source voice. Yes, I said pausing. + * If you want to _actually_ stop, call FlushSourceBuffers next. + * + * Flags: Can be 0 or FAUDIO_PLAY_TAILS, which allows effects to + * keep emitting output even after processing has stopped. + * OperationSet: See CommitChanges. Default is FAUDIO_COMMIT_NOW. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudioSourceVoice_Stop( + FAudioSourceVoice *voice, + uint32_t Flags, + uint32_t OperationSet +); + +/* Submits a block of wavedata for the source to process. + * + * pBuffer: See FAudioBuffer for details. + * pBufferWMA: See FAudioBufferWMA for details. (Also, don't use WMA.) + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudioSourceVoice_SubmitSourceBuffer( + FAudioSourceVoice *voice, + const FAudioBuffer *pBuffer, + const FAudioBufferWMA *pBufferWMA +); + +/* Removes all buffers from a source, with a minor exception. + * If the voice is still playing, the active buffer is left alone. + * All buffers that are removed will spawn an OnBufferEnd callback. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudioSourceVoice_FlushSourceBuffers( + FAudioSourceVoice *voice +); + +/* Takes the last buffer currently queued and sets the END_OF_STREAM flag. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudioSourceVoice_Discontinuity( + FAudioSourceVoice *voice +); + +/* Sets the loop count of the active buffer to 0. + * + * OperationSet: See CommitChanges. Default is FAUDIO_COMMIT_NOW. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudioSourceVoice_ExitLoop( + FAudioSourceVoice *voice, + uint32_t OperationSet +); + +/* Requests the state and some basic statistics for this source. + * + * pVoiceState: See FAudioVoiceState for details. + * Flags: Can be 0 or FAUDIO_VOICE_NOSAMPLESPLAYED. + */ +FAUDIOAPI void FAudioSourceVoice_GetState( + FAudioSourceVoice *voice, + FAudioVoiceState *pVoiceState, + uint32_t Flags +); + +/* Sets the frequency ratio (fancy phrase for pitch) of this source. + * + * Ratio: The frequency ratio, must be <= MaxFrequencyRatio. + * OperationSet: See CommitChanges. Default is FAUDIO_COMMIT_NOW. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudioSourceVoice_SetFrequencyRatio( + FAudioSourceVoice *voice, + float Ratio, + uint32_t OperationSet +); + +/* Requests the frequency ratio (fancy phrase for pitch) of this source. + * + * pRatio: Filled with the frequency ratio. + */ +FAUDIOAPI void FAudioSourceVoice_GetFrequencyRatio( + FAudioSourceVoice *voice, + float *pRatio +); + +/* Resets the core sample rate of this source. + * You probably don't want this, it's more likely you want SetFrequencyRatio. + * This is used to recycle voices without having to constantly reallocate them. + * For example, if you have wavedata that's all float32 mono, but the sample + * rates are different, you can take a source that was being used for a 48KHz + * wave and call this so it can be used for a 44.1KHz wave. + * + * NewSourceSampleRate: The new sample rate for this source. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudioSourceVoice_SetSourceSampleRate( + FAudioSourceVoice *voice, + uint32_t NewSourceSampleRate +); + +/* FAudioMasteringVoice Interface */ + +/* Requests the channel mask for the mastering voice. + * This is typically used with F3DAudioInitialize, but you may find it + * interesting if you want to see the user's basic speaker layout. + * + * pChannelMask: Filled with the channel mask. + * + * Returns 0 on success. + */ +FAUDIOAPI uint32_t FAudioMasteringVoice_GetChannelMask( + FAudioMasteringVoice *voice, + uint32_t *pChannelMask +); + +/* FAudioEngineCallback Interface */ + +/* If something horrible happens, this will be called. + * + * Error: The error code that spawned this callback. + */ +typedef void (FAUDIOCALL * OnCriticalErrorFunc)( + FAudioEngineCallback *callback, + uint32_t Error +); + +/* This is called at the end of a processing update. */ +typedef void (FAUDIOCALL * OnProcessingPassEndFunc)( + FAudioEngineCallback *callback +); + +/* This is called at the beginning of a processing update. */ +typedef void (FAUDIOCALL * OnProcessingPassStartFunc)( + FAudioEngineCallback *callback +); + +struct FAudioEngineCallback +{ + OnCriticalErrorFunc OnCriticalError; + OnProcessingPassEndFunc OnProcessingPassEnd; + OnProcessingPassStartFunc OnProcessingPassStart; +}; + +/* FAudioVoiceCallback Interface */ + +/* When a buffer is no longer in use, this is called. + * + * pBufferContext: The pContext for the FAudioBuffer in question. + */ +typedef void (FAUDIOCALL * OnBufferEndFunc)( + FAudioVoiceCallback *callback, + void *pBufferContext +); + +/* When a buffer is now being used, this is called. + * + * pBufferContext: The pContext for the FAudioBuffer in question. + */ +typedef void (FAUDIOCALL * OnBufferStartFunc)( + FAudioVoiceCallback *callback, + void *pBufferContext +); + +/* When a buffer completes a loop, this is called. + * + * pBufferContext: The pContext for the FAudioBuffer in question. + */ +typedef void (FAUDIOCALL * OnLoopEndFunc)( + FAudioVoiceCallback *callback, + void *pBufferContext +); + +/* When a buffer that has the END_OF_STREAM flag is finished, this is called. */ +typedef void (FAUDIOCALL * OnStreamEndFunc)( + FAudioVoiceCallback *callback +); + +/* If something horrible happens to a voice, this is called. + * + * pBufferContext: The pContext for the FAudioBuffer in question. + * Error: The error code that spawned this callback. + */ +typedef void (FAUDIOCALL * OnVoiceErrorFunc)( + FAudioVoiceCallback *callback, + void *pBufferContext, + uint32_t Error +); + +/* When this voice is done being processed, this is called. */ +typedef void (FAUDIOCALL * OnVoiceProcessingPassEndFunc)( + FAudioVoiceCallback *callback +); + +/* When a voice is about to start being processed, this is called. + * + * BytesRequested: The number of bytes needed from the application to + * complete a full update. For example, if we need 512 + * frames for a whole update, and the voice is a float32 + * stereo source, BytesRequired will be 4096. + */ +typedef void (FAUDIOCALL * OnVoiceProcessingPassStartFunc)( + FAudioVoiceCallback *callback, + uint32_t BytesRequired +); + +struct FAudioVoiceCallback +{ + OnBufferEndFunc OnBufferEnd; + OnBufferStartFunc OnBufferStart; + OnLoopEndFunc OnLoopEnd; + OnStreamEndFunc OnStreamEnd; + OnVoiceErrorFunc OnVoiceError; + OnVoiceProcessingPassEndFunc OnVoiceProcessingPassEnd; + OnVoiceProcessingPassStartFunc OnVoiceProcessingPassStart; +}; + +/* FAudio Custom Allocator API + * See "extensions/CustomAllocatorEXT.txt" for more information. + */ + +typedef void* (FAUDIOCALL * FAudioMallocFunc)(size_t size); +typedef void (FAUDIOCALL * FAudioFreeFunc)(void* ptr); +typedef void* (FAUDIOCALL * FAudioReallocFunc)(void* ptr, size_t size); + +FAUDIOAPI uint32_t FAudioCreateWithCustomAllocatorEXT( + FAudio **ppFAudio, + uint32_t Flags, + FAudioProcessor XAudio2Processor, + FAudioMallocFunc customMalloc, + FAudioFreeFunc customFree, + FAudioReallocFunc customRealloc +); +FAUDIOAPI uint32_t FAudioCOMConstructWithCustomAllocatorEXT( + FAudio **ppFAudio, + uint8_t version, + FAudioMallocFunc customMalloc, + FAudioFreeFunc customFree, + FAudioReallocFunc customRealloc +); + +/* FAudio Engine Procedure API + * See "extensions/EngineProcedureEXT.txt" for more information. + */ +typedef void (FAUDIOCALL *FAudioEngineCallEXT)(FAudio *audio, float *output); +typedef void (FAUDIOCALL *FAudioEngineProcedureEXT)(FAudioEngineCallEXT defaultEngineProc, FAudio *audio, float *output, void *user); + +FAUDIOAPI void FAudio_SetEngineProcedureEXT( + FAudio *audio, + FAudioEngineProcedureEXT clientEngineProc, + void *user +); + + +/* FAudio I/O API */ + +#define FAUDIO_SEEK_SET 0 +#define FAUDIO_SEEK_CUR 1 +#define FAUDIO_SEEK_END 2 +#define FAUDIO_EOF -1 + +typedef size_t (FAUDIOCALL * FAudio_readfunc)( + void *data, + void *dst, + size_t size, + size_t count +); +typedef int64_t (FAUDIOCALL * FAudio_seekfunc)( + void *data, + int64_t offset, + int whence +); +typedef int (FAUDIOCALL * FAudio_closefunc)( + void *data +); + +typedef struct FAudioIOStream +{ + void *data; + FAudio_readfunc read; + FAudio_seekfunc seek; + FAudio_closefunc close; + void *lock; +} FAudioIOStream; + +FAUDIOAPI FAudioIOStream* FAudio_fopen(const char *path); +FAUDIOAPI FAudioIOStream* FAudio_memopen(void *mem, int len); +FAUDIOAPI uint8_t* FAudio_memptr(FAudioIOStream *io, size_t offset); +FAUDIOAPI void FAudio_close(FAudioIOStream *io); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* FAUDIO_H */ + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/include/FAudioFX.h b/libs/faudio/include/FAudioFX.h new file mode 100644 index 00000000000..22052d33e87 --- /dev/null +++ b/libs/faudio/include/FAudioFX.h @@ -0,0 +1,308 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 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 + * + */ + +/* This file has no documentation since the MSDN docs are still perfectly fine: + * https://docs.microsoft.com/en-us/windows/desktop/api/xaudio2fx/ + * + * Note, however, that FAudio's Reverb implementation does NOT support the new + * parameters for XAudio 2.9's 7.1 Reverb effect! + */ + +#ifndef FAUDIOFX_H +#define FAUDIOFX_H + +#include "FAudio.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* GUIDs */ + +extern const FAudioGUID FAudioFX_CLSID_AudioVolumeMeter; +extern const FAudioGUID FAudioFX_CLSID_AudioReverb; + +/* Structures */ + +#pragma pack(push, 1) + +typedef struct FAudioFXVolumeMeterLevels +{ + float *pPeakLevels; + float *pRMSLevels; + uint32_t ChannelCount; +} FAudioFXVolumeMeterLevels; + +typedef struct FAudioFXReverbParameters +{ + float WetDryMix; + uint32_t ReflectionsDelay; + uint8_t ReverbDelay; + uint8_t RearDelay; + uint8_t PositionLeft; + uint8_t PositionRight; + uint8_t PositionMatrixLeft; + uint8_t PositionMatrixRight; + uint8_t EarlyDiffusion; + uint8_t LateDiffusion; + uint8_t LowEQGain; + uint8_t LowEQCutoff; + uint8_t HighEQGain; + uint8_t HighEQCutoff; + float RoomFilterFreq; + float RoomFilterMain; + float RoomFilterHF; + float ReflectionsGain; + float ReverbGain; + float DecayTime; + float Density; + float RoomSize; +} FAudioFXReverbParameters; + +typedef struct FAudioFXReverbParameters9 +{ + float WetDryMix; + uint32_t ReflectionsDelay; + uint8_t ReverbDelay; + uint8_t RearDelay; + uint8_t SideDelay; + uint8_t PositionLeft; + uint8_t PositionRight; + uint8_t PositionMatrixLeft; + uint8_t PositionMatrixRight; + uint8_t EarlyDiffusion; + uint8_t LateDiffusion; + uint8_t LowEQGain; + uint8_t LowEQCutoff; + uint8_t HighEQGain; + uint8_t HighEQCutoff; + float RoomFilterFreq; + float RoomFilterMain; + float RoomFilterHF; + float ReflectionsGain; + float ReverbGain; + float DecayTime; + float Density; + float RoomSize; +} FAudioFXReverbParameters9; + +typedef struct FAudioFXReverbI3DL2Parameters +{ + float WetDryMix; + int32_t Room; + int32_t RoomHF; + float RoomRolloffFactor; + float DecayTime; + float DecayHFRatio; + int32_t Reflections; + float ReflectionsDelay; + int32_t Reverb; + float ReverbDelay; + float Diffusion; + float Density; + float HFReference; +} FAudioFXReverbI3DL2Parameters; + +#pragma pack(pop) + +/* Constants */ + +#define FAUDIOFX_DEBUG 1 + +#define FAUDIOFX_REVERB_MIN_FRAMERATE 20000 +#define FAUDIOFX_REVERB_MAX_FRAMERATE 48000 + +#define FAUDIOFX_REVERB_MIN_WET_DRY_MIX 0.0f +#define FAUDIOFX_REVERB_MIN_REFLECTIONS_DELAY 0 +#define FAUDIOFX_REVERB_MIN_REVERB_DELAY 0 +#define FAUDIOFX_REVERB_MIN_REAR_DELAY 0 +#define FAUDIOFX_REVERB_MIN_7POINT1_SIDE_DELAY 0 +#define FAUDIOFX_REVERB_MIN_7POINT1_REAR_DELAY 0 +#define FAUDIOFX_REVERB_MIN_POSITION 0 +#define FAUDIOFX_REVERB_MIN_DIFFUSION 0 +#define FAUDIOFX_REVERB_MIN_LOW_EQ_GAIN 0 +#define FAUDIOFX_REVERB_MIN_LOW_EQ_CUTOFF 0 +#define FAUDIOFX_REVERB_MIN_HIGH_EQ_GAIN 0 +#define FAUDIOFX_REVERB_MIN_HIGH_EQ_CUTOFF 0 +#define FAUDIOFX_REVERB_MIN_ROOM_FILTER_FREQ 20.0f +#define FAUDIOFX_REVERB_MIN_ROOM_FILTER_MAIN -100.0f +#define FAUDIOFX_REVERB_MIN_ROOM_FILTER_HF -100.0f +#define FAUDIOFX_REVERB_MIN_REFLECTIONS_GAIN -100.0f +#define FAUDIOFX_REVERB_MIN_REVERB_GAIN -100.0f +#define FAUDIOFX_REVERB_MIN_DECAY_TIME 0.1f +#define FAUDIOFX_REVERB_MIN_DENSITY 0.0f +#define FAUDIOFX_REVERB_MIN_ROOM_SIZE 0.0f + +#define FAUDIOFX_REVERB_MAX_WET_DRY_MIX 100.0f +#define FAUDIOFX_REVERB_MAX_REFLECTIONS_DELAY 300 +#define FAUDIOFX_REVERB_MAX_REVERB_DELAY 85 +#define FAUDIOFX_REVERB_MAX_REAR_DELAY 5 +#define FAUDIOFX_REVERB_MAX_7POINT1_SIDE_DELAY 5 +#define FAUDIOFX_REVERB_MAX_7POINT1_REAR_DELAY 20 +#define FAUDIOFX_REVERB_MAX_POSITION 30 +#define FAUDIOFX_REVERB_MAX_DIFFUSION 15 +#define FAUDIOFX_REVERB_MAX_LOW_EQ_GAIN 12 +#define FAUDIOFX_REVERB_MAX_LOW_EQ_CUTOFF 9 +#define FAUDIOFX_REVERB_MAX_HIGH_EQ_GAIN 8 +#define FAUDIOFX_REVERB_MAX_HIGH_EQ_CUTOFF 14 +#define FAUDIOFX_REVERB_MAX_ROOM_FILTER_FREQ 20000.0f +#define FAUDIOFX_REVERB_MAX_ROOM_FILTER_MAIN 0.0f +#define FAUDIOFX_REVERB_MAX_ROOM_FILTER_HF 0.0f +#define FAUDIOFX_REVERB_MAX_REFLECTIONS_GAIN 20.0f +#define FAUDIOFX_REVERB_MAX_REVERB_GAIN 20.0f +#define FAUDIOFX_REVERB_MAX_DENSITY 100.0f +#define FAUDIOFX_REVERB_MAX_ROOM_SIZE 100.0f + +#define FAUDIOFX_REVERB_DEFAULT_WET_DRY_MIX 100.0f +#define FAUDIOFX_REVERB_DEFAULT_REFLECTIONS_DELAY 5 +#define FAUDIOFX_REVERB_DEFAULT_REVERB_DELAY 5 +#define FAUDIOFX_REVERB_DEFAULT_REAR_DELAY 5 +#define FAUDIOFX_REVERB_DEFAULT_7POINT1_SIDE_DELAY 5 +#define FAUDIOFX_REVERB_DEFAULT_7POINT1_REAR_DELAY 20 +#define FAUDIOFX_REVERB_DEFAULT_POSITION 6 +#define FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX 27 +#define FAUDIOFX_REVERB_DEFAULT_EARLY_DIFFUSION 8 +#define FAUDIOFX_REVERB_DEFAULT_LATE_DIFFUSION 8 +#define FAUDIOFX_REVERB_DEFAULT_LOW_EQ_GAIN 8 +#define FAUDIOFX_REVERB_DEFAULT_LOW_EQ_CUTOFF 4 +#define FAUDIOFX_REVERB_DEFAULT_HIGH_EQ_GAIN 8 +#define FAUDIOFX_REVERB_DEFAULT_HIGH_EQ_CUTOFF 4 +#define FAUDIOFX_REVERB_DEFAULT_ROOM_FILTER_FREQ 5000.0f +#define FAUDIOFX_REVERB_DEFAULT_ROOM_FILTER_MAIN 0.0f +#define FAUDIOFX_REVERB_DEFAULT_ROOM_FILTER_HF 0.0f +#define FAUDIOFX_REVERB_DEFAULT_REFLECTIONS_GAIN 0.0f +#define FAUDIOFX_REVERB_DEFAULT_REVERB_GAIN 0.0f +#define FAUDIOFX_REVERB_DEFAULT_DECAY_TIME 1.0f +#define FAUDIOFX_REVERB_DEFAULT_DENSITY 100.0f +#define FAUDIOFX_REVERB_DEFAULT_ROOM_SIZE 100.0f + +#define FAUDIOFX_I3DL2_PRESET_DEFAULT \ + {100,-10000, 0,0.0f, 1.00f,0.50f,-10000,0.020f,-10000,0.040f,100.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_GENERIC \ + {100, -1000, -100,0.0f, 1.49f,0.83f, -2602,0.007f, 200,0.011f,100.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_PADDEDCELL \ + {100, -1000,-6000,0.0f, 0.17f,0.10f, -1204,0.001f, 207,0.002f,100.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_ROOM \ + {100, -1000, -454,0.0f, 0.40f,0.83f, -1646,0.002f, 53,0.003f,100.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_BATHROOM \ + {100, -1000,-1200,0.0f, 1.49f,0.54f, -370,0.007f, 1030,0.011f,100.0f, 60.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_LIVINGROOM \ + {100, -1000,-6000,0.0f, 0.50f,0.10f, -1376,0.003f, -1104,0.004f,100.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_STONEROOM \ + {100, -1000, -300,0.0f, 2.31f,0.64f, -711,0.012f, 83,0.017f,100.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_AUDITORIUM \ + {100, -1000, -476,0.0f, 4.32f,0.59f, -789,0.020f, -289,0.030f,100.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_CONCERTHALL \ + {100, -1000, -500,0.0f, 3.92f,0.70f, -1230,0.020f, -2,0.029f,100.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_CAVE \ + {100, -1000, 0,0.0f, 2.91f,1.30f, -602,0.015f, -302,0.022f,100.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_ARENA \ + {100, -1000, -698,0.0f, 7.24f,0.33f, -1166,0.020f, 16,0.030f,100.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_HANGAR \ + {100, -1000,-1000,0.0f,10.05f,0.23f, -602,0.020f, 198,0.030f,100.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_CARPETEDHALLWAY \ + {100, -1000,-4000,0.0f, 0.30f,0.10f, -1831,0.002f, -1630,0.030f,100.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_HALLWAY \ + {100, -1000, -300,0.0f, 1.49f,0.59f, -1219,0.007f, 441,0.011f,100.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_STONECORRIDOR \ + {100, -1000, -237,0.0f, 2.70f,0.79f, -1214,0.013f, 395,0.020f,100.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_ALLEY \ + {100, -1000, -270,0.0f, 1.49f,0.86f, -1204,0.007f, -4,0.011f,100.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_FOREST \ + {100, -1000,-3300,0.0f, 1.49f,0.54f, -2560,0.162f, -613,0.088f, 79.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_CITY \ + {100, -1000, -800,0.0f, 1.49f,0.67f, -2273,0.007f, -2217,0.011f, 50.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_MOUNTAINS \ + {100, -1000,-2500,0.0f, 1.49f,0.21f, -2780,0.300f, -2014,0.100f, 27.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_QUARRY \ + {100, -1000,-1000,0.0f, 1.49f,0.83f,-10000,0.061f, 500,0.025f,100.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_PLAIN \ + {100, -1000,-2000,0.0f, 1.49f,0.50f, -2466,0.179f, -2514,0.100f, 21.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_PARKINGLOT \ + {100, -1000, 0,0.0f, 1.65f,1.50f, -1363,0.008f, -1153,0.012f,100.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_SEWERPIPE \ + {100, -1000,-1000,0.0f, 2.81f,0.14f, 429,0.014f, 648,0.021f, 80.0f, 60.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_UNDERWATER \ + {100, -1000,-4000,0.0f, 1.49f,0.10f, -449,0.007f, 1700,0.011f,100.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_SMALLROOM \ + {100, -1000, -600,0.0f, 1.10f,0.83f, -400,0.005f, 500,0.010f,100.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_MEDIUMROOM \ + {100, -1000, -600,0.0f, 1.30f,0.83f, -1000,0.010f, -200,0.020f,100.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_LARGEROOM \ + {100, -1000, -600,0.0f, 1.50f,0.83f, -1600,0.020f, -1000,0.040f,100.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_MEDIUMHALL \ + {100, -1000, -600,0.0f, 1.80f,0.70f, -1300,0.015f, -800,0.030f,100.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_LARGEHALL \ + {100, -1000, -600,0.0f, 1.80f,0.70f, -2000,0.030f, -1400,0.060f,100.0f,100.0f,5000.0f} +#define FAUDIOFX_I3DL2_PRESET_PLATE \ + {100, -1000, -200,0.0f, 1.30f,0.90f, 0,0.002f, 0,0.010f,100.0f, 75.0f,5000.0f} + +/* Functions */ + +FAUDIOAPI uint32_t FAudioCreateVolumeMeter(FAPO** ppApo, uint32_t Flags); +FAUDIOAPI uint32_t FAudioCreateReverb(FAPO** ppApo, uint32_t Flags); +FAUDIOAPI uint32_t FAudioCreateReverb9(FAPO** ppApo, uint32_t Flags); + +/* See "extensions/CustomAllocatorEXT.txt" for more information. */ +FAUDIOAPI uint32_t FAudioCreateVolumeMeterWithCustomAllocatorEXT( + FAPO** ppApo, + uint32_t Flags, + FAudioMallocFunc customMalloc, + FAudioFreeFunc customFree, + FAudioReallocFunc customRealloc +); +FAUDIOAPI uint32_t FAudioCreateReverbWithCustomAllocatorEXT( + FAPO** ppApo, + uint32_t Flags, + FAudioMallocFunc customMalloc, + FAudioFreeFunc customFree, + FAudioReallocFunc customRealloc +); +FAUDIOAPI uint32_t FAudioCreateReverb9WithCustomAllocatorEXT( + FAPO** ppApo, + uint32_t Flags, + FAudioMallocFunc customMalloc, + FAudioFreeFunc customFree, + FAudioReallocFunc customRealloc +); + +FAUDIOAPI void ReverbConvertI3DL2ToNative( + const FAudioFXReverbI3DL2Parameters *pI3DL2, + FAudioFXReverbParameters *pNative +); +FAUDIOAPI void ReverbConvertI3DL2ToNative9( + const FAudioFXReverbI3DL2Parameters *pI3DL2, + FAudioFXReverbParameters9 *pNative, + int32_t sevenDotOneReverb +); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* FAUDIOFX_H */ + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/src/F3DAudio.c b/libs/faudio/src/F3DAudio.c new file mode 100644 index 00000000000..bc2b9edad21 --- /dev/null +++ b/libs/faudio/src/F3DAudio.c @@ -0,0 +1,1563 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * Ethan "flibitijibibo" Lee + * + */ + +#include "F3DAudio.h" +#include "FAudio_internal.h" + +#include /* ONLY USE THIS FOR isnan! */ +#include /* ONLY USE THIS FOR FLT_MIN/FLT_MAX! */ + +/* VS2010 doesn't define isnan (which is C99), so here it is. */ +#if defined(_MSC_VER) && !defined(isnan) +#define isnan(x) _isnan(x) +#endif + +/* UTILITY MACROS */ + +#define PARAM_CHECK_OK 1 +#define PARAM_CHECK_FAIL (!PARAM_CHECK_OK) + +#define ARRAY_COUNT(x) (sizeof(x) / sizeof(x[0])) + +#define LERP(a, x, y) ((1.0f - a) * x + a * y) + +/* PARAMETER CHECK MACROS */ + +#define PARAM_CHECK(cond, msg) FAudio_assert(cond && msg) + +#define POINTER_CHECK(p) \ + PARAM_CHECK(p != NULL, "Pointer " #p " must be != NULL") + +#define FLOAT_BETWEEN_CHECK(f, a, b) \ + PARAM_CHECK(f >= a, "Value" #f " is too low"); \ + PARAM_CHECK(f <= b, "Value" #f " is too big") + + +/* Quote X3DAUDIO docs: + * "To be considered orthonormal, a pair of vectors must have a magnitude of + * 1 +- 1x10-5 and a dot product of 0 +- 1x10-5." + * VECTOR_NORMAL_CHECK verifies that vectors are normal (i.e. have norm 1 +- 1x10-5) + * VECTOR_BASE_CHECK verifies that a pair of vectors are orthogonal (i.e. their dot + * product is 0 +- 1x10-5) + */ + +/* TODO: Switch to square length (to save CPU) */ +#define VECTOR_NORMAL_CHECK(v) \ + PARAM_CHECK( \ + FAudio_fabsf(VectorLength(v) - 1.0f) <= 1e-5f, \ + "Vector " #v " isn't normal" \ + ) + +#define VECTOR_BASE_CHECK(u, v) \ + PARAM_CHECK( \ + FAudio_fabsf(VectorDot(u, v)) <= 1e-5f, \ + "Vector u and v have non-negligible dot product" \ + ) + +/************************************* + * F3DAudioInitialize Implementation * + *************************************/ + +/* F3DAUDIO_HANDLE Structure */ +#define SPEAKERMASK(Instance) *((uint32_t*) &Instance[0]) +#define SPEAKERCOUNT(Instance) *((uint32_t*) &Instance[4]) +#define SPEAKER_LF_INDEX(Instance) *((uint32_t*) &Instance[8]) +#define SPEEDOFSOUND(Instance) *((float*) &Instance[12]) +#define SPEEDOFSOUNDEPSILON(Instance) *((float*) &Instance[16]) + +/* Export for unit tests */ +F3DAUDIOAPI uint32_t F3DAudioCheckInitParams( + uint32_t SpeakerChannelMask, + float SpeedOfSound, + F3DAUDIO_HANDLE instance +) { + const uint32_t kAllowedSpeakerMasks[] = + { + SPEAKER_MONO, + SPEAKER_STEREO, + SPEAKER_2POINT1, + SPEAKER_QUAD, + SPEAKER_SURROUND, + SPEAKER_4POINT1, + SPEAKER_5POINT1, + SPEAKER_5POINT1_SURROUND, + SPEAKER_7POINT1, + SPEAKER_7POINT1_SURROUND, + }; + uint8_t speakerMaskIsValid = 0; + uint32_t i; + + POINTER_CHECK(instance); + + for (i = 0; i < ARRAY_COUNT(kAllowedSpeakerMasks); i += 1) + { + if (SpeakerChannelMask == kAllowedSpeakerMasks[i]) + { + speakerMaskIsValid = 1; + break; + } + } + + /* The docs don't clearly say it, but the debug dll does check that + * we're exactly in one of the allowed speaker configurations. + * -Adrien + */ + PARAM_CHECK( + speakerMaskIsValid == 1, + "SpeakerChannelMask is invalid. Needs to be one of" + " MONO, STEREO, QUAD, 2POINT1, 4POINT1, 5POINT1, 7POINT1," + " SURROUND, 5POINT1_SURROUND, or 7POINT1_SURROUND." + ); + + PARAM_CHECK(SpeedOfSound >= FLT_MIN, "SpeedOfSound needs to be >= FLT_MIN"); + + return PARAM_CHECK_OK; +} + +void F3DAudioInitialize( + uint32_t SpeakerChannelMask, + float SpeedOfSound, + F3DAUDIO_HANDLE Instance +) { + F3DAudioInitialize8(SpeakerChannelMask, SpeedOfSound, Instance); +} + +uint32_t F3DAudioInitialize8( + uint32_t SpeakerChannelMask, + float SpeedOfSound, + F3DAUDIO_HANDLE Instance +) { + union + { + float f; + uint32_t i; + } epsilonHack; + uint32_t speakerCount = 0; + + if (!F3DAudioCheckInitParams(SpeakerChannelMask, SpeedOfSound, Instance)) + { + return FAUDIO_E_INVALID_CALL; + } + + SPEAKERMASK(Instance) = SpeakerChannelMask; + SPEEDOFSOUND(Instance) = SpeedOfSound; + + /* "Convert" raw float to int... */ + epsilonHack.f = SpeedOfSound; + /* ... Subtract epsilon value... */ + epsilonHack.i -= 1; + /* ... Convert back to float. */ + SPEEDOFSOUNDEPSILON(Instance) = epsilonHack.f; + + SPEAKER_LF_INDEX(Instance) = 0xFFFFFFFF; + if (SpeakerChannelMask & SPEAKER_LOW_FREQUENCY) + { + if (SpeakerChannelMask & SPEAKER_FRONT_CENTER) + { + SPEAKER_LF_INDEX(Instance) = 3; + } + else + { + SPEAKER_LF_INDEX(Instance) = 2; + } + } + + while (SpeakerChannelMask) + { + speakerCount += 1; + SpeakerChannelMask &= SpeakerChannelMask - 1; + } + SPEAKERCOUNT(Instance) = speakerCount; + + return 0; +} + + +/************************************ + * F3DAudioCalculate Implementation * + ************************************/ + +/* VECTOR UTILITIES */ + +static inline F3DAUDIO_VECTOR Vec(float x, float y, float z) +{ + F3DAUDIO_VECTOR res; + res.x = x; + res.y = y; + res.z = z; + return res; +} + +#define VectorAdd(u, v) Vec(u.x + v.x, u.y + v.y, u.z + v.z) + +#define VectorSub(u, v) Vec(u.x - v.x, u.y - v.y, u.z - v.z) + +#define VectorScale(u, s) Vec(u.x * s, u.y * s, u.z * s) + +#define VectorCross(u, v) Vec( \ + (u.y * v.z) - (u.z * v.y), \ + (u.z * v.x) - (u.x * v.z), \ + (u.x * v.y) - (u.y * v.x) \ +) + +#define VectorLength(v) FAudio_sqrtf( \ + (v.x * v.x) + (v.y * v.y) + (v.z * v.z) \ +) + +#define VectorDot(u, v) ((u.x * v.x) + (u.y * v.y) + (u.z * v.z)) + +/* This structure represent a tuple of vectors that form a left-handed basis. + * That is, all vectors are normal, orthogonal to each other, and taken in the + * order front, right, top they follow the left-hand rule. + * (https://en.wikipedia.org/wiki/Right-hand_rule) + */ +typedef struct F3DAUDIO_BASIS +{ + F3DAUDIO_VECTOR front; + F3DAUDIO_VECTOR right; + F3DAUDIO_VECTOR top; +} F3DAUDIO_BASIS; + +/* CHECK UTILITY FUNCTIONS */ + +static inline uint8_t CheckCone(F3DAUDIO_CONE *pCone) +{ + if (!pCone) + { + return PARAM_CHECK_OK; + } + + FLOAT_BETWEEN_CHECK(pCone->InnerAngle, 0.0f, F3DAUDIO_2PI); + FLOAT_BETWEEN_CHECK(pCone->OuterAngle, pCone->InnerAngle, F3DAUDIO_2PI); + + FLOAT_BETWEEN_CHECK(pCone->InnerVolume, 0.0f, 2.0f); + FLOAT_BETWEEN_CHECK(pCone->OuterVolume, 0.0f, 2.0f); + + FLOAT_BETWEEN_CHECK(pCone->InnerLPF, 0.0f, 1.0f); + FLOAT_BETWEEN_CHECK(pCone->OuterLPF, 0.0f, 1.0f); + + FLOAT_BETWEEN_CHECK(pCone->InnerReverb, 0.0f, 2.0f); + FLOAT_BETWEEN_CHECK(pCone->OuterReverb, 0.0f, 2.0f); + + return PARAM_CHECK_OK; +} + +static inline uint8_t CheckCurve(F3DAUDIO_DISTANCE_CURVE *pCurve) +{ + F3DAUDIO_DISTANCE_CURVE_POINT *points; + uint32_t i; + if (!pCurve) + { + return PARAM_CHECK_OK; + } + + points = pCurve->pPoints; + POINTER_CHECK(points); + PARAM_CHECK(pCurve->PointCount >= 2, "Invalid number of points for curve"); + + for (i = 0; i < pCurve->PointCount; i += 1) + { + FLOAT_BETWEEN_CHECK(points[i].Distance, 0.0f, 1.0f); + } + + PARAM_CHECK( + points[0].Distance == 0.0f, + "First point in the curve must be at distance 0.0f" + ); + PARAM_CHECK( + points[pCurve->PointCount - 1].Distance == 1.0f, + "Last point in the curve must be at distance 1.0f" + ); + + for (i = 0; i < (pCurve->PointCount - 1); i += 1) + { + PARAM_CHECK( + points[i].Distance < points[i + 1].Distance, + "Curve points must be in strict ascending order" + ); + } + + return PARAM_CHECK_OK; +} + +/* Export for unit tests */ +F3DAUDIOAPI uint8_t F3DAudioCheckCalculateParams( + const F3DAUDIO_HANDLE Instance, + const F3DAUDIO_LISTENER *pListener, + const F3DAUDIO_EMITTER *pEmitter, + uint32_t Flags, + F3DAUDIO_DSP_SETTINGS *pDSPSettings +) { + uint32_t i, ChannelCount; + + POINTER_CHECK(Instance); + POINTER_CHECK(pListener); + POINTER_CHECK(pEmitter); + POINTER_CHECK(pDSPSettings); + + if (Flags & F3DAUDIO_CALCULATE_MATRIX) + { + POINTER_CHECK(pDSPSettings->pMatrixCoefficients); + } + if (Flags & F3DAUDIO_CALCULATE_ZEROCENTER) + { + const uint32_t isCalculateMatrix = (Flags & F3DAUDIO_CALCULATE_MATRIX); + const uint32_t hasCenter = SPEAKERMASK(Instance) & SPEAKER_FRONT_CENTER; + PARAM_CHECK( + isCalculateMatrix && hasCenter, + "F3DAUDIO_CALCULATE_ZEROCENTER is only valid for matrix" + " calculations with an output format that has a center channel" + ); + } + + if (Flags & F3DAUDIO_CALCULATE_REDIRECT_TO_LFE) + { + const uint32_t isCalculateMatrix = (Flags & F3DAUDIO_CALCULATE_MATRIX); + const uint32_t hasLF = SPEAKERMASK(Instance) & SPEAKER_LOW_FREQUENCY; + PARAM_CHECK( + isCalculateMatrix && hasLF, + "F3DAUDIO_CALCULATE_REDIRECT_TO_LFE is only valid for matrix" + " calculations with an output format that has a low-frequency" + " channel" + ); + } + + ChannelCount = SPEAKERCOUNT(Instance); + PARAM_CHECK( + pDSPSettings->DstChannelCount == ChannelCount, + "Invalid channel count, DSP settings and speaker configuration must agree" + ); + PARAM_CHECK( + pDSPSettings->SrcChannelCount == pEmitter->ChannelCount, + "Invalid channel count, DSP settings and emitter must agree" + ); + + if (pListener->pCone) + { + PARAM_CHECK( + CheckCone(pListener->pCone) == PARAM_CHECK_OK, + "Invalid listener cone" + ); + } + VECTOR_NORMAL_CHECK(pListener->OrientFront); + VECTOR_NORMAL_CHECK(pListener->OrientTop); + VECTOR_BASE_CHECK(pListener->OrientFront, pListener->OrientTop); + + if (pEmitter->pCone) + { + VECTOR_NORMAL_CHECK(pEmitter->OrientFront); + PARAM_CHECK( + CheckCone(pEmitter->pCone) == PARAM_CHECK_OK, + "Invalid emitter cone" + ); + } + else if (Flags & F3DAUDIO_CALCULATE_EMITTER_ANGLE) + { + VECTOR_NORMAL_CHECK(pEmitter->OrientFront); + } + if (pEmitter->ChannelCount > 1) + { + /* Only used for multi-channel emitters */ + VECTOR_NORMAL_CHECK(pEmitter->OrientFront); + VECTOR_NORMAL_CHECK(pEmitter->OrientTop); + VECTOR_BASE_CHECK(pEmitter->OrientFront, pEmitter->OrientTop); + } + FLOAT_BETWEEN_CHECK(pEmitter->InnerRadius, 0.0f, FLT_MAX); + FLOAT_BETWEEN_CHECK(pEmitter->InnerRadiusAngle, 0.0f, F3DAUDIO_2PI / 4.0f); + PARAM_CHECK( + pEmitter->ChannelCount > 0, + "Invalid channel count for emitter" + ); + PARAM_CHECK( + pEmitter->ChannelRadius >= 0.0f, + "Invalid channel radius for emitter" + ); + if (pEmitter->ChannelCount > 1) + { + PARAM_CHECK( + pEmitter->pChannelAzimuths != NULL, + "Invalid channel azimuths for multi-channel emitter" + ); + if (pEmitter->pChannelAzimuths) + { + for (i = 0; i < pEmitter->ChannelCount; i += 1) + { + float currentAzimuth = pEmitter->pChannelAzimuths[i]; + FLOAT_BETWEEN_CHECK(currentAzimuth, 0.0f, F3DAUDIO_2PI); + if (currentAzimuth == F3DAUDIO_2PI) + { + PARAM_CHECK( + !(Flags & F3DAUDIO_CALCULATE_REDIRECT_TO_LFE), + "F3DAUDIO_CALCULATE_REDIRECT_TO_LFE valid only for" + " matrix calculations with emitters that have no LFE" + " channel" + ); + } + } + } + } + FLOAT_BETWEEN_CHECK(pEmitter->CurveDistanceScaler, FLT_MIN, FLT_MAX); + FLOAT_BETWEEN_CHECK(pEmitter->DopplerScaler, 0.0f, FLT_MAX); + + PARAM_CHECK( + CheckCurve(pEmitter->pVolumeCurve) == PARAM_CHECK_OK, + "Invalid Volume curve" + ); + PARAM_CHECK( + CheckCurve(pEmitter->pLFECurve) == PARAM_CHECK_OK, + "Invalid LFE curve" + ); + PARAM_CHECK( + CheckCurve(pEmitter->pLPFDirectCurve) == PARAM_CHECK_OK, + "Invalid LPFDirect curve" + ); + PARAM_CHECK( + CheckCurve(pEmitter->pLPFReverbCurve) == PARAM_CHECK_OK, + "Invalid LPFReverb curve" + ); + PARAM_CHECK( + CheckCurve(pEmitter->pReverbCurve) == PARAM_CHECK_OK, + "Invalid Reverb curve" + ); + + return PARAM_CHECK_OK; +} + +/* + * MATRIX CALCULATION + */ + +/* This function computes the distance either according to a curve if pCurve + * isn't NULL, or according to the inverse distance law 1/d otherwise. + */ +static inline float ComputeDistanceAttenuation( + float normalizedDistance, + F3DAUDIO_DISTANCE_CURVE *pCurve +) { + float res; + float alpha; + uint32_t n_points; + size_t i; + if (pCurve) + { + F3DAUDIO_DISTANCE_CURVE_POINT* points = pCurve->pPoints; + n_points = pCurve->PointCount; + + /* By definition, the first point in the curve must be 0.0f + * -Adrien + */ + + /* We advance i up until our normalizedDistance lies between the distances of + * the i_th and (i-1)_th points, or we reach the last point. + */ + for (i = 1; (i < n_points) && (normalizedDistance >= points[i].Distance); i += 1); + if (i == n_points) + { + /* We've reached the last point, so we use its value directly. + * Quote X3DAUDIO docs: + * "If an emitter moves beyond a distance of (CurveDistanceScaler × 1.0f), + * the last point on the curve is used to compute the volume output level." + */ + res = points[n_points - 1].DSPSetting; + } + else + { + /* We're between two points: the distance attenuation is the linear interpolation of the DSPSetting + * values defined by our points, according to the distance. + */ + alpha = (points[i].Distance - normalizedDistance) / (points[i].Distance - points[i - 1].Distance); + res = LERP(alpha, points[i].DSPSetting, points[i - 1].DSPSetting); + } + } + else + { + res = 1.0f; + if (normalizedDistance >= 1.0f) + { + res /= normalizedDistance; + } + } + return res; +} + +static inline float ComputeConeParameter( + float distance, + float angle, + float innerAngle, + float outerAngle, + float innerParam, + float outerParam +) { + /* When computing whether a point lies inside a cone, X3DAUDIO first determines + * whether the point is close enough to the apex of the cone. + * If it is, the innerParam is used. + * The following constant is the one that is used for this distance check; + * It is an approximation, found by manual binary search. + * TODO: find the exact value of the constant via automated binary search. */ + #define CONE_NULL_DISTANCE_TOLERANCE 1e-7 + + float halfInnerAngle, halfOuterAngle, alpha; + + /* Quote X3DAudio.h: + * "Set both cone angles to 0 or X3DAUDIO_2PI for omnidirectionality using + * only the outer or inner values respectively." + */ + if (innerAngle == 0.0f && outerAngle == 0.0f) + { + return outerParam; + } + if (innerAngle == F3DAUDIO_2PI && outerAngle == F3DAUDIO_2PI) + { + return innerParam; + } + + /* If we're within the inner angle, or close enough to the apex, we use + * the innerParam. */ + halfInnerAngle = innerAngle / 2.0f; + if (distance <= CONE_NULL_DISTANCE_TOLERANCE || angle <= halfInnerAngle) + { + return innerParam; + } + + /* If we're between the inner angle and the outer angle, we must use + * some interpolation of the innerParam and outerParam according to the + * distance between our angle and the inner and outer angles. + */ + halfOuterAngle = outerAngle / 2.0f; + if (angle <= halfOuterAngle) + { + alpha = (angle - halfInnerAngle) / (halfOuterAngle - halfInnerAngle); + + /* Sooo... This is awkward. MSDN doesn't say anything, but + * X3DAudio.h says that this should be lerped. However in + * practice the behaviour of X3DAudio isn't a lerp at all. It's + * easy to see with big (InnerAngle / OuterAngle) values. If we + * want accurate emulation, we'll need to either find what + * formula they use, or use a more advanced interpolation, like + * tricubic. + * + * TODO: HIGH_ACCURACY version. + * -Adrien + */ + return LERP(alpha, innerParam, outerParam); + } + + /* Otherwise, we're outside the outer angle, so we just return the outer param. */ + return outerParam; +} + +/* X3DAudio.h declares something like this, but the default (if emitter is NULL) + * volume curve is a *computed* inverse law, while on the other hand a curve + * leads to a piecewise linear function. So a "default curve" like this is + * pointless, not sure what X3DAudio does with it... + * -Adrien + */ +#if 0 +static F3DAUDIO_DISTANCE_CURVE_POINT DefaultVolumeCurvePoints[] = +{ + { 0.0f, 1.0f }, + { 1.0f, 0.0f } +}; +static F3DAUDIO_DISTANCE_CURVE DefaultVolumeCurve = +{ + DefaultVolumeCurvePoints, + ARRAY_COUNT(DefaultVolumeCurvePoints) +}; +#endif + +/* Here we declare the azimuths of every speaker for every speaker + * configuration, ordered by increasing angle, as well as the index to which + * they map in the final matrix for their respective configuration. It had to be + * reverse engineered by looking at the data from various X3DAudioCalculate() + * matrix results for the various speaker configurations; *in particular*, the + * azimuths are different from the ones in F3DAudio.h (and X3DAudio.h) for + * SPEAKER_STEREO (which is declared has having front L and R speakers in the + * bit mask, but in fact has L and R *side* speakers). LF speakers are + * deliberately not included in the SpeakerInfo list, rather, we store the index + * into a separate field (with a -1 sentinel value if it has no LF speaker). + * -Adrien + */ +typedef struct +{ + float azimuth; + uint32_t matrixIdx; +} SpeakerInfo; + +typedef struct +{ + uint32_t configMask; + const SpeakerInfo *speakers; + + /* Not strictly necessary because it can be inferred from the + * SpeakerCount field of the F3DAUDIO_HANDLE, but makes code much + * cleaner and less error prone + */ + uint32_t numNonLFSpeakers; + + int32_t LFSpeakerIdx; +} ConfigInfo; + +/* It is absolutely necessary that these are stored in increasing, *positive* + * azimuth order (i.e. all angles between [0; 2PI]), as we'll do a linear + * interval search inside FindSpeakerAzimuths. + * -Adrien + */ + +#define SPEAKER_AZIMUTH_CENTER 0.0f +#define SPEAKER_AZIMUTH_FRONT_RIGHT_OF_CENTER (F3DAUDIO_PI * 1.0f / 8.0f) +#define SPEAKER_AZIMUTH_FRONT_RIGHT (F3DAUDIO_PI * 1.0f / 4.0f) +#define SPEAKER_AZIMUTH_SIDE_RIGHT (F3DAUDIO_PI * 1.0f / 2.0f) +#define SPEAKER_AZIMUTH_BACK_RIGHT (F3DAUDIO_PI * 3.0f / 4.0f) +#define SPEAKER_AZIMUTH_BACK_CENTER F3DAUDIO_PI +#define SPEAKER_AZIMUTH_BACK_LEFT (F3DAUDIO_PI * 5.0f / 4.0f) +#define SPEAKER_AZIMUTH_SIDE_LEFT (F3DAUDIO_PI * 3.0f / 2.0f) +#define SPEAKER_AZIMUTH_FRONT_LEFT (F3DAUDIO_PI * 7.0f / 4.0f) +#define SPEAKER_AZIMUTH_FRONT_LEFT_OF_CENTER (F3DAUDIO_PI * 15.0f / 8.0f) + +const SpeakerInfo kMonoConfigSpeakers[] = +{ + { SPEAKER_AZIMUTH_CENTER, 0 }, +}; +const SpeakerInfo kStereoConfigSpeakers[] = +{ + { SPEAKER_AZIMUTH_SIDE_RIGHT, 1 }, + { SPEAKER_AZIMUTH_SIDE_LEFT, 0 }, +}; +const SpeakerInfo k2Point1ConfigSpeakers[] = +{ + { SPEAKER_AZIMUTH_SIDE_RIGHT, 1 }, + { SPEAKER_AZIMUTH_SIDE_LEFT, 0 }, +}; +const SpeakerInfo kSurroundConfigSpeakers[] = +{ + { SPEAKER_AZIMUTH_CENTER, 2 }, + { SPEAKER_AZIMUTH_FRONT_RIGHT, 1 }, + { SPEAKER_AZIMUTH_BACK_CENTER, 3 }, + { SPEAKER_AZIMUTH_FRONT_LEFT, 0 }, +}; +const SpeakerInfo kQuadConfigSpeakers[] = +{ + { SPEAKER_AZIMUTH_FRONT_RIGHT, 1 }, + { SPEAKER_AZIMUTH_BACK_RIGHT, 3 }, + { SPEAKER_AZIMUTH_BACK_LEFT, 2 }, + { SPEAKER_AZIMUTH_FRONT_LEFT, 0 }, +}; +const SpeakerInfo k4Point1ConfigSpeakers[] = +{ + { SPEAKER_AZIMUTH_FRONT_RIGHT, 1 }, + { SPEAKER_AZIMUTH_BACK_RIGHT, 4 }, + { SPEAKER_AZIMUTH_BACK_LEFT, 3 }, + { SPEAKER_AZIMUTH_FRONT_LEFT, 0 }, +}; +const SpeakerInfo k5Point1ConfigSpeakers[] = +{ + { SPEAKER_AZIMUTH_CENTER, 2 }, + { SPEAKER_AZIMUTH_FRONT_RIGHT, 1 }, + { SPEAKER_AZIMUTH_BACK_RIGHT, 5 }, + { SPEAKER_AZIMUTH_BACK_LEFT, 4 }, + { SPEAKER_AZIMUTH_FRONT_LEFT, 0 }, +}; +const SpeakerInfo k7Point1ConfigSpeakers[] = +{ + { SPEAKER_AZIMUTH_CENTER, 2 }, + { SPEAKER_AZIMUTH_FRONT_RIGHT_OF_CENTER, 7 }, + { SPEAKER_AZIMUTH_FRONT_RIGHT, 1 }, + { SPEAKER_AZIMUTH_BACK_RIGHT, 5 }, + { SPEAKER_AZIMUTH_BACK_LEFT, 4 }, + { SPEAKER_AZIMUTH_FRONT_LEFT, 0 }, + { SPEAKER_AZIMUTH_FRONT_LEFT_OF_CENTER, 6 }, +}; +const SpeakerInfo k5Point1SurroundConfigSpeakers[] = +{ + { SPEAKER_AZIMUTH_CENTER, 2 }, + { SPEAKER_AZIMUTH_FRONT_RIGHT, 1 }, + { SPEAKER_AZIMUTH_SIDE_RIGHT, 5 }, + { SPEAKER_AZIMUTH_SIDE_LEFT, 4 }, + { SPEAKER_AZIMUTH_FRONT_LEFT, 0 }, +}; +const SpeakerInfo k7Point1SurroundConfigSpeakers[] = +{ + { SPEAKER_AZIMUTH_CENTER, 2 }, + { SPEAKER_AZIMUTH_FRONT_RIGHT, 1 }, + { SPEAKER_AZIMUTH_SIDE_RIGHT, 7 }, + { SPEAKER_AZIMUTH_BACK_RIGHT, 5 }, + { SPEAKER_AZIMUTH_BACK_LEFT, 4 }, + { SPEAKER_AZIMUTH_SIDE_LEFT, 6 }, + { SPEAKER_AZIMUTH_FRONT_LEFT, 0 }, +}; + +/* With that organization, the index of the LF speaker into the matrix array + * strangely looks *exactly* like the mystery field in the F3DAUDIO_HANDLE!! + * We're keeping a separate field within ConfigInfo because it makes the code + * much cleaner, though. + * -Adrien + */ +const ConfigInfo kSpeakersConfigInfo[] = +{ + { SPEAKER_MONO, kMonoConfigSpeakers, ARRAY_COUNT(kMonoConfigSpeakers), -1 }, + { SPEAKER_STEREO, kStereoConfigSpeakers, ARRAY_COUNT(kStereoConfigSpeakers), -1 }, + { SPEAKER_2POINT1, k2Point1ConfigSpeakers, ARRAY_COUNT(k2Point1ConfigSpeakers), 2 }, + { SPEAKER_SURROUND, kSurroundConfigSpeakers, ARRAY_COUNT(kSurroundConfigSpeakers), -1 }, + { SPEAKER_QUAD, kQuadConfigSpeakers, ARRAY_COUNT(kQuadConfigSpeakers), -1 }, + { SPEAKER_4POINT1, k4Point1ConfigSpeakers, ARRAY_COUNT(k4Point1ConfigSpeakers), 2 }, + { SPEAKER_5POINT1, k5Point1ConfigSpeakers, ARRAY_COUNT(k5Point1ConfigSpeakers), 3 }, + { SPEAKER_7POINT1, k7Point1ConfigSpeakers, ARRAY_COUNT(k7Point1ConfigSpeakers), 3 }, + { SPEAKER_5POINT1_SURROUND, k5Point1SurroundConfigSpeakers, ARRAY_COUNT(k5Point1SurroundConfigSpeakers), 3 }, + { SPEAKER_7POINT1_SURROUND, k7Point1SurroundConfigSpeakers, ARRAY_COUNT(k7Point1SurroundConfigSpeakers), 3 }, +}; + +/* A simple linear search is absolutely OK for 10 elements. */ +static const ConfigInfo* GetConfigInfo(uint32_t speakerConfigMask) +{ + uint32_t i; + for (i = 0; i < ARRAY_COUNT(kSpeakersConfigInfo); i += 1) + { + if (kSpeakersConfigInfo[i].configMask == speakerConfigMask) + { + return &kSpeakersConfigInfo[i]; + } + } + + FAudio_assert(0 && "Config info not found!"); + return NULL; +} + +/* Given a configuration, this function finds the azimuths of the two speakers + * between which the emitter lies. All the azimuths here are relative to the + * listener's base, since that's where the speakers are defined. + */ +static inline void FindSpeakerAzimuths( + const ConfigInfo* config, + float emitterAzimuth, + uint8_t skipCenter, + const SpeakerInfo **speakerInfo +) { + uint32_t i, nexti = 0; + float a0 = 0.0f, a1 = 0.0f; + + FAudio_assert(config != NULL); + + /* We want to find, given an azimuth, which speakers are the closest + * ones (in terms of angle) to that azimuth. + * This is done by iterating through the list of speaker azimuths, as + * given to us by the current ConfigInfo (which stores speaker azimuths + * in increasing order of azimuth for each possible speaker configuration; + * each speaker azimuth is defined to be between 0 and 2PI by construction). + */ + for (i = 0; i < config->numNonLFSpeakers; i += 1) + { + /* a0 and a1 are the azimuths of candidate speakers */ + a0 = config->speakers[i].azimuth; + nexti = (i + 1) % config->numNonLFSpeakers; + a1 = config->speakers[nexti].azimuth; + + if (a0 < a1) + { + if (emitterAzimuth >= a0 && emitterAzimuth < a1) + { + break; + } + } + /* It is possible for a speaker pair to enclose the singulary at 0 == 2PI: + * consider for example the quad config, which has a front left speaker + * at 7PI/4 and a front right speaker at PI/4. In that case a0 = 7PI/4 and + * a1 = PI/4, and the way we know whether our current azimuth lies between + * that pair is by checking whether the azimuth is greather than 7PI/4 or + * whether it's less than PI/4. (By contract, currentAzimuth is always less + * than 2PI.) + */ + else + { + if (emitterAzimuth >= a0 || emitterAzimuth < a1) + { + break; + } + } + } + FAudio_assert(emitterAzimuth >= a0 || emitterAzimuth < a1); + + /* skipCenter means that we don't want to use the center speaker. + * The easiest way to deal with this is to check whether either of our candidate + * speakers are the center, which always has an azimuth of 0.0. If that is the case + * we just replace it with either the previous one or the next one. + */ + if (skipCenter) + { + if (a0 == 0.0f) + { + if (i == 0) + { + i = config->numNonLFSpeakers - 1; + } + else + { + i -= 1; + } + } + else if (a1 == 0.0f) + { + nexti += 1; + if (nexti >= config->numNonLFSpeakers) + { + nexti -= config->numNonLFSpeakers; + } + } + } + speakerInfo[0] = &config->speakers[i]; + speakerInfo[1] = &config->speakers[nexti]; +} + +/* Used to store diffusion factors */ +/* See below for explanation. */ +#define DIFFUSION_SPEAKERS_ALL 0 +#define DIFFUSION_SPEAKERS_MATCHING 1 +#define DIFFUSION_SPEAKERS_OPPOSITE 2 +typedef float DiffusionSpeakerFactors[3]; + +/* ComputeInnerRadiusDiffusionFactors is a utility function that returns how + * energy dissipates to the speakers, given the radial distance between the + * emitter and the listener and the (optionally 0) InnerRadius distance. It + * returns 3 floats, via the diffusionFactors array, that say how much energy + * (after distance attenuation) will need to be distributed between each of the + * following cases: + * + * - SPEAKERS_ALL for all (non-LF) speakers, _INCLUDING_ the MATCHING and OPPOSITE. + * - SPEAKERS_OPPOSITE corresponds to the two speakers OPPOSITE the emitter. + * - SPEAKERS_MATCHING corresponds to the two speakers closest to the emitter. + * + * For a distance below a certain threshold (DISTANCE_EQUAL_ENERGY), all + * speakers receive equal energy. + * + * Above that, the amount that all speakers receive decreases linearly as radial + * distance increases, up until InnerRadius / 2. (If InnerRadius is null, we use + * MINIMUM_INNER_RADIUS.) + * + * At the same time, both opposite and matching speakers start to receive sound + * (in addition to the energy they receive from the aforementioned "all + * speakers" linear law) according to some unknown as of now law, + * that is currently emulated with a LERP. This is true up until InnerRadius. + * + * Above InnerRadius, only the two matching speakers receive sound. + * + * For more detail, see the "Inner Radius and Inner Radius Angle" in the + * MSDN docs for the X3DAUDIO_EMITTER structure. + * https://msdn.microsoft.com/en-us/library/windows/desktop/microsoft.directx_sdk.x3daudio.x3daudio_emitter(v=vs.85).aspx + */ +static inline void ComputeInnerRadiusDiffusionFactors( + float radialDistance, + float InnerRadius, + DiffusionSpeakerFactors diffusionFactors +) { + + /* Determined experimentally; this is the midpoint value, i.e. the + * value at 0.5 for the matching speakers, used for the standard + * diffusion curve. + * + * Note: It is SUSPICIOUSLY close to 1/sqrt(2), but I haven't figured out why. + * -Adrien + */ + #define DIFFUSION_LERP_MIDPOINT_VALUE 0.707107f + + /* X3DAudio always uses an InnerRadius-like behaviour (i.e. diffusing sound to more than + * a pair of speakers) even if InnerRadius is set to 0.0f. + * This constant determines the distance at which this behaviour is produced in that case. */ + /* This constant was determined by manual binary search. TODO: get a more accurate version + * via an automated binary search. */ + #define DIFFUSION_DISTANCE_MINIMUM_INNER_RADIUS 4e-7f + float actualInnerRadius = FAudio_max(InnerRadius, DIFFUSION_DISTANCE_MINIMUM_INNER_RADIUS); + float normalizedRadialDist; + float a, ms, os; + + normalizedRadialDist = radialDistance / actualInnerRadius; + + /* X3DAudio does another check for small radial distances before applying any InnerRadius-like + * behaviour. This is the constant that determines the threshold: below this distance we simply + * diffuse to all speakers equally. */ + #define DIFFUSION_DISTANCE_EQUAL_ENERGY 1e-7f + if (radialDistance <= DIFFUSION_DISTANCE_EQUAL_ENERGY) + { + a = 1.0f; + ms = 0.0f; + os = 0.0f; + } + else if (normalizedRadialDist <= 0.5f) + { + /* Determined experimentally that this is indeed a linear law, + * with 100% confidence. + * -Adrien + */ + a = 1.0f - 2.0f * normalizedRadialDist; + + /* Lerping here is an approximation. + * TODO: High accuracy version. Having stared at the curves long + * enough, I'm pretty sure this is a quadratic, but trying to + * polyfit with numpy didn't give nice, round polynomial + * coefficients... + * -Adrien + */ + ms = LERP(2.0f * normalizedRadialDist, 0.0f, DIFFUSION_LERP_MIDPOINT_VALUE); + os = 1.0f - a - ms; + } + else if (normalizedRadialDist <= 1.0f) + { + a = 0.0f; + + /* Similarly, this is a lerp based on the midpoint value; the + * real, high-accuracy curve also looks like a quadratic. + * -Adrien + */ + ms = LERP(2.0f * (normalizedRadialDist - 0.5f), DIFFUSION_LERP_MIDPOINT_VALUE, 1.0f); + os = 1.0f - a - ms; + } + else + { + a = 0.0f; + ms = 1.0f; + os = 0.0f; + } + diffusionFactors[DIFFUSION_SPEAKERS_ALL] = a; + diffusionFactors[DIFFUSION_SPEAKERS_MATCHING] = ms; + diffusionFactors[DIFFUSION_SPEAKERS_OPPOSITE] = os; +} + +/* ComputeEmitterChannelCoefficients handles the coefficients calculation for 1 + * column of the matrix. It uses ComputeInnerRadiusDiffusionFactors to separate + * into three discrete cases; and for each case does the right repartition of + * the energy after attenuation to the right speakers, in particular in the + * MATCHING and OPPOSITE cases, it gives each of the two speakers found a linear + * amount of the energy, according to the angular distance between the emitter + * and the speaker azimuth. + */ +static inline void ComputeEmitterChannelCoefficients( + const ConfigInfo *curConfig, + const F3DAUDIO_BASIS *listenerBasis, + float innerRadius, + F3DAUDIO_VECTOR channelPosition, + float attenuation, + float LFEattenuation, + uint32_t flags, + uint32_t currentChannel, + uint32_t numSrcChannels, + float *pMatrixCoefficients +) { + float elevation, radialDistance; + F3DAUDIO_VECTOR projTopVec, projPlane; + uint8_t skipCenter = (flags & F3DAUDIO_CALCULATE_ZEROCENTER) ? 1 : 0; + DiffusionSpeakerFactors diffusionFactors = { 0.0f }; + + float x, y; + float emitterAzimuth; + float energyPerChannel; + float totalEnergy; + uint32_t nChannelsToDiffuseTo; + uint32_t iS, centerChannelIdx = -1; + const SpeakerInfo* infos[2]; + float a0, a1, val; + uint32_t i0, i1; + + /* We project against the listener basis' top vector to get the elevation of the + * current emitter channel position. + */ + elevation = VectorDot(listenerBasis->top, channelPosition); + + /* To obtain the projection in the front-right plane of the listener's basis of the + * emitter channel position, we simply remove the projection against the top vector. + * The radial distance is then the length of the projected vector. + */ + projTopVec = VectorScale(listenerBasis->top, elevation); + projPlane = VectorSub(channelPosition, projTopVec); + radialDistance = VectorLength(projPlane); + + ComputeInnerRadiusDiffusionFactors( + radialDistance, + innerRadius, + diffusionFactors + ); + + /* See the ComputeInnerRadiusDiffusionFactors comment above for more context. */ + /* DIFFUSION_SPEAKERS_ALL corresponds to diffusing part of the sound to all of the + * speakers, equally. The amount of sound is determined by the float value + * diffusionFactors[DIFFUSION_SPEAKERS_ALL]. */ + if (diffusionFactors[DIFFUSION_SPEAKERS_ALL] > 0.0f) + { + nChannelsToDiffuseTo = curConfig->numNonLFSpeakers; + totalEnergy = diffusionFactors[DIFFUSION_SPEAKERS_ALL] * attenuation; + + if (skipCenter) + { + nChannelsToDiffuseTo -= 1; + FAudio_assert(curConfig->speakers[0].azimuth == SPEAKER_AZIMUTH_CENTER); + centerChannelIdx = curConfig->speakers[0].matrixIdx; + } + + energyPerChannel = totalEnergy / nChannelsToDiffuseTo; + + for (iS = 0; iS < curConfig->numNonLFSpeakers; iS += 1) + { + const uint32_t curSpeakerIdx = curConfig->speakers[iS].matrixIdx; + if (skipCenter && curSpeakerIdx == centerChannelIdx) + { + continue; + } + + pMatrixCoefficients[curSpeakerIdx * numSrcChannels + currentChannel] += energyPerChannel; + } + } + + /* DIFFUSION_SPEAKERS_MATCHING corresponds to sending part of the sound to the speakers closest + * (in terms of azimuth) to the current position of the emitter. The amount of sound we shoud send + * corresponds here to diffusionFactors[DIFFUSION_SPEAKERS_MATCHING]. + * We use the FindSpeakerAzimuths function to find the speakers that match. */ + if (diffusionFactors[DIFFUSION_SPEAKERS_MATCHING] > 0.0f) + { + const float totalEnergy = diffusionFactors[DIFFUSION_SPEAKERS_MATCHING] * attenuation; + + x = VectorDot(listenerBasis->front, projPlane); + y = VectorDot(listenerBasis->right, projPlane); + + /* Now, a critical point: We shouldn't be sending sound to + * matching speakers when x and y are close to 0. That's the + * contract we get from ComputeInnerRadiusDiffusionFactors, + * which checks that we're not too close to the zero distance. + * This allows the atan2 calculation to give good results. + */ + + /* atan2 returns [-PI, PI], but we want [0, 2PI] */ + emitterAzimuth = FAudio_atan2f(y, x); + if (emitterAzimuth < 0.0f) + { + emitterAzimuth += F3DAUDIO_2PI; + } + + FindSpeakerAzimuths(curConfig, emitterAzimuth, skipCenter, infos); + a0 = infos[0]->azimuth; + a1 = infos[1]->azimuth; + + /* The following code is necessary to handle the singularity in + * (0 == 2PI). It'll give us a nice, well ordered interval. + */ + if (a0 > a1) + { + if (emitterAzimuth >= a0) + { + emitterAzimuth -= F3DAUDIO_2PI; + } + a0 -= F3DAUDIO_2PI; + } + FAudio_assert(emitterAzimuth >= a0 && emitterAzimuth <= a1); + + val = (emitterAzimuth - a0) / (a1 - a0); + + i0 = infos[0]->matrixIdx; + i1 = infos[1]->matrixIdx; + + pMatrixCoefficients[i0 * numSrcChannels + currentChannel] += (1.0f - val) * totalEnergy; + pMatrixCoefficients[i1 * numSrcChannels + currentChannel] += ( val) * totalEnergy; + } + + /* DIFFUSION_SPEAKERS_OPPOSITE corresponds to sending part of the sound to the speakers + * _opposite_ the ones that are the closest to the current emitter position. + * To find these, we simply find the ones that are closest to the current emitter's azimuth + PI + * using the FindSpeakerAzimuth function. */ + if (diffusionFactors[DIFFUSION_SPEAKERS_OPPOSITE] > 0.0f) + { + /* This code is similar to the matching speakers code above. */ + const float totalEnergy = diffusionFactors[DIFFUSION_SPEAKERS_OPPOSITE] * attenuation; + + x = VectorDot(listenerBasis->front, projPlane); + y = VectorDot(listenerBasis->right, projPlane); + + /* Similarly, we expect atan2 to be well behaved here. */ + emitterAzimuth = FAudio_atan2f(y, x); + + /* Opposite speakers lie at azimuth + PI */ + emitterAzimuth += F3DAUDIO_PI; + + /* Normalize to [0; 2PI) range. */ + if (emitterAzimuth < 0.0f) + { + emitterAzimuth += F3DAUDIO_2PI; + } + else if (emitterAzimuth > F3DAUDIO_2PI) + { + emitterAzimuth -= F3DAUDIO_2PI; + } + + FindSpeakerAzimuths(curConfig, emitterAzimuth, skipCenter, infos); + a0 = infos[0]->azimuth; + a1 = infos[1]->azimuth; + + /* The following code is necessary to handle the singularity in + * (0 == 2PI). It'll give us a nice, well ordered interval. + */ + if (a0 > a1) + { + if (emitterAzimuth >= a0) + { + emitterAzimuth -= F3DAUDIO_2PI; + } + a0 -= F3DAUDIO_2PI; + } + FAudio_assert(emitterAzimuth >= a0 && emitterAzimuth <= a1); + + val = (emitterAzimuth - a0) / (a1 - a0); + + i0 = infos[0]->matrixIdx; + i1 = infos[1]->matrixIdx; + + pMatrixCoefficients[i0 * numSrcChannels + currentChannel] += (1.0f - val) * totalEnergy; + pMatrixCoefficients[i1 * numSrcChannels + currentChannel] += ( val) * totalEnergy; + } + + if (flags & F3DAUDIO_CALCULATE_REDIRECT_TO_LFE) + { + FAudio_assert(curConfig->LFSpeakerIdx != -1); + pMatrixCoefficients[curConfig->LFSpeakerIdx * numSrcChannels + currentChannel] += LFEattenuation / numSrcChannels; + } +} + +/* Calculations consist of several orthogonal steps that compose multiplicatively: + * + * First, we compute the attenuations (volume and LFE) due to distance, which + * may involve an optional volume and/or LFE volume curve. + * + * Then, we compute those due to optional cones. + * + * We then compute how much energy is diffuse w.r.t InnerRadius. If InnerRadius + * is 0.0f, this step is computed as if it was InnerRadius was + * NON_NULL_DISTANCE_DISK_RADIUS. The way this works is, we look at the radial + * distance of the current emitter channel to the listener, with regard to the + * listener's top orientation (i.e. this distance is independant of the + * emitter's elevation!). If this distance is less than NULL_DISTANCE_RADIUS, + * energy is diffused equally between all channels. If it's greater than + * InnerRadius (or NON_NULL_DISTANCE_RADIUS, if InnerRadius is 0.0f, as + * mentioned above), the two closest speakers, by azimuth, receive all the + * energy. Between InnerRadius/2.0f and InnerRadius, the energy starts bleeding + * into the opposite speakers. Once we go below InnerRadius/2.0f, the energy + * also starts to bleed into the other (non-opposite) channels, if there are + * any. This computation is handled by the ComputeInnerRadiusDiffusionFactors + * function. (TODO: High-accuracy version of this.) + * + * Finally, if we're not in the equal diffusion case, we find out the azimuths + * of the two closest speakers (with azimuth being defined with respect to the + * listener's front orientation, in the plane normal to the listener's top + * vector), as well as the azimuths of the two opposite speakers, if necessary, + * and linearly interpolate with respect to the angular distance. In the equal + * diffusion case, each channel receives the same value. + * + * Note: in the case of multi-channel emitters, the distance attenuation is only + * compted once, but all the azimuths and InnerRadius calculations are done per + * emitter channel. + * + * TODO: Handle InnerRadiusAngle. But honestly the X3DAudio default behaviour is + * so wacky that I wonder if anybody has ever used it. + * -Adrien + */ +static inline void CalculateMatrix( + uint32_t ChannelMask, + uint32_t Flags, + const F3DAUDIO_LISTENER *pListener, + const F3DAUDIO_EMITTER *pEmitter, + uint32_t SrcChannelCount, + uint32_t DstChannelCount, + F3DAUDIO_VECTOR emitterToListener, + float eToLDistance, + float normalizedDistance, + float* MatrixCoefficients +) { + uint32_t iEC; + float curEmAzimuth; + const ConfigInfo* curConfig = GetConfigInfo(ChannelMask); + float attenuation = ComputeDistanceAttenuation( + normalizedDistance, + pEmitter->pVolumeCurve + ); + /* TODO: this could be skipped if the destination has no LFE */ + float LFEattenuation = ComputeDistanceAttenuation( + normalizedDistance, + pEmitter->pLFECurve + ); + + F3DAUDIO_VECTOR listenerToEmitter; + F3DAUDIO_VECTOR listenerToEmChannel; + F3DAUDIO_BASIS listenerBasis; + + /* Note: For both cone calculations, the angle might be NaN or infinite + * if distance == 0... ComputeConeParameter *does* check for this + * special case. It is necessary that we still go through the + * ComputeConeParameter function, because omnidirectional cones might + * give either InnerVolume or OuterVolume. + * -Adrien + */ + if (pListener->pCone) + { + /* Negate the dot product because we need listenerToEmitter in + * this case + * -Adrien + */ + const float angle = -FAudio_acosf( + VectorDot(pListener->OrientFront, emitterToListener) / + eToLDistance + ); + + const float listenerConeParam = ComputeConeParameter( + eToLDistance, + angle, + pListener->pCone->InnerAngle, + pListener->pCone->OuterAngle, + pListener->pCone->InnerVolume, + pListener->pCone->OuterVolume + ); + attenuation *= listenerConeParam; + LFEattenuation *= listenerConeParam; + } + + /* See note above. */ + if (pEmitter->pCone && pEmitter->ChannelCount == 1) + { + const float angle = FAudio_acosf( + VectorDot(pEmitter->OrientFront, emitterToListener) / + eToLDistance + ); + + const float emitterConeParam = ComputeConeParameter( + eToLDistance, + angle, + pEmitter->pCone->InnerAngle, + pEmitter->pCone->OuterAngle, + pEmitter->pCone->InnerVolume, + pEmitter->pCone->OuterVolume + ); + attenuation *= emitterConeParam; + } + + FAudio_zero(MatrixCoefficients, sizeof(float) * SrcChannelCount * DstChannelCount); + + /* In the SPEAKER_MONO case, we can skip all energy diffusion calculation. */ + if (DstChannelCount == 1) + { + for (iEC = 0; iEC < pEmitter->ChannelCount; iEC += 1) + { + curEmAzimuth = 0.0f; + if (pEmitter->pChannelAzimuths) + { + curEmAzimuth = pEmitter->pChannelAzimuths[iEC]; + } + + /* The MONO setup doesn't have an LFE speaker. */ + if (curEmAzimuth != F3DAUDIO_2PI) + { + MatrixCoefficients[iEC] = attenuation; + } + } + } + else + { + listenerToEmitter = VectorScale(emitterToListener, -1.0f); + + /* Remember here that the coordinate system is Left-Handed. */ + listenerBasis.front = pListener->OrientFront; + listenerBasis.right = VectorCross(pListener->OrientTop, pListener->OrientFront); + listenerBasis.top = pListener->OrientTop; + + + /* Handling the mono-channel emitter case separately is easier + * than having it as a separate case of a for-loop; indeed, in + * this case, we need to ignore the non-relevant values from the + * emitter, _even if they're set_. + */ + if (pEmitter->ChannelCount == 1) + { + listenerToEmChannel = listenerToEmitter; + + ComputeEmitterChannelCoefficients( + curConfig, + &listenerBasis, + pEmitter->InnerRadius, + listenerToEmChannel, + attenuation, + LFEattenuation, + Flags, + 0 /* currentChannel */, + 1 /* numSrcChannels */, + MatrixCoefficients + ); + } + else /* Multi-channel emitter case. */ + { + const F3DAUDIO_VECTOR emitterRight = VectorCross(pEmitter->OrientTop, pEmitter->OrientFront); + + for (iEC = 0; iEC < pEmitter->ChannelCount; iEC += 1) + { + const float emChAzimuth = pEmitter->pChannelAzimuths[iEC]; + + /* LFEs are easy enough to deal with; we can + * just do them separately. + */ + if (emChAzimuth == F3DAUDIO_2PI) + { + MatrixCoefficients[curConfig->LFSpeakerIdx * pEmitter->ChannelCount + iEC] = LFEattenuation; + } + else + { + /* First compute the emitter channel + * vector relative to the emitter base... + */ + const F3DAUDIO_VECTOR emitterBaseToChannel = VectorAdd( + VectorScale(pEmitter->OrientFront, pEmitter->ChannelRadius * FAudio_cosf(emChAzimuth)), + VectorScale(emitterRight, pEmitter->ChannelRadius * FAudio_sinf(emChAzimuth)) + ); + /* ... then translate. */ + listenerToEmChannel = VectorAdd( + listenerToEmitter, + emitterBaseToChannel + ); + + ComputeEmitterChannelCoefficients( + curConfig, + &listenerBasis, + pEmitter->InnerRadius, + listenerToEmChannel, + attenuation, + LFEattenuation, + Flags, + iEC, + pEmitter->ChannelCount, + MatrixCoefficients + ); + } + } + } + + + } + + /* TODO: add post check to validate values + * (sum < 1, all values > 0, no Inf / NaN.. + * Sum can be >1 when cone or curve is set to a gain! + * Perhaps under a paranoid check disabled by default. + */ +} + +/* + * OTHER CALCULATIONS + */ + +/* DopplerPitchScalar + * Adapted from algorithm published as a part of the webaudio specification: + * https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#Spatialization-doppler-shift + * -Chad + */ +static inline void CalculateDoppler( + float SpeedOfSound, + const F3DAUDIO_LISTENER* pListener, + const F3DAUDIO_EMITTER* pEmitter, + F3DAUDIO_VECTOR emitterToListener, + float eToLDistance, + float* listenerVelocityComponent, + float* emitterVelocityComponent, + float* DopplerFactor +) { + float scaledSpeedOfSound; + *DopplerFactor = 1.0f; + + /* Project... */ + if (eToLDistance != 0.0f) + { + *listenerVelocityComponent = + VectorDot(emitterToListener, pListener->Velocity) / eToLDistance; + *emitterVelocityComponent = + VectorDot(emitterToListener, pEmitter->Velocity) / eToLDistance; + } + else + { + *listenerVelocityComponent = 0.0f; + *emitterVelocityComponent = 0.0f; + } + + if (pEmitter->DopplerScaler > 0.0f) + { + scaledSpeedOfSound = SpeedOfSound / pEmitter->DopplerScaler; + + /* Clamp... */ + *listenerVelocityComponent = FAudio_min( + *listenerVelocityComponent, + scaledSpeedOfSound + ); + *emitterVelocityComponent = FAudio_min( + *emitterVelocityComponent, + scaledSpeedOfSound + ); + + /* ... then Multiply. */ + *DopplerFactor = ( + SpeedOfSound - pEmitter->DopplerScaler * *listenerVelocityComponent + ) / ( + SpeedOfSound - pEmitter->DopplerScaler * *emitterVelocityComponent + ); + if (isnan(*DopplerFactor)) /* If emitter/listener are at the same pos... */ + { + *DopplerFactor = 1.0f; + } + + /* Limit the pitch shifting to 2 octaves up and 1 octave down */ + *DopplerFactor = FAudio_clamp( + *DopplerFactor, + 0.5f, + 4.0f + ); + } +} + +void F3DAudioCalculate( + const F3DAUDIO_HANDLE Instance, + const F3DAUDIO_LISTENER *pListener, + const F3DAUDIO_EMITTER *pEmitter, + uint32_t Flags, + F3DAUDIO_DSP_SETTINGS *pDSPSettings +) { + uint32_t i; + F3DAUDIO_VECTOR emitterToListener; + float eToLDistance, normalizedDistance, dp; + + #define DEFAULT_POINTS(name, x1, y1, x2, y2) \ + static F3DAUDIO_DISTANCE_CURVE_POINT name##Points[2] = \ + { \ + { x1, y1 }, \ + { x2, y2 } \ + }; \ + static F3DAUDIO_DISTANCE_CURVE name##Default = \ + { \ + (F3DAUDIO_DISTANCE_CURVE_POINT*) &name##Points[0], 2 \ + }; + DEFAULT_POINTS(lpfDirect, 0.0f, 1.0f, 1.0f, 0.75f) + DEFAULT_POINTS(lpfReverb, 0.0f, 0.75f, 1.0f, 0.75f) + DEFAULT_POINTS(reverb, 0.0f, 1.0f, 1.0f, 0.0f) + #undef DEFAULT_POINTS + + /* For XACT, this calculates "Distance" */ + emitterToListener = VectorSub(pListener->Position, pEmitter->Position); + eToLDistance = VectorLength(emitterToListener); + pDSPSettings->EmitterToListenerDistance = eToLDistance; + + F3DAudioCheckCalculateParams(Instance, pListener, pEmitter, Flags, pDSPSettings); + + /* This is used by MATRIX, LPF, and REVERB */ + normalizedDistance = eToLDistance / pEmitter->CurveDistanceScaler; + + if (Flags & F3DAUDIO_CALCULATE_MATRIX) + { + CalculateMatrix( + SPEAKERMASK(Instance), + Flags, + pListener, + pEmitter, + pDSPSettings->SrcChannelCount, + pDSPSettings->DstChannelCount, + emitterToListener, + eToLDistance, + normalizedDistance, + pDSPSettings->pMatrixCoefficients + ); + } + + if (Flags & F3DAUDIO_CALCULATE_LPF_DIRECT) + { + pDSPSettings->LPFDirectCoefficient = ComputeDistanceAttenuation( + normalizedDistance, + (pEmitter->pLPFDirectCurve != NULL) ? + pEmitter->pLPFDirectCurve : + &lpfDirectDefault + ); + } + + if (Flags & F3DAUDIO_CALCULATE_LPF_REVERB) + { + pDSPSettings->LPFReverbCoefficient = ComputeDistanceAttenuation( + normalizedDistance, + (pEmitter->pLPFReverbCurve != NULL) ? + pEmitter->pLPFReverbCurve : + &lpfReverbDefault + ); + } + + if (Flags & F3DAUDIO_CALCULATE_REVERB) + { + pDSPSettings->ReverbLevel = ComputeDistanceAttenuation( + normalizedDistance, + (pEmitter->pReverbCurve != NULL) ? + pEmitter->pReverbCurve : + &reverbDefault + ); + } + + /* For XACT, this calculates "DopplerPitchScalar" */ + if (Flags & F3DAUDIO_CALCULATE_DOPPLER) + { + CalculateDoppler( + SPEEDOFSOUND(Instance), + pListener, + pEmitter, + emitterToListener, + eToLDistance, + &pDSPSettings->ListenerVelocityComponent, + &pDSPSettings->EmitterVelocityComponent, + &pDSPSettings->DopplerFactor + ); + } + + /* For XACT, this calculates "OrientationAngle" */ + if (Flags & F3DAUDIO_CALCULATE_EMITTER_ANGLE) + { + /* Determined roughly. + * Below that distance, the emitter angle is considered to be PI/2. + */ + #define EMITTER_ANGLE_NULL_DISTANCE 1.2e-7 + if (eToLDistance < EMITTER_ANGLE_NULL_DISTANCE) + { + pDSPSettings->EmitterToListenerAngle = F3DAUDIO_PI / 2.0f; + } + else + { + /* Note: pEmitter->OrientFront is normalized. */ + dp = VectorDot(emitterToListener, pEmitter->OrientFront) / eToLDistance; + pDSPSettings->EmitterToListenerAngle = FAudio_acosf(dp); + } + } + + /* Unimplemented Flags */ + if ( (Flags & F3DAUDIO_CALCULATE_DELAY) && + SPEAKERMASK(Instance) == SPEAKER_STEREO ) + { + for (i = 0; i < pDSPSettings->DstChannelCount; i += 1) + { + pDSPSettings->pDelayTimes[i] = 0.0f; + } + FAudio_assert(0 && "DELAY not implemented!"); + } +} + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/src/FACT.c b/libs/faudio/src/FACT.c new file mode 100644 index 00000000000..5eca83b389f --- /dev/null +++ b/libs/faudio/src/FACT.c @@ -0,0 +1,3021 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * Ethan "flibitijibibo" Lee + * + */ + +#include "FAudioFX.h" +#include "FACT_internal.h" + +/* AudioEngine implementation */ + +uint32_t FACTCreateEngine( + uint32_t dwCreationFlags, + FACTAudioEngine **ppEngine +) { + return FACTCreateEngineWithCustomAllocatorEXT( + dwCreationFlags, + ppEngine, + FAudio_malloc, + FAudio_free, + FAudio_realloc + ); +} + +uint32_t FACTCreateEngineWithCustomAllocatorEXT( + uint32_t dwCreationFlags, + FACTAudioEngine **ppEngine, + FAudioMallocFunc customMalloc, + FAudioFreeFunc customFree, + FAudioReallocFunc customRealloc +) { + /* TODO: Anything fun with dwCreationFlags? */ + FAudio_PlatformAddRef(); + *ppEngine = (FACTAudioEngine*) customMalloc(sizeof(FACTAudioEngine)); + if (*ppEngine == NULL) + { + return -1; /* TODO: E_OUTOFMEMORY */ + } + FAudio_zero(*ppEngine, sizeof(FACTAudioEngine)); + (*ppEngine)->sbLock = FAudio_PlatformCreateMutex(); + (*ppEngine)->wbLock = FAudio_PlatformCreateMutex(); + (*ppEngine)->apiLock = FAudio_PlatformCreateMutex(); + (*ppEngine)->pMalloc = customMalloc; + (*ppEngine)->pFree = customFree; + (*ppEngine)->pRealloc = customRealloc; + (*ppEngine)->refcount = 1; + return 0; +} + +uint32_t FACTAudioEngine_AddRef(FACTAudioEngine *pEngine) +{ + FAudio_PlatformLockMutex(pEngine->apiLock); + pEngine->refcount += 1; + FAudio_PlatformUnlockMutex(pEngine->apiLock); + return pEngine->refcount; +} + +uint32_t FACTAudioEngine_Release(FACTAudioEngine *pEngine) +{ + FAudio_PlatformLockMutex(pEngine->apiLock); + pEngine->refcount -= 1; + if (pEngine->refcount > 0) + { + FAudio_PlatformUnlockMutex(pEngine->apiLock); + return pEngine->refcount; + } + FACTAudioEngine_ShutDown(pEngine); + FAudio_PlatformDestroyMutex(pEngine->sbLock); + FAudio_PlatformDestroyMutex(pEngine->wbLock); + FAudio_PlatformUnlockMutex(pEngine->apiLock); + FAudio_PlatformDestroyMutex(pEngine->apiLock); + if (pEngine->settings != NULL) + { + pEngine->pFree(pEngine->settings); + } + pEngine->pFree(pEngine); + FAudio_PlatformRelease(); + return 0; +} + +uint32_t FACTAudioEngine_GetRendererCount( + FACTAudioEngine *pEngine, + uint16_t *pnRendererCount +) { + FAudio_PlatformLockMutex(pEngine->apiLock); + *pnRendererCount = (uint16_t) FAudio_PlatformGetDeviceCount(); + FAudio_PlatformUnlockMutex(pEngine->apiLock); + return 0; +} + +uint32_t FACTAudioEngine_GetRendererDetails( + FACTAudioEngine *pEngine, + uint16_t nRendererIndex, + FACTRendererDetails *pRendererDetails +) { + FAudioDeviceDetails deviceDetails; + + FAudio_PlatformLockMutex(pEngine->apiLock); + + FAudio_PlatformGetDeviceDetails( + nRendererIndex, + &deviceDetails + ); + FAudio_memcpy( + pRendererDetails->rendererID, + deviceDetails.DeviceID, + sizeof(int16_t) * 0xFF + ); + FAudio_memcpy( + pRendererDetails->displayName, + deviceDetails.DisplayName, + sizeof(int16_t) * 0xFF + ); + /* FIXME: Which defaults does it care about...? */ + pRendererDetails->defaultDevice = (deviceDetails.Role & ( + FAudioGlobalDefaultDevice | + FAudioDefaultGameDevice + )) != 0; + + FAudio_PlatformUnlockMutex(pEngine->apiLock); + return 0; +} + +uint32_t FACTAudioEngine_GetFinalMixFormat( + FACTAudioEngine *pEngine, + FAudioWaveFormatExtensible *pFinalMixFormat +) { + FAudio_PlatformLockMutex(pEngine->apiLock); + FAudio_memcpy( + pFinalMixFormat, + &pEngine->audio->mixFormat, + sizeof(FAudioWaveFormatExtensible) + ); + FAudio_PlatformUnlockMutex(pEngine->apiLock); + return 0; +} + +uint32_t FACTAudioEngine_Initialize( + FACTAudioEngine *pEngine, + const FACTRuntimeParameters *pParams +) { + uint32_t parseRet; + uint32_t deviceIndex; + FAudioVoiceDetails masterDetails; + FAudioEffectDescriptor reverbDesc; + FAudioEffectChain reverbChain; + + FAudio_PlatformLockMutex(pEngine->apiLock); + + if (!pParams->pGlobalSettingsBuffer || pParams->globalSettingsBufferSize == 0) + { + /* No file? Just go with a safe default. (Also why are you using XACT) */ + pEngine->categoryCount = 3; + pEngine->variableCount = 0; + pEngine->rpcCount = 0; + pEngine->dspPresetCount = 0; + pEngine->dspParameterCount = 0; + + pEngine->categories = (FACTAudioCategory*) pEngine->pMalloc( + sizeof(FACTAudioCategory) * pEngine->categoryCount + ); + pEngine->categoryNames = (char**) pEngine->pMalloc( + sizeof(char*) * pEngine->categoryCount + ); + + pEngine->categoryNames[0] = pEngine->pMalloc(7); + FAudio_strlcpy(pEngine->categoryNames[0], "Global", 7); + pEngine->categories[0].instanceLimit = 255; + pEngine->categories[0].fadeInMS = 0; + pEngine->categories[0].fadeOutMS = 0; + pEngine->categories[0].maxInstanceBehavior = 0; + pEngine->categories[0].parentCategory = -1; + pEngine->categories[0].volume = 1.0f; + pEngine->categories[0].visibility = 1; + pEngine->categories[0].instanceCount = 0; + pEngine->categories[0].currentVolume = 1.0f; + + pEngine->categoryNames[1] = pEngine->pMalloc(8); + FAudio_strlcpy(pEngine->categoryNames[1], "Default", 8); + pEngine->categories[1].instanceLimit = 255; + pEngine->categories[1].fadeInMS = 0; + pEngine->categories[1].fadeOutMS = 0; + pEngine->categories[1].maxInstanceBehavior = 0; + pEngine->categories[1].parentCategory = 0; + pEngine->categories[1].volume = 1.0f; + pEngine->categories[1].visibility = 1; + pEngine->categories[1].instanceCount = 0; + pEngine->categories[1].currentVolume = 1.0f; + + pEngine->categoryNames[2] = pEngine->pMalloc(6); + FAudio_strlcpy(pEngine->categoryNames[2], "Music", 6); + pEngine->categories[2].instanceLimit = 255; + pEngine->categories[2].fadeInMS = 0; + pEngine->categories[2].fadeOutMS = 0; + pEngine->categories[2].maxInstanceBehavior = 0; + pEngine->categories[2].parentCategory = 0; + pEngine->categories[2].volume = 1.0f; + pEngine->categories[2].visibility = 1; + pEngine->categories[2].instanceCount = 0; + pEngine->categories[2].currentVolume = 1.0f; + + pEngine->variables = NULL; + pEngine->variableNames = NULL; + pEngine->globalVariableValues = NULL; + pEngine->rpcs = NULL; + pEngine->dspPresets = NULL; + } + else + { + /* Parse the file */ + parseRet = FACT_INTERNAL_ParseAudioEngine(pEngine, pParams); + if (parseRet != 0) + { + FAudio_PlatformUnlockMutex(pEngine->apiLock); + return parseRet; + } + } + + /* Peristent Notifications */ + pEngine->notifications = 0; + pEngine->cue_context = NULL; + pEngine->sb_context = NULL; + pEngine->wb_context = NULL; + pEngine->wave_context = NULL; + + /* Assign the callbacks */ + pEngine->notificationCallback = pParams->fnNotificationCallback; + pEngine->pReadFile = pParams->fileIOCallbacks.readFileCallback; + pEngine->pGetOverlappedResult = pParams->fileIOCallbacks.getOverlappedResultCallback; + if (pEngine->pReadFile == NULL) + { + pEngine->pReadFile = FACT_INTERNAL_DefaultReadFile; + } + if (pEngine->pGetOverlappedResult == NULL) + { + pEngine->pGetOverlappedResult = FACT_INTERNAL_DefaultGetOverlappedResult; + } + + /* Init the FAudio subsystem */ + pEngine->audio = pParams->pXAudio2; + if (pEngine->audio == NULL) + { + FAudio_assert(pParams->pMasteringVoice == NULL); + FAudioCreate(&pEngine->audio, 0, FAUDIO_DEFAULT_PROCESSOR); + } + + /* Create the audio device */ + pEngine->master = pParams->pMasteringVoice; + if (pEngine->master == NULL) + { + if (pParams->pRendererID == NULL || pParams->pRendererID[0] == 0) + { + deviceIndex = 0; + } + else + { + deviceIndex = pParams->pRendererID[0] - L'0'; + if (deviceIndex > FAudio_PlatformGetDeviceCount()) + { + deviceIndex = 0; + } + } + if (FAudio_CreateMasteringVoice( + pEngine->audio, + &pEngine->master, + FAUDIO_DEFAULT_CHANNELS, + FAUDIO_DEFAULT_SAMPLERATE, + 0, + deviceIndex, + NULL + ) != 0) { + FAudio_Release(pEngine->audio); + return FAUDIO_E_INVALID_CALL; + } + } + + /* Create the reverb effect, if applicable */ + if (pEngine->dspPresetCount > 0) /* Never more than 1...? */ + { + FAudioVoice_GetVoiceDetails(pEngine->master, &masterDetails); + + /* Reverb effect chain... */ + FAudioCreateReverb(&reverbDesc.pEffect, 0); + reverbDesc.InitialState = 1; + reverbDesc.OutputChannels = (masterDetails.InputChannels == 6) ? 6 : 1; + reverbChain.EffectCount = 1; + reverbChain.pEffectDescriptors = &reverbDesc; + + /* Reverb submix voice... */ + FAudio_CreateSubmixVoice( + pEngine->audio, + &pEngine->reverbVoice, + 1, /* Reverb will be omnidirectional */ + masterDetails.InputSampleRate, + 0, + 0, + NULL, + &reverbChain + ); + + /* We can release now, the submix owns this! */ + FAPOBase_Release((FAPOBase*) reverbDesc.pEffect); + } + + pEngine->initialized = 1; + pEngine->apiThread = FAudio_PlatformCreateThread( + FACT_INTERNAL_APIThread, + "FACT Thread", + pEngine + ); + + FAudio_PlatformUnlockMutex(pEngine->apiLock); + return 0; +} + +uint32_t FACTAudioEngine_ShutDown(FACTAudioEngine *pEngine) +{ + uint32_t i, refcount; + FAudioMutex mutex; + FAudioMallocFunc pMalloc; + FAudioFreeFunc pFree; + FAudioReallocFunc pRealloc; + + /* Close thread, then lock ASAP */ + pEngine->initialized = 0; + FAudio_PlatformWaitThread(pEngine->apiThread, NULL); + FAudio_PlatformLockMutex(pEngine->apiLock); + + /* Stop the platform stream before freeing stuff! */ + if (pEngine->audio != NULL) + { + FAudio_StopEngine(pEngine->audio); + } + + /* This method destroys all existing cues, sound banks, and wave banks. + * It blocks until all cues are destroyed. + */ + while (pEngine->wbList != NULL) + { + FACTWaveBank_Destroy((FACTWaveBank*) pEngine->wbList->entry); + } + while (pEngine->sbList != NULL) + { + FACTSoundBank_Destroy((FACTSoundBank*) pEngine->sbList->entry); + } + + /* Category data */ + for (i = 0; i < pEngine->categoryCount; i += 1) + { + pEngine->pFree(pEngine->categoryNames[i]); + } + pEngine->pFree(pEngine->categoryNames); + pEngine->pFree(pEngine->categories); + + /* Variable data */ + for (i = 0; i < pEngine->variableCount; i += 1) + { + pEngine->pFree(pEngine->variableNames[i]); + } + pEngine->pFree(pEngine->variableNames); + pEngine->pFree(pEngine->variables); + pEngine->pFree(pEngine->globalVariableValues); + + /* RPC data */ + for (i = 0; i < pEngine->rpcCount; i += 1) + { + pEngine->pFree(pEngine->rpcs[i].points); + } + pEngine->pFree(pEngine->rpcs); + pEngine->pFree(pEngine->rpcCodes); + + /* DSP data */ + for (i = 0; i < pEngine->dspPresetCount; i += 1) + { + pEngine->pFree(pEngine->dspPresets[i].parameters); + } + pEngine->pFree(pEngine->dspPresets); + pEngine->pFree(pEngine->dspPresetCodes); + + /* Audio resources */ + if (pEngine->reverbVoice != NULL) + { + FAudioVoice_DestroyVoice(pEngine->reverbVoice); + } + if (pEngine->master != NULL) + { + FAudioVoice_DestroyVoice(pEngine->master); + } + if (pEngine->audio != NULL) + { + FAudio_Release(pEngine->audio); + } + + /* Finally. */ + refcount = pEngine->refcount; + mutex = pEngine->apiLock; + pMalloc = pEngine->pMalloc; + pFree = pEngine->pFree; + pRealloc = pEngine->pRealloc; + FAudio_zero(pEngine, sizeof(FACTAudioEngine)); + pEngine->pMalloc = pMalloc; + pEngine->pFree = pFree; + pEngine->pRealloc = pRealloc; + pEngine->refcount = refcount; + pEngine->apiLock = mutex; + + FAudio_PlatformUnlockMutex(pEngine->apiLock); + return 0; +} + +uint32_t FACTAudioEngine_DoWork(FACTAudioEngine *pEngine) +{ + uint8_t i; + FACTCue *cue; + LinkedList *list; + + FAudio_PlatformLockMutex(pEngine->apiLock); + + list = pEngine->sbList; + while (list != NULL) + { + cue = ((FACTSoundBank*) list->entry)->cueList; + while (cue != NULL) + { + if (cue->playingSound != NULL) + for (i = 0; i < cue->playingSound->sound->trackCount; i += 1) + { + if ( cue->playingSound->tracks[i].upcomingWave.wave == NULL && + cue->playingSound->tracks[i].waveEvtInst->loopCount > 0 ) + { + FACT_INTERNAL_GetNextWave( + cue, + cue->playingSound->sound, + &cue->playingSound->sound->tracks[i], + &cue->playingSound->tracks[i], + cue->playingSound->tracks[i].waveEvt, + cue->playingSound->tracks[i].waveEvtInst + ); + } + } + cue = cue->next; + } + list = list->next; + } + + FAudio_PlatformUnlockMutex(pEngine->apiLock); + return 0; +} + +uint32_t FACTAudioEngine_CreateSoundBank( + FACTAudioEngine *pEngine, + const void *pvBuffer, + uint32_t dwSize, + uint32_t dwFlags, + uint32_t dwAllocAttributes, + FACTSoundBank **ppSoundBank +) { + uint32_t retval; + FAudio_PlatformLockMutex(pEngine->apiLock); + retval = FACT_INTERNAL_ParseSoundBank( + pEngine, + pvBuffer, + dwSize, + ppSoundBank + ); + FAudio_PlatformUnlockMutex(pEngine->apiLock); + return retval; +} + +uint32_t FACTAudioEngine_CreateInMemoryWaveBank( + FACTAudioEngine *pEngine, + const void *pvBuffer, + uint32_t dwSize, + uint32_t dwFlags, + uint32_t dwAllocAttributes, + FACTWaveBank **ppWaveBank +) { + uint32_t retval; + FAudio_PlatformLockMutex(pEngine->apiLock); + retval = FACT_INTERNAL_ParseWaveBank( + pEngine, + FAudio_memopen((void*) pvBuffer, dwSize), + 0, + 0, + FACT_INTERNAL_DefaultReadFile, + FACT_INTERNAL_DefaultGetOverlappedResult, + 0, + ppWaveBank + ); + FAudio_PlatformUnlockMutex(pEngine->apiLock); + return retval; +} + +uint32_t FACTAudioEngine_CreateStreamingWaveBank( + FACTAudioEngine *pEngine, + const FACTStreamingParameters *pParms, + FACTWaveBank **ppWaveBank +) { + uint32_t retval, packetSize; + FAudio_PlatformLockMutex(pEngine->apiLock); + if ( pEngine->pReadFile == FACT_INTERNAL_DefaultReadFile && + pEngine->pGetOverlappedResult == FACT_INTERNAL_DefaultGetOverlappedResult ) + { + /* Our I/O doesn't care about packets, set to 0 as an optimization */ + packetSize = 0; + } + else + { + packetSize = pParms->packetSize * 2048; + } + retval = FACT_INTERNAL_ParseWaveBank( + pEngine, + pParms->file, + pParms->offset, + packetSize, + pEngine->pReadFile, + pEngine->pGetOverlappedResult, + 1, + ppWaveBank + ); + FAudio_PlatformUnlockMutex(pEngine->apiLock); + return retval; +} + +uint32_t FACTAudioEngine_PrepareWave( + FACTAudioEngine *pEngine, + uint32_t dwFlags, + const char *szWavePath, + uint32_t wStreamingPacketSize, + uint32_t dwAlignment, + uint32_t dwPlayOffset, + uint8_t nLoopCount, + FACTWave **ppWave +) { + /* TODO: FACTWave */ + return 0; +} + +uint32_t FACTAudioEngine_PrepareInMemoryWave( + FACTAudioEngine *pEngine, + uint32_t dwFlags, + FACTWaveBankEntry entry, + uint32_t *pdwSeekTable, /* Optional! */ + uint8_t *pbWaveData, + uint32_t dwPlayOffset, + uint8_t nLoopCount, + FACTWave **ppWave +) { + /* TODO: FACTWave */ + return 0; +} + +uint32_t FACTAudioEngine_PrepareStreamingWave( + FACTAudioEngine *pEngine, + uint32_t dwFlags, + FACTWaveBankEntry entry, + FACTStreamingParameters streamingParams, + uint32_t dwAlignment, + uint32_t *pdwSeekTable, /* Optional! */ + uint8_t *pbWaveData, /* ABI bug, do not use! */ + uint32_t dwPlayOffset, + uint8_t nLoopCount, + FACTWave **ppWave +) { + /* TODO: FACTWave */ + return 0; +} + +uint32_t FACTAudioEngine_RegisterNotification( + FACTAudioEngine *pEngine, + const FACTNotificationDescription *pNotificationDescription +) { + FAudio_assert(pEngine != NULL); + FAudio_assert(pNotificationDescription != NULL); + FAudio_assert(pEngine->notificationCallback != NULL); + + FAudio_PlatformLockMutex(pEngine->apiLock); + + #define HANDLE_PERSIST(nt) \ + if (pNotificationDescription->type == FACTNOTIFICATIONTYPE_##nt) \ + { \ + if (pNotificationDescription->flags & FACT_FLAG_NOTIFICATION_PERSIST) \ + { \ + pEngine->notifications |= NOTIFY_##nt; \ + PERSIST_ACTION \ + } \ + else \ + { \ + FAudio_assert(0 && "TODO: "#nt" notification!"); \ + } \ + } + + /* Cues */ + #define PERSIST_ACTION pEngine->cue_context = pNotificationDescription->pvContext; + HANDLE_PERSIST(CUEPREPARED) + else HANDLE_PERSIST(CUEPLAY) + else HANDLE_PERSIST(CUESTOP) + else if (pNotificationDescription->type == FACTNOTIFICATIONTYPE_CUEDESTROYED) + { + if (pNotificationDescription->flags & FACT_FLAG_NOTIFICATION_PERSIST) + { + pEngine->notifications |= NOTIFY_CUEDESTROY; + pEngine->cue_context = pNotificationDescription->pvContext; + } + else + { + pNotificationDescription->pCue->notifyOnDestroy = 1; + pNotificationDescription->pCue->usercontext = pNotificationDescription->pvContext; + } + } + #undef PERSIST_ACTION + + /* Markers */ + #define PERSIST_ACTION + else HANDLE_PERSIST(MARKER) + #undef PERSIST_ACTION + + /* SoundBank/WaveBank Destruction */ + else if (pNotificationDescription->type == FACTNOTIFICATIONTYPE_SOUNDBANKDESTROYED) + { + if (pNotificationDescription->flags & FACT_FLAG_NOTIFICATION_PERSIST) + { + pEngine->notifications |= NOTIFY_SOUNDBANKDESTROY; + pEngine->sb_context = pNotificationDescription->pvContext; + } + else + { + pNotificationDescription->pSoundBank->notifyOnDestroy = 1; + pNotificationDescription->pSoundBank->usercontext = pNotificationDescription->pvContext; + } + } + else if (pNotificationDescription->type == FACTNOTIFICATIONTYPE_WAVEBANKDESTROYED) + { + if (pNotificationDescription->flags & FACT_FLAG_NOTIFICATION_PERSIST) + { + pEngine->notifications |= NOTIFY_WAVEBANKDESTROY; + pEngine->wb_context = pNotificationDescription->pvContext; + } + else + { + pNotificationDescription->pWaveBank->notifyOnDestroy = 1; + pNotificationDescription->pWaveBank->usercontext = pNotificationDescription->pvContext; + } + } + + /* Variables, Auditioning Tool */ + #define PERSIST_ACTION + else HANDLE_PERSIST(LOCALVARIABLECHANGED) + else HANDLE_PERSIST(GLOBALVARIABLECHANGED) + else HANDLE_PERSIST(GUICONNECTED) + else HANDLE_PERSIST(GUIDISCONNECTED) + #undef PERSIST_ACTION + + /* Waves */ + #define PERSIST_ACTION pEngine->wave_context = pNotificationDescription->pvContext; + else HANDLE_PERSIST(WAVEPREPARED) + else HANDLE_PERSIST(WAVEPLAY) + else HANDLE_PERSIST(WAVESTOP) + else HANDLE_PERSIST(WAVELOOPED) + else if (pNotificationDescription->type == FACTNOTIFICATIONTYPE_WAVEDESTROYED) + { + if (pNotificationDescription->flags & FACT_FLAG_NOTIFICATION_PERSIST) + { + pEngine->notifications |= NOTIFY_WAVEDESTROY; + pEngine->wave_context = pNotificationDescription->pvContext; + } + else + { + pNotificationDescription->pWave->notifyOnDestroy = 1; + pNotificationDescription->pWave->usercontext = pNotificationDescription->pvContext; + } + } + #undef PERSIST_ACTION + + /* WaveBanks */ + #define PERSIST_ACTION pEngine->wb_context = pNotificationDescription->pvContext; + else HANDLE_PERSIST(WAVEBANKPREPARED) + #undef PERSIST_ACTION + + /* Anything else? */ + else + { + FAudio_assert(0 && "TODO: Unimplemented notification!"); + } + + #undef HANDLE_PERSIST + + FAudio_PlatformUnlockMutex(pEngine->apiLock); + return 0; +} + +uint32_t FACTAudioEngine_UnRegisterNotification( + FACTAudioEngine *pEngine, + const FACTNotificationDescription *pNotificationDescription +) { + FAudio_assert(pEngine != NULL); + FAudio_assert(pNotificationDescription != NULL); + FAudio_assert(pEngine->notificationCallback != NULL); + + FAudio_PlatformLockMutex(pEngine->apiLock); + + #define HANDLE_PERSIST(nt) \ + if (pNotificationDescription->type == FACTNOTIFICATIONTYPE_##nt) \ + { \ + if (pNotificationDescription->flags & FACT_FLAG_NOTIFICATION_PERSIST) \ + { \ + pEngine->notifications &= ~NOTIFY_##nt; \ + PERSIST_ACTION \ + } \ + else \ + { \ + FAudio_assert(0 && "TODO: "#nt" notification!"); \ + } \ + } + + /* Cues */ + #define PERSIST_ACTION pEngine->cue_context = pNotificationDescription->pvContext; + HANDLE_PERSIST(CUEPREPARED) + else HANDLE_PERSIST(CUEPLAY) + else HANDLE_PERSIST(CUESTOP) + else if (pNotificationDescription->type == FACTNOTIFICATIONTYPE_CUEDESTROYED) + { + if (pNotificationDescription->flags & FACT_FLAG_NOTIFICATION_PERSIST) + { + pEngine->notifications &= ~NOTIFY_CUEDESTROY; + pEngine->cue_context = pNotificationDescription->pvContext; + } + else + { + pNotificationDescription->pCue->notifyOnDestroy = 0; + pNotificationDescription->pCue->usercontext = pNotificationDescription->pvContext; + } + } + #undef PERSIST_ACTION + + /* Markers */ + #define PERSIST_ACTION + else HANDLE_PERSIST(MARKER) + #undef PERSIST_ACTION + + /* SoundBank/WaveBank Destruction */ + else if (pNotificationDescription->type == FACTNOTIFICATIONTYPE_SOUNDBANKDESTROYED) + { + if (pNotificationDescription->flags & FACT_FLAG_NOTIFICATION_PERSIST) + { + pEngine->notifications &= ~NOTIFY_SOUNDBANKDESTROY; + pEngine->sb_context = pNotificationDescription->pvContext; + } + else + { + pNotificationDescription->pSoundBank->notifyOnDestroy = 0; + pNotificationDescription->pSoundBank->usercontext = pNotificationDescription->pvContext; + } + } + else if (pNotificationDescription->type == FACTNOTIFICATIONTYPE_WAVEBANKDESTROYED) + { + if (pNotificationDescription->flags & FACT_FLAG_NOTIFICATION_PERSIST) + { + pEngine->notifications &= ~NOTIFY_WAVEBANKDESTROY; + pEngine->wb_context = pNotificationDescription->pvContext; + } + else + { + pNotificationDescription->pWaveBank->notifyOnDestroy = 0; + pNotificationDescription->pWaveBank->usercontext = pNotificationDescription->pvContext; + } + } + + /* Variables, Auditioning Tool */ + #define PERSIST_ACTION + else HANDLE_PERSIST(LOCALVARIABLECHANGED) + else HANDLE_PERSIST(GLOBALVARIABLECHANGED) + else HANDLE_PERSIST(GUICONNECTED) + else HANDLE_PERSIST(GUIDISCONNECTED) + #undef PERSIST_ACTION + + /* Waves */ + #define PERSIST_ACTION pEngine->wave_context = pNotificationDescription->pvContext; + else HANDLE_PERSIST(WAVEPREPARED) + else HANDLE_PERSIST(WAVEPLAY) + else HANDLE_PERSIST(WAVESTOP) + else HANDLE_PERSIST(WAVELOOPED) + else if (pNotificationDescription->type == FACTNOTIFICATIONTYPE_WAVEDESTROYED) + { + if (pNotificationDescription->flags & FACT_FLAG_NOTIFICATION_PERSIST) + { + pEngine->notifications &= ~NOTIFY_WAVEDESTROY; + pEngine->wave_context = pNotificationDescription->pvContext; + } + else + { + pNotificationDescription->pWave->notifyOnDestroy = 0; + pNotificationDescription->pWave->usercontext = pNotificationDescription->pvContext; + } + } + #undef PERSIST_ACTION + + /* WaveBanks */ + #define PERSIST_ACTION pEngine->wb_context = pNotificationDescription->pvContext; + else HANDLE_PERSIST(WAVEBANKPREPARED) + #undef PERSIST_ACTION + + /* Anything else? */ + else + { + FAudio_assert(0 && "TODO: Unimplemented notification!"); + } + + #undef HANDLE_PERSIST + + FAudio_PlatformUnlockMutex(pEngine->apiLock); + return 0; +} + +uint16_t FACTAudioEngine_GetCategory( + FACTAudioEngine *pEngine, + const char *szFriendlyName +) { + uint16_t i; + FAudio_PlatformLockMutex(pEngine->apiLock); + for (i = 0; i < pEngine->categoryCount; i += 1) + { + if (FAudio_strcmp(szFriendlyName, pEngine->categoryNames[i]) == 0) + { + FAudio_PlatformUnlockMutex(pEngine->apiLock); + return i; + } + } + FAudio_PlatformUnlockMutex(pEngine->apiLock); + return FACTCATEGORY_INVALID; +} + +uint8_t FACT_INTERNAL_IsInCategory( + FACTAudioEngine *engine, + uint16_t target, + uint16_t category +) { + FACTAudioCategory *cat; + + /* Same category, no need to go on a crazy hunt */ + if (category == target) + { + return 1; + } + + /* Right, on with the crazy hunt */ + cat = &engine->categories[category]; + while (cat->parentCategory != -1) + { + if (cat->parentCategory == target) + { + return 1; + } + cat = &engine->categories[cat->parentCategory]; + } + return 0; +} + +uint32_t FACTAudioEngine_Stop( + FACTAudioEngine *pEngine, + uint16_t nCategory, + uint32_t dwFlags +) { + FACTCue *cue, *backup; + LinkedList *list; + + FAudio_PlatformLockMutex(pEngine->apiLock); + list = pEngine->sbList; + while (list != NULL) + { + cue = ((FACTSoundBank*) list->entry)->cueList; + while (cue != NULL) + { + if ( cue->playingSound != NULL && + FACT_INTERNAL_IsInCategory( + pEngine, + nCategory, + cue->playingSound->sound->category + ) ) + { + if ( dwFlags == FACT_FLAG_STOP_IMMEDIATE && + cue->managed ) + { + /* Just blow this up now */ + backup = cue->next; + FACTCue_Destroy(cue); + cue = backup; + } + else + { + /* If managed, the mixer will destroy for us */ + FACTCue_Stop(cue, dwFlags); + cue = cue->next; + } + } + else + { + cue = cue->next; + } + } + list = list->next; + } + FAudio_PlatformUnlockMutex(pEngine->apiLock); + return 0; +} + +uint32_t FACTAudioEngine_SetVolume( + FACTAudioEngine *pEngine, + uint16_t nCategory, + float volume +) { + uint16_t i; + FAudio_PlatformLockMutex(pEngine->apiLock); + pEngine->categories[nCategory].currentVolume = ( + pEngine->categories[nCategory].volume * + volume + ); + for (i = 0; i < pEngine->categoryCount; i += 1) + { + if (pEngine->categories[i].parentCategory == nCategory) + { + FACTAudioEngine_SetVolume( + pEngine, + i, + pEngine->categories[i].currentVolume + ); + } + } + FAudio_PlatformUnlockMutex(pEngine->apiLock); + return 0; +} + +uint32_t FACTAudioEngine_Pause( + FACTAudioEngine *pEngine, + uint16_t nCategory, + int32_t fPause +) { + FACTCue *cue; + LinkedList *list; + + FAudio_PlatformLockMutex(pEngine->apiLock); + list = pEngine->sbList; + while (list != NULL) + { + cue = ((FACTSoundBank*) list->entry)->cueList; + while (cue != NULL) + { + if ( cue->playingSound != NULL && + FACT_INTERNAL_IsInCategory( + pEngine, + nCategory, + cue->playingSound->sound->category + ) ) + { + FACTCue_Pause(cue, fPause); + } + cue = cue->next; + } + list = list->next; + } + FAudio_PlatformUnlockMutex(pEngine->apiLock); + return 0; +} + +uint16_t FACTAudioEngine_GetGlobalVariableIndex( + FACTAudioEngine *pEngine, + const char *szFriendlyName +) { + uint16_t i; + FAudio_PlatformLockMutex(pEngine->apiLock); + for (i = 0; i < pEngine->variableCount; i += 1) + { + if ( FAudio_strcmp(szFriendlyName, pEngine->variableNames[i]) == 0 && + !(pEngine->variables[i].accessibility & 0x04) ) + { + FAudio_PlatformUnlockMutex(pEngine->apiLock); + return i; + } + } + FAudio_PlatformUnlockMutex(pEngine->apiLock); + return FACTVARIABLEINDEX_INVALID; +} + +uint32_t FACTAudioEngine_SetGlobalVariable( + FACTAudioEngine *pEngine, + uint16_t nIndex, + float nValue +) { + FACTVariable *var; + + FAudio_PlatformLockMutex(pEngine->apiLock); + + var = &pEngine->variables[nIndex]; + FAudio_assert(var->accessibility & 0x01); + FAudio_assert(!(var->accessibility & 0x02)); + FAudio_assert(!(var->accessibility & 0x04)); + pEngine->globalVariableValues[nIndex] = FAudio_clamp( + nValue, + var->minValue, + var->maxValue + ); + + FAudio_PlatformUnlockMutex(pEngine->apiLock); + return 0; +} + +uint32_t FACTAudioEngine_GetGlobalVariable( + FACTAudioEngine *pEngine, + uint16_t nIndex, + float *pnValue +) { + FACTVariable *var; + + FAudio_PlatformLockMutex(pEngine->apiLock); + + var = &pEngine->variables[nIndex]; + FAudio_assert(var->accessibility & 0x01); + FAudio_assert(!(var->accessibility & 0x04)); + *pnValue = pEngine->globalVariableValues[nIndex]; + + FAudio_PlatformUnlockMutex(pEngine->apiLock); + return 0; +} + +/* SoundBank implementation */ + +uint16_t FACTSoundBank_GetCueIndex( + FACTSoundBank *pSoundBank, + const char *szFriendlyName +) { + uint16_t i; + if (pSoundBank == NULL) + { + return FACTINDEX_INVALID; + } + + FAudio_PlatformLockMutex(pSoundBank->parentEngine->apiLock); + if (pSoundBank->cueNames != NULL) + for (i = 0; i < pSoundBank->cueCount; i += 1) + { + if (FAudio_strcmp(szFriendlyName, pSoundBank->cueNames[i]) == 0) + { + FAudio_PlatformUnlockMutex( + pSoundBank->parentEngine->apiLock + ); + return i; + } + } + FAudio_PlatformUnlockMutex(pSoundBank->parentEngine->apiLock); + return FACTINDEX_INVALID; +} + +uint32_t FACTSoundBank_GetNumCues( + FACTSoundBank *pSoundBank, + uint16_t *pnNumCues +) { + if (pSoundBank == NULL) + { + *pnNumCues = 0; + return 0; + } + + FAudio_PlatformLockMutex(pSoundBank->parentEngine->apiLock); + *pnNumCues = pSoundBank->cueCount; + FAudio_PlatformUnlockMutex(pSoundBank->parentEngine->apiLock); + return 0; +} + +uint32_t FACTSoundBank_GetCueProperties( + FACTSoundBank *pSoundBank, + uint16_t nCueIndex, + FACTCueProperties *pProperties +) { + uint16_t i; + if (pSoundBank == NULL) + { + return 1; + } + + FAudio_PlatformLockMutex(pSoundBank->parentEngine->apiLock); + + if (pSoundBank->cueNames == NULL) + { + FAudio_zero(pProperties->friendlyName, 0xFF); + } + else + { + FAudio_strlcpy( + pProperties->friendlyName, + pSoundBank->cueNames[nCueIndex], + 0xFF + ); + } + if (!(pSoundBank->cues[nCueIndex].flags & 0x04)) + { + for (i = 0; i < pSoundBank->variationCount; i += 1) + { + if (pSoundBank->variationCodes[i] == pSoundBank->cues[nCueIndex].sbCode) + { + break; + } + } + + FAudio_assert(i < pSoundBank->variationCount && "Variation table not found!"); + + if (pSoundBank->variations[i].flags == 3) + { + pProperties->interactive = 1; + pProperties->iaVariableIndex = pSoundBank->variations[i].variable; + } + else + { + pProperties->interactive = 0; + pProperties->iaVariableIndex = 0; + } + pProperties->numVariations = pSoundBank->variations[i].entryCount; + } + else + { + pProperties->interactive = 0; + pProperties->iaVariableIndex = 0; + pProperties->numVariations = 0; + } + pProperties->maxInstances = pSoundBank->cues[nCueIndex].instanceLimit; + pProperties->currentInstances = pSoundBank->cues[nCueIndex].instanceCount; + + FAudio_PlatformUnlockMutex(pSoundBank->parentEngine->apiLock); + return 0; +} + +uint32_t FACTSoundBank_Prepare( + FACTSoundBank *pSoundBank, + uint16_t nCueIndex, + uint32_t dwFlags, + int32_t timeOffset, + FACTCue** ppCue +) { + uint16_t i; + FACTCue *latest; + + if (pSoundBank == NULL) + { + *ppCue = NULL; + return 1; + } + + *ppCue = (FACTCue*) pSoundBank->parentEngine->pMalloc(sizeof(FACTCue)); + FAudio_zero(*ppCue, sizeof(FACTCue)); + + FAudio_PlatformLockMutex(pSoundBank->parentEngine->apiLock); + + /* Engine references */ + (*ppCue)->parentBank = pSoundBank; + (*ppCue)->next = NULL; + (*ppCue)->managed = 0; + (*ppCue)->index = nCueIndex; + (*ppCue)->notifyOnDestroy = 0; + (*ppCue)->usercontext = NULL; + + /* Sound data */ + (*ppCue)->data = &pSoundBank->cues[nCueIndex]; + if ((*ppCue)->data->flags & 0x04) + { + for (i = 0; i < pSoundBank->soundCount; i += 1) + { + if ((*ppCue)->data->sbCode == pSoundBank->soundCodes[i]) + { + (*ppCue)->sound = &pSoundBank->sounds[i]; + break; + } + } + } + else + { + for (i = 0; i < pSoundBank->variationCount; i += 1) + { + if ((*ppCue)->data->sbCode == pSoundBank->variationCodes[i]) + { + (*ppCue)->variation = &pSoundBank->variations[i]; + break; + } + } + if ((*ppCue)->variation->flags == 3) + { + (*ppCue)->interactive = pSoundBank->parentEngine->variables[ + (*ppCue)->variation->variable + ].initialValue; + } + } + + /* Instance data */ + (*ppCue)->variableValues = (float*) pSoundBank->parentEngine->pMalloc( + sizeof(float) * pSoundBank->parentEngine->variableCount + ); + for (i = 0; i < pSoundBank->parentEngine->variableCount; i += 1) + { + (*ppCue)->variableValues[i] = + pSoundBank->parentEngine->variables[i].initialValue; + } + + /* Playback */ + (*ppCue)->state = FACT_STATE_PREPARED; + + /* Add to the SoundBank Cue list */ + if (pSoundBank->cueList == NULL) + { + pSoundBank->cueList = *ppCue; + } + else + { + latest = pSoundBank->cueList; + while (latest->next != NULL) + { + latest = latest->next; + } + latest->next = *ppCue; + } + + FAudio_PlatformUnlockMutex(pSoundBank->parentEngine->apiLock); + return 0; +} + +uint32_t FACTSoundBank_Play( + FACTSoundBank *pSoundBank, + uint16_t nCueIndex, + uint32_t dwFlags, + int32_t timeOffset, + FACTCue** ppCue /* Optional! */ +) { + FACTCue *result; + if (pSoundBank == NULL) + { + if (ppCue != NULL) + { + *ppCue = NULL; + } + return 1; + } + + FAudio_PlatformLockMutex(pSoundBank->parentEngine->apiLock); + + FACTSoundBank_Prepare( + pSoundBank, + nCueIndex, + dwFlags, + timeOffset, + &result + ); + if (ppCue != NULL) + { + *ppCue = result; + } + else + { + /* AKA we get to Destroy() this ourselves */ + result->managed = 1; + } + FACTCue_Play(result); + + FAudio_PlatformUnlockMutex(pSoundBank->parentEngine->apiLock); + return 0; +} + +uint32_t FACTSoundBank_Play3D( + FACTSoundBank *pSoundBank, + uint16_t nCueIndex, + uint32_t dwFlags, + int32_t timeOffset, + F3DAUDIO_DSP_SETTINGS *pDSPSettings, + FACTCue** ppCue /* Optional! */ +) { + FACTCue *result; + if (pSoundBank == NULL) + { + if (ppCue != NULL) + { + *ppCue = NULL; + } + return 1; + } + + FAudio_PlatformLockMutex(pSoundBank->parentEngine->apiLock); + + FACTSoundBank_Prepare( + pSoundBank, + nCueIndex, + dwFlags, + timeOffset, + &result + ); + if (ppCue != NULL) + { + *ppCue = result; + } + else + { + /* AKA we get to Destroy() this ourselves */ + result->managed = 1; + } + FACT3DApply(pDSPSettings, result); + FACTCue_Play(result); + + FAudio_PlatformUnlockMutex(pSoundBank->parentEngine->apiLock); + return 0; +} + +uint32_t FACTSoundBank_Stop( + FACTSoundBank *pSoundBank, + uint16_t nCueIndex, + uint32_t dwFlags +) { + FACTCue *backup, *cue; + if (pSoundBank == NULL) + { + return 1; + } + + FAudio_PlatformLockMutex(pSoundBank->parentEngine->apiLock); + cue = pSoundBank->cueList; + while (cue != NULL) + { + if (cue->index == nCueIndex) + { + if ( dwFlags == FACT_FLAG_STOP_IMMEDIATE && + cue->managed ) + { + /* Just blow this up now */ + backup = cue->next; + FACTCue_Destroy(cue); + cue = backup; + } + else + { + /* If managed, the mixer will destroy for us */ + FACTCue_Stop(cue, dwFlags); + cue = cue->next; + } + } + else + { + cue = cue->next; + } + } + FAudio_PlatformUnlockMutex(pSoundBank->parentEngine->apiLock); + return 0; +} + +uint32_t FACTSoundBank_Destroy(FACTSoundBank *pSoundBank) +{ + uint16_t i, j, k; + FAudioMutex mutex; + FACTNotification note; + if (pSoundBank == NULL) + { + return 1; + } + + FAudio_PlatformLockMutex(pSoundBank->parentEngine->apiLock); + + /* Synchronously destroys all cues that are associated */ + while (pSoundBank->cueList != NULL) + { + FACTCue_Destroy(pSoundBank->cueList); + } + + if (pSoundBank->parentEngine != NULL) + { + /* Remove this SoundBank from the Engine list */ + LinkedList_RemoveEntry( + &pSoundBank->parentEngine->sbList, + pSoundBank, + pSoundBank->parentEngine->sbLock, + pSoundBank->parentEngine->pFree + ); + } + + /* SoundBank Name */ + pSoundBank->parentEngine->pFree(pSoundBank->name); + + /* Cue data */ + pSoundBank->parentEngine->pFree(pSoundBank->cues); + + /* WaveBank Name data */ + for (i = 0; i < pSoundBank->wavebankCount; i += 1) + { + pSoundBank->parentEngine->pFree(pSoundBank->wavebankNames[i]); + } + pSoundBank->parentEngine->pFree(pSoundBank->wavebankNames); + + /* Sound data */ + for (i = 0; i < pSoundBank->soundCount; i += 1) + { + for (j = 0; j < pSoundBank->sounds[i].trackCount; j += 1) + { + for (k = 0; k < pSoundBank->sounds[i].tracks[j].eventCount; k += 1) + { + #define MATCH(t) \ + pSoundBank->sounds[i].tracks[j].events[k].type == t + if ( MATCH(FACTEVENT_PLAYWAVE) || + MATCH(FACTEVENT_PLAYWAVETRACKVARIATION) || + MATCH(FACTEVENT_PLAYWAVEEFFECTVARIATION) || + MATCH(FACTEVENT_PLAYWAVETRACKEFFECTVARIATION) ) + { + if (pSoundBank->sounds[i].tracks[j].events[k].wave.isComplex) + { + pSoundBank->parentEngine->pFree( + pSoundBank->sounds[i].tracks[j].events[k].wave.complex.tracks + ); + pSoundBank->parentEngine->pFree( + pSoundBank->sounds[i].tracks[j].events[k].wave.complex.wavebanks + ); + pSoundBank->parentEngine->pFree( + pSoundBank->sounds[i].tracks[j].events[k].wave.complex.weights + ); + } + } + #undef MATCH + } + pSoundBank->parentEngine->pFree( + pSoundBank->sounds[i].tracks[j].events + ); + } + pSoundBank->parentEngine->pFree(pSoundBank->sounds[i].tracks); + pSoundBank->parentEngine->pFree(pSoundBank->sounds[i].rpcCodes); + pSoundBank->parentEngine->pFree(pSoundBank->sounds[i].dspCodes); + } + pSoundBank->parentEngine->pFree(pSoundBank->sounds); + pSoundBank->parentEngine->pFree(pSoundBank->soundCodes); + + /* Variation data */ + for (i = 0; i < pSoundBank->variationCount; i += 1) + { + pSoundBank->parentEngine->pFree( + pSoundBank->variations[i].entries + ); + } + pSoundBank->parentEngine->pFree(pSoundBank->variations); + pSoundBank->parentEngine->pFree(pSoundBank->variationCodes); + + /* Transition data */ + for (i = 0; i < pSoundBank->transitionCount; i += 1) + { + pSoundBank->parentEngine->pFree( + pSoundBank->transitions[i].entries + ); + } + pSoundBank->parentEngine->pFree(pSoundBank->transitions); + pSoundBank->parentEngine->pFree(pSoundBank->transitionCodes); + + /* Cue Name data */ + if (pSoundBank->cueNames != NULL) + { + for (i = 0; i < pSoundBank->cueCount; i += 1) + { + pSoundBank->parentEngine->pFree(pSoundBank->cueNames[i]); + } + pSoundBank->parentEngine->pFree(pSoundBank->cueNames); + } + + /* Finally. */ + if (pSoundBank->notifyOnDestroy || pSoundBank->parentEngine->notifications & NOTIFY_SOUNDBANKDESTROY) + { + note.type = FACTNOTIFICATIONTYPE_SOUNDBANKDESTROYED; + note.soundBank.pSoundBank = pSoundBank; + if (pSoundBank->parentEngine->notifications & NOTIFY_SOUNDBANKDESTROY) + { + note.pvContext = pSoundBank->parentEngine->sb_context; + } + else + { + note.pvContext = pSoundBank->usercontext; + } + pSoundBank->parentEngine->notificationCallback(¬e); + } + + mutex = pSoundBank->parentEngine->apiLock; + pSoundBank->parentEngine->pFree(pSoundBank); + FAudio_PlatformUnlockMutex(mutex); + return 0; +} + +uint32_t FACTSoundBank_GetState( + FACTSoundBank *pSoundBank, + uint32_t *pdwState +) { + uint16_t i; + if (pSoundBank == NULL) + { + *pdwState = 0; + return 1; + } + + FAudio_PlatformLockMutex(pSoundBank->parentEngine->apiLock); + + *pdwState = FACT_STATE_PREPARED; + for (i = 0; i < pSoundBank->cueCount; i += 1) + { + if (pSoundBank->cues[i].instanceCount > 0) + { + *pdwState |= FACT_STATE_INUSE; + FAudio_PlatformUnlockMutex( + pSoundBank->parentEngine->apiLock + ); + return 0; + } + } + + FAudio_PlatformUnlockMutex(pSoundBank->parentEngine->apiLock); + return 0; +} + +/* WaveBank implementation */ + +uint32_t FACTWaveBank_Destroy(FACTWaveBank *pWaveBank) +{ + uint32_t i; + FACTWave *wave; + FAudioMutex mutex; + FACTNotification note; + if (pWaveBank == NULL) + { + return 1; + } + + FAudio_PlatformLockMutex(pWaveBank->parentEngine->apiLock); + + /* Synchronously destroys any cues that are using the wavebank */ + while (pWaveBank->waveList != NULL) + { + wave = (FACTWave*) pWaveBank->waveList->entry; + if (wave->parentCue != NULL) + { + /* Destroying this Cue destroys the Wave */ + FACTCue_Destroy(wave->parentCue); + } + else + { + FACTWave_Destroy(wave); + } + } + + if (pWaveBank->parentEngine != NULL) + { + /* Remove this WaveBank from the Engine list */ + LinkedList_RemoveEntry( + &pWaveBank->parentEngine->wbList, + pWaveBank, + pWaveBank->parentEngine->wbLock, + pWaveBank->parentEngine->pFree + ); + } + + /* Free everything, finally. */ + pWaveBank->parentEngine->pFree(pWaveBank->name); + pWaveBank->parentEngine->pFree(pWaveBank->entries); + pWaveBank->parentEngine->pFree(pWaveBank->entryRefs); + if (pWaveBank->seekTables != NULL) + { + for (i = 0; i < pWaveBank->entryCount; i += 1) + { + if (pWaveBank->seekTables[i].entries != NULL) + { + pWaveBank->parentEngine->pFree( + pWaveBank->seekTables[i].entries + ); + } + } + pWaveBank->parentEngine->pFree(pWaveBank->seekTables); + } + + if (!pWaveBank->streaming) + { + FAudio_close(pWaveBank->io); + } + + if (pWaveBank->packetBuffer != NULL) + { + pWaveBank->parentEngine->pFree(pWaveBank->packetBuffer); + } + if (pWaveBank->notifyOnDestroy || pWaveBank->parentEngine->notifications & NOTIFY_WAVEBANKDESTROY) + { + note.type = FACTNOTIFICATIONTYPE_WAVEBANKDESTROYED; + note.waveBank.pWaveBank = pWaveBank; + if (pWaveBank->parentEngine->notifications & NOTIFY_WAVEBANKDESTROY) + { + note.pvContext = pWaveBank->parentEngine->wb_context; + } + else + { + note.pvContext = pWaveBank->usercontext; + } + pWaveBank->parentEngine->notificationCallback(¬e); + } + FAudio_PlatformDestroyMutex(pWaveBank->waveLock); + + if (pWaveBank->waveBankNames != NULL) + { + pWaveBank->parentEngine->pFree(pWaveBank->waveBankNames); + } + + mutex = pWaveBank->parentEngine->apiLock; + pWaveBank->parentEngine->pFree(pWaveBank); + FAudio_PlatformUnlockMutex(mutex); + return 0; +} + +uint32_t FACTWaveBank_GetState( + FACTWaveBank *pWaveBank, + uint32_t *pdwState +) { + uint32_t i; + if (pWaveBank == NULL) + { + *pdwState = 0; + return 1; + } + + FAudio_PlatformLockMutex(pWaveBank->parentEngine->apiLock); + + *pdwState = FACT_STATE_PREPARED; + for (i = 0; i < pWaveBank->entryCount; i += 1) + { + if (pWaveBank->entryRefs[i] > 0) + { + *pdwState |= FACT_STATE_INUSE; + FAudio_PlatformUnlockMutex( + pWaveBank->parentEngine->apiLock + ); + return 0; + } + } + + FAudio_PlatformUnlockMutex(pWaveBank->parentEngine->apiLock); + return 0; +} + +uint32_t FACTWaveBank_GetNumWaves( + FACTWaveBank *pWaveBank, + uint16_t *pnNumWaves +) { + if (pWaveBank == NULL) + { + *pnNumWaves = 0; + return 1; + } + FAudio_PlatformLockMutex(pWaveBank->parentEngine->apiLock); + *pnNumWaves = pWaveBank->entryCount; + FAudio_PlatformUnlockMutex(pWaveBank->parentEngine->apiLock); + return 0; +} + +uint16_t FACTWaveBank_GetWaveIndex( + FACTWaveBank *pWaveBank, + const char *szFriendlyName +) { + uint16_t i; + char *curName; + if (pWaveBank == NULL || pWaveBank->waveBankNames == NULL) + { + return FACTINDEX_INVALID; + } + + FAudio_PlatformLockMutex(pWaveBank->parentEngine->apiLock); + curName = pWaveBank->waveBankNames; + for (i = 0; i < pWaveBank->entryCount; i += 1, curName += 64) + { + if (FAudio_strncmp(szFriendlyName, curName, 64) == 0) + { + FAudio_PlatformUnlockMutex(pWaveBank->parentEngine->apiLock); + return i; + } + } + FAudio_PlatformUnlockMutex(pWaveBank->parentEngine->apiLock); + + return FACTINDEX_INVALID; +} + +uint32_t FACTWaveBank_GetWaveProperties( + FACTWaveBank *pWaveBank, + uint16_t nWaveIndex, + FACTWaveProperties *pWaveProperties +) { + FACTWaveBankEntry *entry; + if (pWaveBank == NULL) + { + return 1; + } + + FAudio_PlatformLockMutex(pWaveBank->parentEngine->apiLock); + + entry = &pWaveBank->entries[nWaveIndex]; + + if (pWaveBank->waveBankNames) + { + FAudio_memcpy( + pWaveProperties->friendlyName, + &pWaveBank->waveBankNames[nWaveIndex * 64], + sizeof(pWaveProperties->friendlyName) + ); + } + else + { + FAudio_zero( + pWaveProperties->friendlyName, + sizeof(pWaveProperties->friendlyName) + ); + } + + pWaveProperties->format = entry->Format; + pWaveProperties->durationInSamples = entry->PlayRegion.dwLength; + if (entry->Format.wFormatTag == 0) + { + pWaveProperties->durationInSamples /= (8 << entry->Format.wBitsPerSample) / 8; + pWaveProperties->durationInSamples /= entry->Format.nChannels; + } + else if (entry->Format.wFormatTag == FAUDIO_FORMAT_MSADPCM) + { + pWaveProperties->durationInSamples = ( + pWaveProperties->durationInSamples / + ((entry->Format.wBlockAlign + 22) * entry->Format.nChannels) * + ((entry->Format.wBlockAlign + 16) * 2) + ); + } + else + { + FAudio_assert(0 && "Unrecognized wFormatTag!"); + } + + pWaveProperties->loopRegion = entry->LoopRegion; + pWaveProperties->streaming = pWaveBank->streaming; + + FAudio_PlatformUnlockMutex(pWaveBank->parentEngine->apiLock); + return 0; +} + +uint32_t FACTWaveBank_Prepare( + FACTWaveBank *pWaveBank, + uint16_t nWaveIndex, + uint32_t dwFlags, + uint32_t dwPlayOffset, + uint8_t nLoopCount, + FACTWave **ppWave +) { + FAudioBuffer buffer; + FAudioBufferWMA bufferWMA; + FAudioVoiceSends sends; + FAudioSendDescriptor send; + union + { + FAudioWaveFormatEx pcm; + FAudioADPCMWaveFormat adpcm; + FAudioXMA2WaveFormat xma2; + } format; + FACTWaveBankEntry *entry; + FACTSeekTable *seek; + if (pWaveBank == NULL) + { + *ppWave = NULL; + return 1; + } + + *ppWave = (FACTWave*) pWaveBank->parentEngine->pMalloc(sizeof(FACTWave)); + + FAudio_PlatformLockMutex(pWaveBank->parentEngine->apiLock); + + entry = &pWaveBank->entries[nWaveIndex]; + + /* Engine references */ + (*ppWave)->parentBank = pWaveBank; + (*ppWave)->parentCue = NULL; + (*ppWave)->index = nWaveIndex; + (*ppWave)->notifyOnDestroy = 0; + (*ppWave)->usercontext = NULL; + + /* Playback */ + (*ppWave)->state = FACT_STATE_PREPARED; + (*ppWave)->volume = 1.0f; + (*ppWave)->pitch = 0; + (*ppWave)->loopCount = nLoopCount; + + /* TODO: Convert dwPlayOffset to a byte offset */ + FAudio_assert(dwPlayOffset == 0); +#if 0 + if (dwFlags & FACT_FLAG_UNITS_MS) + { + dwPlayOffset = (uint32_t) ( + ( /* Samples per millisecond... */ + (float) entry->Format.nSamplesPerSec / + 1000.0f + ) * (float) dwPlayOffset + ); + } +#endif + + /* Create the voice */ + send.Flags = 0; + send.pOutputVoice = pWaveBank->parentEngine->master; + sends.SendCount = 1; + sends.pSends = &send; + format.pcm.nChannels = entry->Format.nChannels; + format.pcm.nSamplesPerSec = entry->Format.nSamplesPerSec; + if (entry->Format.wFormatTag == 0x0) + { + format.pcm.wFormatTag = FAUDIO_FORMAT_PCM; + format.pcm.wBitsPerSample = 8 << entry->Format.wBitsPerSample; + format.pcm.nBlockAlign = format.pcm.nChannels * format.pcm.wBitsPerSample / 8; + format.pcm.nAvgBytesPerSec = format.pcm.nBlockAlign * format.pcm.nSamplesPerSec; + format.pcm.cbSize = 0; + } + else if (entry->Format.wFormatTag == 0x1) + { + /* XMA2 is quite similar to WMA Pro... is what everyone thought. + * What a great way to start this comment. + * + * Let's reconstruct the extra data because who knows what decoder we're dealing with in . + * It's also a good exercise in understanding XMA2 metadata and feeding blocks into the decoder properly. + * At the time of writing this patch, it's FFmpeg via gstreamer which doesn't even respect most of this. + * ... which means: good luck to whoever ends up finding inaccuracies here in the future! + * + * dwLoopLength seems to match dwPlayLength in everything I've seen that had bLoopCount == 0. + * dwLoopBegin can be > 0 even with bLoopCount == 0 because why not. Let's ignore that. + * + * dwSamplesEncoded is usually close to dwPlayLength but not always (if ever?) equal. Let's assume equality. + * The XMA2 seek table uses sample indices as opposed to WMA's byte index seek table. + * + * nBlockAlign uses aWMABlockAlign given the entire WMA Pro thing BUT it's expected to be the block size for decoding. + * The XMA2 block size MUST be a multiple of 2048 BUT entry->PlayRegion.dwLength / seek->entryCount doesn't respect that. + * And even when correctly guesstimating the block size, we sometimes end up with block sizes >= 64k BYTES. nBlockAlign IS 16-BIT! + * Scrap nBlockAlign. I've given up and made all FAudio gstreamer functions use dwBytesPerBlock if available. + * Still though, if we don't want FAudio_INTERNAL_DecodeGSTREAMER to hang, the total data length must match (see SoundEffect.cs in FNA). + * As such, we round up when guessing the block size, feed GStreamer with zeroes^Wundersized blocks and hope for the best. + * + * This is FUN. + * -ade + */ + FAudio_assert(entry->Format.wBitsPerSample != 0); + + seek = &pWaveBank->seekTables[nWaveIndex]; + format.pcm.wFormatTag = FAUDIO_FORMAT_XMAUDIO2; + format.pcm.wBitsPerSample = 16; + format.pcm.nAvgBytesPerSec = aWMAAvgBytesPerSec[entry->Format.wBlockAlign >> 5]; + format.pcm.nBlockAlign = aWMABlockAlign[entry->Format.wBlockAlign & 0x1F]; + format.pcm.cbSize = ( + sizeof(FAudioXMA2WaveFormat) - + sizeof(FAudioWaveFormatEx) + ); + format.xma2.wNumStreams = (format.pcm.nChannels + 1) / 2; + format.xma2.dwChannelMask = format.pcm.nChannels > 1 ? 0xFFFFFFFF >> (32 - format.pcm.nChannels) : 0; + format.xma2.dwSamplesEncoded = seek->entries[seek->entryCount - 1]; + format.xma2.dwBytesPerBlock = (uint16_t) FAudio_ceil( + (double) entry->PlayRegion.dwLength / + (double) seek->entryCount / + 2048.0 + ) * 2048; + format.xma2.dwPlayBegin = format.xma2.dwLoopBegin = 0; + format.xma2.dwPlayLength = format.xma2.dwLoopLength = format.xma2.dwSamplesEncoded; + format.xma2.bLoopCount = 0; + format.xma2.bEncoderVersion = 4; + format.xma2.wBlockCount = seek->entryCount; + } + else if (entry->Format.wFormatTag == 0x2) + { + format.pcm.wFormatTag = FAUDIO_FORMAT_MSADPCM; + format.pcm.nBlockAlign = (entry->Format.wBlockAlign + 22) * format.pcm.nChannels; + format.pcm.wBitsPerSample = 16; + format.pcm.cbSize = ( + sizeof(FAudioADPCMWaveFormat) - + sizeof(FAudioWaveFormatEx) + ); + format.adpcm.wSamplesPerBlock = ( + ((format.pcm.nBlockAlign / format.pcm.nChannels) - 6) * 2 + ); + } + else if (entry->Format.wFormatTag == 0x3) + { + /* Apparently this is used to detect WMA Pro...? */ + FAudio_assert(entry->Format.wBitsPerSample == 0); + + format.pcm.wFormatTag = FAUDIO_FORMAT_WMAUDIO2; + format.pcm.nAvgBytesPerSec = aWMAAvgBytesPerSec[entry->Format.wBlockAlign >> 5]; + format.pcm.nBlockAlign = aWMABlockAlign[entry->Format.wBlockAlign & 0x1F]; + format.pcm.wBitsPerSample = 16; + format.pcm.cbSize = 0; + } + else + { + FAudio_assert(0 && "Rebuild your WaveBanks with ADPCM!"); + } + (*ppWave)->callback.callback.OnBufferEnd = pWaveBank->streaming ? + FACT_INTERNAL_OnBufferEnd : + NULL; + (*ppWave)->callback.callback.OnBufferStart = NULL; + (*ppWave)->callback.callback.OnLoopEnd = NULL; + (*ppWave)->callback.callback.OnStreamEnd = FACT_INTERNAL_OnStreamEnd; + (*ppWave)->callback.callback.OnVoiceError = NULL; + (*ppWave)->callback.callback.OnVoiceProcessingPassEnd = NULL; + (*ppWave)->callback.callback.OnVoiceProcessingPassStart = NULL; + (*ppWave)->callback.wave = *ppWave; + (*ppWave)->srcChannels = format.pcm.nChannels; + FAudio_CreateSourceVoice( + pWaveBank->parentEngine->audio, + &(*ppWave)->voice, + &format.pcm, + FAUDIO_VOICE_USEFILTER, /* FIXME: Can this be optional? */ + 4.0f, + (FAudioVoiceCallback*) &(*ppWave)->callback, + &sends, + NULL + ); + if (pWaveBank->streaming) + { + /* Init stream cache info */ + if (format.pcm.wFormatTag == FAUDIO_FORMAT_PCM) + { + (*ppWave)->streamSize = ( + format.pcm.nSamplesPerSec * + format.pcm.nBlockAlign + ); + } + else if (format.pcm.wFormatTag == FAUDIO_FORMAT_MSADPCM) + { + (*ppWave)->streamSize = ( + format.pcm.nSamplesPerSec / + format.adpcm.wSamplesPerBlock * + format.pcm.nBlockAlign + ); + } + else + { + /* Screw it, load the whole thing */ + (*ppWave)->streamSize = entry->PlayRegion.dwLength; + + /* XACT does NOT support loop subregions for these formats */ + FAudio_assert(entry->LoopRegion.dwStartSample == 0); + FAudio_assert(entry->LoopRegion.dwTotalSamples == 0 || entry->LoopRegion.dwTotalSamples == entry->Duration); + } + (*ppWave)->streamCache = (uint8_t*) pWaveBank->parentEngine->pMalloc( + (*ppWave)->streamSize + ); + (*ppWave)->streamOffset = entry->PlayRegion.dwOffset; + + /* Read and submit first buffer from the WaveBank */ + FACT_INTERNAL_OnBufferEnd(&(*ppWave)->callback.callback, NULL); + } + else + { + (*ppWave)->streamCache = NULL; + + buffer.Flags = FAUDIO_END_OF_STREAM; + buffer.AudioBytes = entry->PlayRegion.dwLength; + buffer.pAudioData = FAudio_memptr( + pWaveBank->io, + entry->PlayRegion.dwOffset + ); + buffer.PlayBegin = 0; + buffer.PlayLength = entry->Duration; + if (nLoopCount == 0) + { + buffer.LoopBegin = 0; + buffer.LoopLength = 0; + buffer.LoopCount = 0; + } + else + { + buffer.LoopBegin = entry->LoopRegion.dwStartSample; + buffer.LoopLength = entry->LoopRegion.dwTotalSamples; + buffer.LoopCount = nLoopCount; + } + buffer.pContext = NULL; + if (format.pcm.wFormatTag == FAUDIO_FORMAT_WMAUDIO2) + { + bufferWMA.pDecodedPacketCumulativeBytes = + pWaveBank->seekTables[nWaveIndex].entries; + bufferWMA.PacketCount = + pWaveBank->seekTables[nWaveIndex].entryCount; + FAudioSourceVoice_SubmitSourceBuffer( + (*ppWave)->voice, + &buffer, + &bufferWMA + ); + } + else + { + FAudioSourceVoice_SubmitSourceBuffer( + (*ppWave)->voice, + &buffer, + NULL + ); + } + } + + /* Add to the WaveBank Wave list */ + LinkedList_AddEntry( + &pWaveBank->waveList, + *ppWave, + pWaveBank->waveLock, + pWaveBank->parentEngine->pMalloc + ); + + FAudio_PlatformUnlockMutex(pWaveBank->parentEngine->apiLock); + return 0; +} + +uint32_t FACTWaveBank_Play( + FACTWaveBank *pWaveBank, + uint16_t nWaveIndex, + uint32_t dwFlags, + uint32_t dwPlayOffset, + uint8_t nLoopCount, + FACTWave **ppWave +) { + if (pWaveBank == NULL) + { + *ppWave = NULL; + return 1; + } + FAudio_PlatformLockMutex(pWaveBank->parentEngine->apiLock); + FACTWaveBank_Prepare( + pWaveBank, + nWaveIndex, + dwFlags, + dwPlayOffset, + nLoopCount, + ppWave + ); + FACTWave_Play(*ppWave); + FAudio_PlatformUnlockMutex(pWaveBank->parentEngine->apiLock); + return 0; +} + +uint32_t FACTWaveBank_Stop( + FACTWaveBank *pWaveBank, + uint16_t nWaveIndex, + uint32_t dwFlags +) { + FACTWave *wave; + LinkedList *list; + if (pWaveBank == NULL) + { + return 1; + } + FAudio_PlatformLockMutex(pWaveBank->parentEngine->apiLock); + list = pWaveBank->waveList; + while (list != NULL) + { + wave = (FACTWave*) list->entry; + if (wave->index == nWaveIndex) + { + FACTWave_Stop(wave, dwFlags); + } + list = list->next; + } + FAudio_PlatformUnlockMutex(pWaveBank->parentEngine->apiLock); + return 0; +} + +/* Wave implementation */ + +uint32_t FACTWave_Destroy(FACTWave *pWave) +{ + FAudioMutex mutex; + FACTNotification note; + if (pWave == NULL) + { + return 1; + } + + FAudio_PlatformLockMutex(pWave->parentBank->parentEngine->apiLock); + + /* Stop before we start deleting everything */ + FACTWave_Stop(pWave, FACT_FLAG_STOP_IMMEDIATE); + + LinkedList_RemoveEntry( + &pWave->parentBank->waveList, + pWave, + pWave->parentBank->waveLock, + pWave->parentBank->parentEngine->pFree + ); + + FAudioVoice_DestroyVoice(pWave->voice); + if (pWave->streamCache != NULL) + { + pWave->parentBank->parentEngine->pFree(pWave->streamCache); + } + if (pWave->notifyOnDestroy || pWave->parentBank->parentEngine->notifications & NOTIFY_WAVEDESTROY) + { + note.type = FACTNOTIFICATIONTYPE_WAVEDESTROYED; + note.wave.pWave = pWave; + if (pWave->parentBank->parentEngine->notifications & NOTIFY_WAVEDESTROY) + { + note.pvContext = pWave->parentBank->parentEngine->wave_context; + } + else + { + note.pvContext = pWave->usercontext; + } + pWave->parentBank->parentEngine->notificationCallback(¬e); + } + + mutex = pWave->parentBank->parentEngine->apiLock; + pWave->parentBank->parentEngine->pFree(pWave); + FAudio_PlatformUnlockMutex(mutex); + return 0; +} + +uint32_t FACTWave_Play(FACTWave *pWave) +{ + if (pWave == NULL) + { + return 1; + } + FAudio_PlatformLockMutex(pWave->parentBank->parentEngine->apiLock); + FAudio_assert(!(pWave->state & (FACT_STATE_PLAYING | FACT_STATE_STOPPING))); + pWave->state |= FACT_STATE_PLAYING; + pWave->state &= ~( + FACT_STATE_PAUSED | + FACT_STATE_STOPPED + ); + FAudioSourceVoice_Start(pWave->voice, 0, 0); + FAudio_PlatformUnlockMutex(pWave->parentBank->parentEngine->apiLock); + return 0; +} + +uint32_t FACTWave_Stop(FACTWave *pWave, uint32_t dwFlags) +{ + if (pWave == NULL) + { + return 1; + } + FAudio_PlatformLockMutex(pWave->parentBank->parentEngine->apiLock); + + /* There are two ways that a Wave might be stopped immediately: + * 1. The program explicitly asks for it + * 2. The Wave is paused and therefore we can't do fade/release effects + */ + if ( dwFlags & FACT_FLAG_STOP_IMMEDIATE || + pWave->state & FACT_STATE_PAUSED ) + { + pWave->state |= FACT_STATE_STOPPED; + pWave->state &= ~( + FACT_STATE_PLAYING | + FACT_STATE_STOPPING | + FACT_STATE_PAUSED + ); + FAudioSourceVoice_Stop(pWave->voice, 0, 0); + FAudioSourceVoice_FlushSourceBuffers(pWave->voice); + } + else + { + pWave->state |= FACT_STATE_STOPPING; + FAudioSourceVoice_ExitLoop(pWave->voice, 0); + } + + if (pWave->parentBank->parentEngine->notifications & NOTIFY_WAVESTOP) + { + FACTNotification note; + note.type = FACTNOTIFICATIONTYPE_WAVESTOP; + note.wave.pWave = pWave; + if (pWave->parentBank->parentEngine->notifications & NOTIFY_WAVESTOP) + { + note.pvContext = pWave->parentBank->parentEngine->wave_context; + } + pWave->parentBank->parentEngine->notificationCallback(¬e); + } + + FAudio_PlatformUnlockMutex(pWave->parentBank->parentEngine->apiLock); + return 0; +} + +uint32_t FACTWave_Pause(FACTWave *pWave, int32_t fPause) +{ + if (pWave == NULL) + { + return 1; + } + FAudio_PlatformLockMutex(pWave->parentBank->parentEngine->apiLock); + + /* FIXME: Does the Cue STOPPING/STOPPED rule apply here too? */ + if (pWave->state & (FACT_STATE_STOPPING | FACT_STATE_STOPPED)) + { + FAudio_PlatformUnlockMutex( + pWave->parentBank->parentEngine->apiLock + ); + return 0; + } + + /* All we do is set the flag, the mixer handles the rest */ + if (fPause) + { + pWave->state |= FACT_STATE_PAUSED; + FAudioSourceVoice_Stop(pWave->voice, 0, 0); + } + else + { + pWave->state &= ~FACT_STATE_PAUSED; + FAudioSourceVoice_Start(pWave->voice, 0, 0); + } + + FAudio_PlatformUnlockMutex(pWave->parentBank->parentEngine->apiLock); + return 0; +} + +uint32_t FACTWave_GetState(FACTWave *pWave, uint32_t *pdwState) +{ + if (pWave == NULL) + { + *pdwState = 0; + return 1; + } + FAudio_PlatformLockMutex(pWave->parentBank->parentEngine->apiLock); + *pdwState = pWave->state; + FAudio_PlatformUnlockMutex(pWave->parentBank->parentEngine->apiLock); + return 0; +} + +uint32_t FACTWave_SetPitch(FACTWave *pWave, int16_t pitch) +{ + if (pWave == NULL) + { + return 1; + } + FAudio_PlatformLockMutex(pWave->parentBank->parentEngine->apiLock); + pWave->pitch = FAudio_clamp( + pitch, + FACTPITCH_MIN_TOTAL, + FACTPITCH_MAX_TOTAL + ); + FAudioSourceVoice_SetFrequencyRatio( + pWave->voice, + (float) FAudio_pow(2.0, pWave->pitch / 1200.0), + 0 + ); + FAudio_PlatformUnlockMutex(pWave->parentBank->parentEngine->apiLock); + return 0; +} + +uint32_t FACTWave_SetVolume(FACTWave *pWave, float volume) +{ + if (pWave == NULL) + { + return 1; + } + FAudio_PlatformLockMutex(pWave->parentBank->parentEngine->apiLock); + pWave->volume = FAudio_clamp( + volume, + FACTVOLUME_MIN, + FACTVOLUME_MAX + ); + FAudioVoice_SetVolume( + pWave->voice, + pWave->volume, + 0 + ); + FAudio_PlatformUnlockMutex(pWave->parentBank->parentEngine->apiLock); + return 0; +} + +uint32_t FACTWave_SetMatrixCoefficients( + FACTWave *pWave, + uint32_t uSrcChannelCount, + uint32_t uDstChannelCount, + float *pMatrixCoefficients +) { + uint32_t i; + float *mtxDst, *mtxSrc, *mtxTmp = NULL; + if (pWave == NULL) + { + return 1; + } + + /* There seems to be this weird feature in XACT where the channel count + * can be completely wrong and it'll go to the right place. + * I guess these XACT functions do some extra work to merge coefficients + * but I have no idea where it really happens and XAudio2 definitely + * does NOT like it when this is wrong, so here it goes... + * -flibit + */ + if (uSrcChannelCount == 1 && pWave->srcChannels == 2) + { + mtxTmp = (float*) FAudio_alloca( + sizeof(float) * + pWave->srcChannels * + uDstChannelCount + ); + mtxDst = mtxTmp; + mtxSrc = pMatrixCoefficients; + for (i = 0; i < uDstChannelCount; i += 1) + { + mtxDst[0] = *mtxSrc; + mtxDst[1] = *mtxSrc; + mtxDst += 2; + mtxSrc += 1; + } + uSrcChannelCount = 2; + pMatrixCoefficients = mtxTmp; + } + else if (uSrcChannelCount == 2 && pWave->srcChannels == 1) + { + mtxTmp = (float*) FAudio_alloca( + sizeof(float) * + pWave->srcChannels * + uDstChannelCount + ); + mtxDst = mtxTmp; + mtxSrc = pMatrixCoefficients; + for (i = 0; i < uDstChannelCount; i += 1) + { + *mtxDst = (mtxSrc[0] + mtxSrc[1]) / 2.0f; + mtxDst += 1; + mtxSrc += 2; + } + uSrcChannelCount = 1; + pMatrixCoefficients = mtxTmp; + } + + FAudio_PlatformLockMutex(pWave->parentBank->parentEngine->apiLock); + + FAudioVoice_SetOutputMatrix( + pWave->voice, + pWave->voice->sends.pSends->pOutputVoice, + uSrcChannelCount, + uDstChannelCount, + pMatrixCoefficients, + 0 + ); + + FAudio_PlatformUnlockMutex(pWave->parentBank->parentEngine->apiLock); + if (mtxTmp != NULL) + { + FAudio_dealloca(mtxTmp); + } + return 0; +} + +uint32_t FACTWave_GetProperties( + FACTWave *pWave, + FACTWaveInstanceProperties *pProperties +) { + if (pWave == NULL) + { + return 1; + } + FAudio_PlatformLockMutex(pWave->parentBank->parentEngine->apiLock); + + FACTWaveBank_GetWaveProperties( + pWave->parentBank, + pWave->index, + &pProperties->properties + ); + + /* FIXME: This is unsupported on PC, do we care about this? */ + pProperties->backgroundMusic = 0; + + FAudio_PlatformUnlockMutex(pWave->parentBank->parentEngine->apiLock); + return 0; +} + +/* Cue implementation */ + +uint32_t FACTCue_Destroy(FACTCue *pCue) +{ + FACTCue *cue, *prev; + FAudioMutex mutex; + FACTNotification note; + if (pCue == NULL) + { + return 1; + } + + FAudio_PlatformLockMutex(pCue->parentBank->parentEngine->apiLock); + + /* Stop before we start deleting everything */ + FACTCue_Stop(pCue, FACT_FLAG_STOP_IMMEDIATE); + + /* Remove this Cue from the SoundBank list */ + cue = pCue->parentBank->cueList; + prev = cue; + while (cue != NULL) + { + if (cue == pCue) + { + if (cue == prev) /* First in list */ + { + pCue->parentBank->cueList = cue->next; + } + else + { + prev->next = cue->next; + } + break; + } + prev = cue; + cue = cue->next; + } + FAudio_assert(cue != NULL && "Could not find Cue reference!"); + + pCue->parentBank->parentEngine->pFree(pCue->variableValues); + if (pCue->notifyOnDestroy || pCue->parentBank->parentEngine->notifications & NOTIFY_CUEDESTROY) + { + note.type = FACTNOTIFICATIONTYPE_CUEDESTROYED; + note.cue.pCue = pCue; + if (pCue->parentBank->parentEngine->notifications & NOTIFY_CUEDESTROY) + { + note.pvContext = pCue->parentBank->parentEngine->cue_context; + } + else + { + note.pvContext = pCue->usercontext; + } + pCue->parentBank->parentEngine->notificationCallback(¬e); + } + + mutex = pCue->parentBank->parentEngine->apiLock; + pCue->parentBank->parentEngine->pFree(pCue); + FAudio_PlatformUnlockMutex(mutex); + return 0; +} + +uint32_t FACTCue_Play(FACTCue *pCue) +{ + union + { + float maxf; + uint8_t maxi; + } limitmax; + FACTCue *tmp, *wnr; + uint16_t fadeInMS = 0; + FACTCueData *data; + if (pCue == NULL) + { + return 1; + } + + FAudio_PlatformLockMutex(pCue->parentBank->parentEngine->apiLock); + + FAudio_assert(!(pCue->state & (FACT_STATE_PLAYING | FACT_STATE_STOPPING))); + + data = &pCue->parentBank->cues[pCue->index]; + + /* Cue Instance Limits */ + if (data->instanceCount >= data->instanceLimit) + { + wnr = NULL; + tmp = pCue->parentBank->cueList; + if (data->maxInstanceBehavior == 0) /* Fail */ + { + pCue->state |= FACT_STATE_STOPPED; + pCue->state &= ~( + FACT_STATE_PLAYING | + FACT_STATE_STOPPING | + FACT_STATE_PAUSED + ); + + FACT_INTERNAL_SendCueNotification(pCue, NOTIFY_CUESTOP, FACTNOTIFICATIONTYPE_CUESTOP); + + FAudio_PlatformUnlockMutex( + pCue->parentBank->parentEngine->apiLock + ); + return 1; + } + else if (data->maxInstanceBehavior == 1) /* Queue */ + { + /* FIXME: How is this different from Replace Oldest? */ + while (tmp != NULL) + { + if ( tmp != pCue && + tmp->index == pCue->index && + !(tmp->state & (FACT_STATE_STOPPING | FACT_STATE_STOPPED)) ) + { + wnr = tmp; + break; + } + tmp = tmp->next; + } + } + else if (data->maxInstanceBehavior == 2) /* Replace Oldest */ + { + while (tmp != NULL) + { + if ( tmp != pCue && + tmp->index == pCue->index && + !(tmp->state & (FACT_STATE_STOPPING | FACT_STATE_STOPPED)) ) + { + wnr = tmp; + break; + } + tmp = tmp->next; + } + } + else if (data->maxInstanceBehavior == 3) /* Replace Quietest */ + { + limitmax.maxf = FACTVOLUME_MAX; + while (tmp != NULL) + { + if ( tmp != pCue && + tmp->index == pCue->index && + tmp->playingSound != NULL && + /*FIXME: tmp->playingSound->volume < limitmax.maxf &&*/ + !(tmp->state & (FACT_STATE_STOPPING | FACT_STATE_STOPPED)) ) + { + wnr = tmp; + /* limitmax.maxf = tmp->playingSound->volume; */ + } + tmp = tmp->next; + } + } + else if (data->maxInstanceBehavior == 4) /* Replace Lowest Priority */ + { + limitmax.maxi = 0xFF; + while (tmp != NULL) + { + if ( tmp != pCue && + tmp->index == pCue->index && + tmp->playingSound != NULL && + tmp->playingSound->sound->priority < limitmax.maxi && + !(tmp->state & (FACT_STATE_STOPPING | FACT_STATE_STOPPED)) ) + { + wnr = tmp; + limitmax.maxi = tmp->playingSound->sound->priority; + } + tmp = tmp->next; + } + } + if (wnr != NULL) + { + fadeInMS = data->fadeInMS; + if (wnr->playingSound != NULL) + { + FACT_INTERNAL_BeginFadeOut(wnr->playingSound, data->fadeOutMS); + } + else + { + FACTCue_Stop(wnr, 0); + } + } + } + + /* Need an initial sound to play */ + if (!FACT_INTERNAL_CreateSound(pCue, fadeInMS)) + { + FAudio_PlatformUnlockMutex( + pCue->parentBank->parentEngine->apiLock + ); + return 1; + } + data->instanceCount += 1; + + pCue->state |= FACT_STATE_PLAYING; + pCue->state &= ~( + FACT_STATE_PAUSED | + FACT_STATE_STOPPED | + FACT_STATE_PREPARED + ); + + FACT_INTERNAL_SendCueNotification(pCue, NOTIFY_CUEPLAY, FACTNOTIFICATIONTYPE_CUEPLAY); + + pCue->start = FAudio_timems(); + + /* If it's a simple wave, just play it! */ + if (pCue->simpleWave != NULL) + { + if (pCue->active3D) + { + FACTWave_SetMatrixCoefficients( + pCue->simpleWave, + pCue->srcChannels, + pCue->dstChannels, + pCue->matrixCoefficients + ); + } + FACTWave_Play(pCue->simpleWave); + } + + FAudio_PlatformUnlockMutex(pCue->parentBank->parentEngine->apiLock); + return 0; +} + +uint32_t FACTCue_Stop(FACTCue *pCue, uint32_t dwFlags) +{ + if (pCue == NULL) + { + return 1; + } + FAudio_PlatformLockMutex(pCue->parentBank->parentEngine->apiLock); + + /* If we're already stopped, there's nothing to do... */ + if (pCue->state & FACT_STATE_STOPPED) + { + FAudio_PlatformUnlockMutex( + pCue->parentBank->parentEngine->apiLock + ); + return 0; + } + + /* If we're stopping and we haven't asked for IMMEDIATE, we're already + * doing what the application is asking us to do... + */ + if ( (pCue->state & FACT_STATE_STOPPING) && + !(dwFlags & FACT_FLAG_STOP_IMMEDIATE) ) + { + FAudio_PlatformUnlockMutex( + pCue->parentBank->parentEngine->apiLock + ); + return 0; + } + + /* There are three ways that a Cue might be stopped immediately: + * 1. The program explicitly asks for it + * 2. The Cue is paused and therefore we can't do fade/release effects + * 3. The Cue is stopped "as authored" and has no fade effects + */ + if ( dwFlags & FACT_FLAG_STOP_IMMEDIATE || + pCue->state & FACT_STATE_PAUSED || + pCue->playingSound == NULL || + ( pCue->parentBank->cues[pCue->index].fadeOutMS == 0 && + pCue->maxRpcReleaseTime == 0 ) ) + { + pCue->start = 0; + pCue->elapsed = 0; + pCue->state |= FACT_STATE_STOPPED; + pCue->state &= ~( + FACT_STATE_PLAYING | + FACT_STATE_STOPPING | + FACT_STATE_PAUSED + ); + + if (pCue->simpleWave != NULL) + { + FACTWave_Destroy(pCue->simpleWave); + pCue->simpleWave = NULL; + + pCue->data->instanceCount -= 1; + } + else if (pCue->playingSound != NULL) + { + FACT_INTERNAL_DestroySound(pCue->playingSound); + } + } + else + { + if (pCue->parentBank->cues[pCue->index].fadeOutMS > 0) + { + FACT_INTERNAL_BeginFadeOut( + pCue->playingSound, + pCue->parentBank->cues[pCue->index].fadeOutMS + ); + } + else if (pCue->maxRpcReleaseTime > 0) + { + FACT_INTERNAL_BeginReleaseRPC( + pCue->playingSound, + pCue->maxRpcReleaseTime + ); + } + else + { + /* Pretty sure this doesn't happen, but just in case? */ + pCue->state |= FACT_STATE_STOPPING; + } + } + + FAudio_PlatformUnlockMutex(pCue->parentBank->parentEngine->apiLock); + return 0; +} + +uint32_t FACTCue_GetState(FACTCue *pCue, uint32_t *pdwState) +{ + if (pCue == NULL) + { + *pdwState = 0; + return 1; + } + FAudio_PlatformLockMutex(pCue->parentBank->parentEngine->apiLock); + *pdwState = pCue->state; + FAudio_PlatformUnlockMutex(pCue->parentBank->parentEngine->apiLock); + return 0; +} + +uint32_t FACTCue_SetMatrixCoefficients( + FACTCue *pCue, + uint32_t uSrcChannelCount, + uint32_t uDstChannelCount, + float *pMatrixCoefficients +) { + uint8_t i; + + FAudio_PlatformLockMutex(pCue->parentBank->parentEngine->apiLock); + + /* See FACTCue.matrixCoefficients declaration */ + FAudio_assert(uSrcChannelCount > 0 && uSrcChannelCount < 3); + FAudio_assert(uDstChannelCount > 0 && uDstChannelCount < 9); + + /* Local storage */ + pCue->srcChannels = uSrcChannelCount; + pCue->dstChannels = uDstChannelCount; + FAudio_memcpy( + pCue->matrixCoefficients, + pMatrixCoefficients, + sizeof(float) * uSrcChannelCount * uDstChannelCount + ); + pCue->active3D = 1; + + /* Apply to Waves if they exist */ + if (pCue->simpleWave != NULL) + { + FACTWave_SetMatrixCoefficients( + pCue->simpleWave, + uSrcChannelCount, + uDstChannelCount, + pMatrixCoefficients + ); + } + else if (pCue->playingSound != NULL) + { + for (i = 0; i < pCue->playingSound->sound->trackCount; i += 1) + { + if (pCue->playingSound->tracks[i].activeWave.wave != NULL) + { + FACTWave_SetMatrixCoefficients( + pCue->playingSound->tracks[i].activeWave.wave, + uSrcChannelCount, + uDstChannelCount, + pMatrixCoefficients + ); + } + } + } + + FAudio_PlatformUnlockMutex(pCue->parentBank->parentEngine->apiLock); + return 0; +} + +uint16_t FACTCue_GetVariableIndex( + FACTCue *pCue, + const char *szFriendlyName +) { + uint16_t i; + if (pCue == NULL) + { + return FACTVARIABLEINDEX_INVALID; + } + FAudio_PlatformLockMutex(pCue->parentBank->parentEngine->apiLock); + for (i = 0; i < pCue->parentBank->parentEngine->variableCount; i += 1) + { + if ( FAudio_strcmp(szFriendlyName, pCue->parentBank->parentEngine->variableNames[i]) == 0 && + pCue->parentBank->parentEngine->variables[i].accessibility & 0x04 ) + { + FAudio_PlatformUnlockMutex( + pCue->parentBank->parentEngine->apiLock + ); + return i; + } + } + FAudio_PlatformUnlockMutex(pCue->parentBank->parentEngine->apiLock); + return FACTVARIABLEINDEX_INVALID; +} + +uint32_t FACTCue_SetVariable( + FACTCue *pCue, + uint16_t nIndex, + float nValue +) { + FACTVariable *var; + if (pCue == NULL) + { + return 1; + } + + if (nIndex == FACTINDEX_INVALID) + { + return 1; + } + + FAudio_PlatformLockMutex(pCue->parentBank->parentEngine->apiLock); + + var = &pCue->parentBank->parentEngine->variables[nIndex]; + FAudio_assert(var->accessibility & 0x01); + FAudio_assert(!(var->accessibility & 0x02)); + FAudio_assert(var->accessibility & 0x04); + pCue->variableValues[nIndex] = FAudio_clamp( + nValue, + var->minValue, + var->maxValue + ); + + FAudio_PlatformUnlockMutex(pCue->parentBank->parentEngine->apiLock); + return 0; +} + +uint32_t FACTCue_GetVariable( + FACTCue *pCue, + uint16_t nIndex, + float *nValue +) { + FACTVariable *var; + if (pCue == NULL) + { + *nValue = 0.0f; + return 1; + } + + if (nIndex == FACTINDEX_INVALID) + { + return 1; + } + + FAudio_PlatformLockMutex(pCue->parentBank->parentEngine->apiLock); + + var = &pCue->parentBank->parentEngine->variables[nIndex]; + FAudio_assert(var->accessibility & 0x01); + FAudio_assert(var->accessibility & 0x04); + + if (nIndex == 0) /* NumCueInstances */ + { + *nValue = pCue->parentBank->cues[pCue->index].instanceCount; + } + else + { + *nValue = pCue->variableValues[nIndex]; + } + + FAudio_PlatformUnlockMutex(pCue->parentBank->parentEngine->apiLock); + return 0; +} + +uint32_t FACTCue_Pause(FACTCue *pCue, int32_t fPause) +{ + uint8_t i; + if (pCue == NULL) + { + return 1; + } + + FAudio_PlatformLockMutex(pCue->parentBank->parentEngine->apiLock); + + /* "A stopping or stopped cue cannot be paused." */ + if (pCue->state & (FACT_STATE_STOPPING | FACT_STATE_STOPPED)) + { + FAudio_PlatformUnlockMutex( + pCue->parentBank->parentEngine->apiLock + ); + return 0; + } + + /* Store elapsed time */ + pCue->elapsed += FAudio_timems() - pCue->start; + + /* All we do is set the flag, not much to see here */ + if (fPause) + { + pCue->state |= FACT_STATE_PAUSED; + } + else + { + pCue->state &= ~FACT_STATE_PAUSED; + } + + /* Pause the Waves */ + if (pCue->simpleWave != NULL) + { + FACTWave_Pause(pCue->simpleWave, fPause); + } + else if (pCue->playingSound != NULL) + { + for (i = 0; i < pCue->playingSound->sound->trackCount; i += 1) + { + if (pCue->playingSound->tracks[i].activeWave.wave != NULL) + { + FACTWave_Pause( + pCue->playingSound->tracks[i].activeWave.wave, + fPause + ); + } + } + } + + FAudio_PlatformUnlockMutex(pCue->parentBank->parentEngine->apiLock); + return 0; +} + +uint32_t FACTCue_GetProperties( + FACTCue *pCue, + FACTCueInstanceProperties **ppProperties +) { + uint32_t i; + size_t allocSize; + FACTCueInstanceProperties *cueProps; + FACTVariationProperties *varProps; + FACTSoundProperties *sndProps; + FACTWaveInstanceProperties waveProps; + if (pCue == NULL) + { + return 1; + } + + FAudio_PlatformLockMutex(pCue->parentBank->parentEngine->apiLock); + + /* Alloc container (including variable length array space) */ + allocSize = sizeof(FACTCueInstanceProperties); + if (pCue->playingSound != NULL) + { + allocSize += ( + sizeof(FACTTrackProperties) * + pCue->playingSound->sound->trackCount + ); + } + cueProps = (FACTCueInstanceProperties*) pCue->parentBank->parentEngine->pMalloc( + allocSize + ); + FAudio_zero(cueProps, allocSize); + + /* Cue Properties */ + FACTSoundBank_GetCueProperties( + pCue->parentBank, + pCue->index, + &cueProps->cueProperties + ); + + /* Variation Properties */ + varProps = &cueProps->activeVariationProperties.variationProperties; + if (pCue->playingVariation != NULL) + { + varProps->index = 0; /* TODO: Index of what...? */ + /* TODO: This is just max - min right? Also why u8 wtf */ + varProps->weight = (uint8_t) ( + pCue->playingVariation->maxWeight - + pCue->playingVariation->minWeight + ); + if (pCue->variation->flags == 3) + { + varProps->iaVariableMin = + pCue->playingVariation->minWeight; + varProps->iaVariableMax = + pCue->playingVariation->maxWeight; + } + else + { + varProps->iaVariableMin = 0; + varProps->iaVariableMax = 0; + } + varProps->linger = pCue->playingVariation->linger; + } + + /* Sound Properties */ + sndProps = &cueProps->activeVariationProperties.soundProperties; + if (pCue->playingSound != NULL) + { + sndProps->category = pCue->playingSound->sound->category; + sndProps->priority = pCue->playingSound->sound->priority; + sndProps->pitch = pCue->playingSound->sound->pitch; + sndProps->volume = pCue->playingSound->sound->volume; + sndProps->numTracks = pCue->playingSound->sound->trackCount; + + for (i = 0; i < sndProps->numTracks; i += 1) + { + if (FACTWave_GetProperties( + pCue->playingSound->tracks[i].activeWave.wave, + &waveProps + ) == 0) { + sndProps->arrTrackProperties[i].duration = (uint32_t) ( + ( + (float) waveProps.properties.durationInSamples / + (float) waveProps.properties.format.nSamplesPerSec + ) / 1000.0f + ); + sndProps->arrTrackProperties[i].numVariations = 1; /* ? */ + sndProps->arrTrackProperties[i].numChannels = + waveProps.properties.format.nChannels; + sndProps->arrTrackProperties[i].waveVariation = 0; /* ? */ + sndProps->arrTrackProperties[i].loopCount = + pCue->playingSound->tracks[i].waveEvt->wave.loopCount; + } + } + } + + FAudio_PlatformUnlockMutex(pCue->parentBank->parentEngine->apiLock); + + *ppProperties = cueProps; + return 0; +} + +uint32_t FACTCue_SetOutputVoices( + FACTCue *pCue, + const FAudioVoiceSends *pSendList /* Optional XAUDIO2_VOICE_SENDS */ +) { + /* TODO */ + return 0; +} + +uint32_t FACTCue_SetOutputVoiceMatrix( + FACTCue *pCue, + const FAudioVoice *pDestinationVoice, /* Optional! */ + uint32_t SourceChannels, + uint32_t DestinationChannels, + const float *pLevelMatrix /* SourceChannels * DestinationChannels */ +) { + /* TODO */ + return 0; +} + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/src/FACT3D.c b/libs/faudio/src/FACT3D.c new file mode 100644 index 00000000000..c9a8f61a4d5 --- /dev/null +++ b/libs/faudio/src/FACT3D.c @@ -0,0 +1,172 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * Ethan "flibitijibibo" Lee + * + */ + +#include "FACT3D.h" + +uint32_t FACT3DInitialize( + FACTAudioEngine *pEngine, + F3DAUDIO_HANDLE F3DInstance +) { + float nSpeedOfSound; + FAudioWaveFormatExtensible wfxFinalMixFormat; + + if (pEngine == NULL) + { + return 0; + } + + FACTAudioEngine_GetGlobalVariable( + pEngine, + FACTAudioEngine_GetGlobalVariableIndex( + pEngine, + "SpeedOfSound" + ), + &nSpeedOfSound + ); + FACTAudioEngine_GetFinalMixFormat( + pEngine, + &wfxFinalMixFormat + ); + F3DAudioInitialize( + wfxFinalMixFormat.dwChannelMask, + nSpeedOfSound, + F3DInstance + ); + return 0; +} + +uint32_t FACT3DCalculate( + F3DAUDIO_HANDLE F3DInstance, + const F3DAUDIO_LISTENER *pListener, + F3DAUDIO_EMITTER *pEmitter, + F3DAUDIO_DSP_SETTINGS *pDSPSettings +) { + static F3DAUDIO_DISTANCE_CURVE_POINT DefaultCurvePoints[2] = + { + { 0.0f, 1.0f }, + { 1.0f, 1.0f } + }; + static F3DAUDIO_DISTANCE_CURVE DefaultCurve = + { + (F3DAUDIO_DISTANCE_CURVE_POINT*) &DefaultCurvePoints[0], 2 + }; + + if (pListener == NULL || pEmitter == NULL || pDSPSettings == NULL) + { + return 0; + } + + if (pEmitter->ChannelCount > 1 && pEmitter->pChannelAzimuths == NULL) + { + pEmitter->ChannelRadius = 1.0f; + + if (pEmitter->ChannelCount == 2) + { + pEmitter->pChannelAzimuths = (float*) &aStereoLayout[0]; + } + else if (pEmitter->ChannelCount == 3) + { + pEmitter->pChannelAzimuths = (float*) &a2Point1Layout[0]; + } + else if (pEmitter->ChannelCount == 4) + { + pEmitter->pChannelAzimuths = (float*) &aQuadLayout[0]; + } + else if (pEmitter->ChannelCount == 5) + { + pEmitter->pChannelAzimuths = (float*) &a4Point1Layout[0]; + } + else if (pEmitter->ChannelCount == 6) + { + pEmitter->pChannelAzimuths = (float*) &a5Point1Layout[0]; + } + else if (pEmitter->ChannelCount == 8) + { + pEmitter->pChannelAzimuths = (float*) &a7Point1Layout[0]; + } + else + { + return 0; + } + } + + if (pEmitter->pVolumeCurve == NULL) + { + pEmitter->pVolumeCurve = &DefaultCurve; + } + if (pEmitter->pLFECurve == NULL) + { + pEmitter->pLFECurve = &DefaultCurve; + } + + F3DAudioCalculate( + F3DInstance, + pListener, + pEmitter, + ( + F3DAUDIO_CALCULATE_MATRIX | + F3DAUDIO_CALCULATE_DOPPLER | + F3DAUDIO_CALCULATE_EMITTER_ANGLE + ), + pDSPSettings + ); + return 0; +} + +uint32_t FACT3DApply( + F3DAUDIO_DSP_SETTINGS *pDSPSettings, + FACTCue *pCue +) { + if (pDSPSettings == NULL || pCue == NULL) + { + return 0; + } + + FACTCue_SetMatrixCoefficients( + pCue, + pDSPSettings->SrcChannelCount, + pDSPSettings->DstChannelCount, + pDSPSettings->pMatrixCoefficients + ); + FACTCue_SetVariable( + pCue, + FACTCue_GetVariableIndex(pCue, "Distance"), + pDSPSettings->EmitterToListenerDistance + ); + FACTCue_SetVariable( + pCue, + FACTCue_GetVariableIndex(pCue, "DopplerPitchScalar"), + pDSPSettings->DopplerFactor + ); + FACTCue_SetVariable( + pCue, + FACTCue_GetVariableIndex(pCue, "OrientationAngle"), + pDSPSettings->EmitterToListenerAngle * (180.0f / F3DAUDIO_PI) + ); + return 0; +} + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/src/FACT_internal.c b/libs/faudio/src/FACT_internal.c new file mode 100644 index 00000000000..63b191c4aec --- /dev/null +++ b/libs/faudio/src/FACT_internal.c @@ -0,0 +1,3336 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * Ethan "flibitijibibo" Lee + * + */ + +#include "FACT_internal.h" +#include "FAudioFX.h" + +/* RNG */ + +#define STB_EXTERN +#define STB_DEFINE +#include "stb.h" +#define FACT_INTERNAL_rng() ((float) stb_frand()) + +/* XACT Versions */ + +#define FACT_CONTENT_VERSION_3_4 45 +#define FACT_CONTENT_VERSION_3_1 44 +#define FACT_CONTENT_VERSION_3_0 43 + +static inline int FACT_INTERNAL_SupportedContent(uint16_t version) +{ + return ( version == FACT_CONTENT_VERSION || + version == FACT_CONTENT_VERSION_3_4 || + version == FACT_CONTENT_VERSION_3_1 || + version == FACT_CONTENT_VERSION_3_0 ); +} + +#define WAVEBANK_HEADER_VERSION 44 +#define WAVEBANK_HEADER_VERSION_3_4 43 +#define WAVEBANK_HEADER_VERSION_3_1 42 + +static inline int FACT_INTERNAL_SupportedWBContent(uint16_t version) +{ + return ( version == WAVEBANK_HEADER_VERSION || + version == WAVEBANK_HEADER_VERSION_3_4 || + version == WAVEBANK_HEADER_VERSION_3_1 ); +} + +/* Helper Functions */ + +static inline float FACT_INTERNAL_CalculateAmplitudeRatio(float decibel) +{ + return (float) FAudio_pow(10.0, decibel / 2000.0); +} + +static inline float FACT_INTERNAL_CalculateFilterFrequency( + float desiredFrequency, + uint32_t sampleRate +) { + /* This is needed to convert linear frequencies to the value + * FAudio_INTERNAL_FilterVoice expects, in order for it to actually + * filter at the correct frequency. + * + * The formula is... + * + * (2 * sin(pi * (desired filter cutoff frequency) / sampleRate)) + * + * ... but it behaves badly as the filter frequency gets too high as a + * fraction of the sample rate, hence the mins. + * + * -@Woflox + */ + float freq = 2 * FAudio_sin( + F3DAUDIO_PI * + FAudio_min(desiredFrequency / sampleRate, 0.5f) + ); + return FAudio_min(freq, 1.0f); +} + +static inline void FACT_INTERNAL_ReadFile( + FACTReadFileCallback pReadFile, + FACTGetOverlappedResultCallback pGetOverlappedResult, + void* io, + uint32_t offset, + uint32_t packetSize, + uint8_t **packetBuffer, + uint32_t *packetBufferLen, + FAudioReallocFunc pRealloc, + void* dst, + uint32_t len +) { + FACTOverlapped ovlp; + uint32_t realOffset, realLen, offPacket, lenPacket, result; + uint8_t usePacketBuffer; + void *buf; + + ovlp.Internal = NULL; + ovlp.InternalHigh = NULL; + ovlp.OffsetHigh = 0; /* I sure hope so... */ + ovlp.hEvent = NULL; + + /* We have to read data in multiples of the sector size, or else + * Win32 ReadFile returns ERROR_INVALID_PARAMETER + */ + realOffset = offset; + realLen = len; + usePacketBuffer = 0; + if (packetSize > 0) + { + offPacket = realOffset % packetSize; + if (offPacket > 0) + { + usePacketBuffer = 1; + realOffset -= offPacket; + realLen += offPacket; + } + lenPacket = realLen % packetSize; + if (lenPacket > 0) + { + usePacketBuffer = 1; + realLen += (packetSize - lenPacket); + } + } + + /* If we're compensating for sector alignment, use a temp buffer and copy to + * the real destination after we're finished. + */ + if (usePacketBuffer) + { + if (*packetBufferLen < realLen) + { + *packetBufferLen = realLen; + *packetBuffer = pRealloc(*packetBuffer, realLen); + } + buf = *packetBuffer; + } + else + { + buf = dst; + } + + /* Read, finally. */ + ovlp.Offset = realOffset; + if (!pReadFile(io, buf, realLen, NULL, &ovlp)) + { + while (ovlp.Internal == (void*) 0x103) /* STATUS_PENDING */ + { + /* Don't actually sleep, just yield the thread */ + FAudio_sleep(0); + } + } + pGetOverlappedResult(io, &ovlp, &result, 1); + + /* Copy the subregion that we actually care about, if applicable */ + if (usePacketBuffer) + { + FAudio_memcpy(dst, *packetBuffer + offPacket, len); + } +} + +/* Internal Functions */ + +void FACT_INTERNAL_GetNextWave( + FACTCue *cue, + FACTSound *sound, + FACTTrack *track, + FACTTrackInstance *trackInst, + FACTEvent *evt, + FACTEventInstance *evtInst +) { + FAudioSendDescriptor reverbDesc[2]; + FAudioVoiceSends reverbSends; + const char *wbName; + FACTWaveBank *wb = NULL; + LinkedList *list; + uint16_t wbTrack; + uint8_t wbIndex; + uint8_t loopCount = 0; + float max, next; + uint8_t noTrackVariation = 1; + uint32_t i; + + /* Track Variation */ + if (evt->wave.isComplex) + { + if ( trackInst->activeWave.wave == NULL || + !(evt->wave.complex.variation & 0x00F0) ) + { + /* No-op, no variation on loop */ + } + /* Ordered, Ordered From Random */ + else if ( (evt->wave.complex.variation & 0xF) == 0 || + (evt->wave.complex.variation & 0xF) == 1 ) + { + evtInst->valuei += 1; + if (evtInst->valuei >= evt->wave.complex.trackCount) + { + evtInst->valuei = 0; + } + } + /* Random */ + else if ((evt->wave.complex.variation & 0xF) == 2) + { + max = 0.0f; + for (i = 0; i < evt->wave.complex.trackCount; i += 1) + { + max += evt->wave.complex.weights[i]; + } + next = FACT_INTERNAL_rng() * max; + for (i = evt->wave.complex.trackCount; i > 0; i -= 1) + { + if (next > (max - evt->wave.complex.weights[i - 1])) + { + evtInst->valuei = i - 1; + break; + } + max -= evt->wave.complex.weights[i - 1]; + } + } + /* Random (No Immediate Repeats), Shuffle */ + else if ( (evt->wave.complex.variation & 0xF) == 3 || + (evt->wave.complex.variation & 0xF) == 4 ) + { + max = 0.0f; + for (i = 0; i < evt->wave.complex.trackCount; i += 1) + { + if (i == evtInst->valuei) + { + continue; + } + max += evt->wave.complex.weights[i]; + } + next = FACT_INTERNAL_rng() * max; + for (i = evt->wave.complex.trackCount; i > 0; i -= 1) + { + if (i - 1 == evtInst->valuei) + { + continue; + } + if (next > (max - evt->wave.complex.weights[i - 1])) + { + evtInst->valuei = i - 1; + break; + } + max -= evt->wave.complex.weights[i - 1]; + } + } + + if (evt->wave.complex.variation & 0x00F0) + { + noTrackVariation = 0; + } + + wbIndex = evt->wave.complex.wavebanks[evtInst->valuei]; + wbTrack = evt->wave.complex.tracks[evtInst->valuei]; + } + else + { + wbIndex = evt->wave.simple.wavebank; + wbTrack = evt->wave.simple.track; + } + wbName = cue->parentBank->wavebankNames[wbIndex]; + list = cue->parentBank->parentEngine->wbList; + while (list != NULL) + { + wb = (FACTWaveBank*) list->entry; + if (FAudio_strcmp(wbName, wb->name) == 0) + { + break; + } + list = list->next; + } + FAudio_assert(wb != NULL); + + /* Generate the Wave */ + if ( evtInst->loopCount == 255 && + noTrackVariation && + !(evt->wave.variationFlags & 0x0F00) ) + { + /* For infinite loops with no variation, let Wave do the work */ + loopCount = 255; + } + FACTWaveBank_Prepare( + wb, + wbTrack, + evt->wave.flags, + 0, + loopCount, + &trackInst->upcomingWave.wave + ); + trackInst->upcomingWave.wave->parentCue = cue; + if (sound->dspCodeCount > 0) /* Never more than 1...? */ + { + reverbDesc[0].Flags = 0; + reverbDesc[0].pOutputVoice = cue->parentBank->parentEngine->master; + reverbDesc[1].Flags = 0; + reverbDesc[1].pOutputVoice = cue->parentBank->parentEngine->reverbVoice; + reverbSends.SendCount = 2; + reverbSends.pSends = reverbDesc; + FAudioVoice_SetOutputVoices( + trackInst->upcomingWave.wave->voice, + &reverbSends + ); + } + + /* 3D Audio */ + if (cue->active3D) + { + FACTWave_SetMatrixCoefficients( + trackInst->upcomingWave.wave, + cue->srcChannels, + cue->dstChannels, + cue->matrixCoefficients + ); + } + else + { + /* TODO: Position/Angle/UseCenterSpeaker */ + } + + /* Pitch Variation */ + if (evt->wave.variationFlags & 0x1000) + { + const int16_t rngPitch = (int16_t) ( + FACT_INTERNAL_rng() * + (evt->wave.maxPitch - evt->wave.minPitch) + ) + evt->wave.minPitch; + if (trackInst->activeWave.wave != NULL) + { + /* Variation on Loop */ + if (evt->wave.variationFlags & 0x0100) + { + /* Add/Replace */ + if (evt->wave.variationFlags & 0x0004) + { + trackInst->upcomingWave.basePitch = + trackInst->activeWave.basePitch + rngPitch; + } + else + { + trackInst->upcomingWave.basePitch = rngPitch + sound->pitch; + } + } + } + else + { + /* Initial Pitch Variation */ + trackInst->upcomingWave.basePitch = rngPitch + sound->pitch; + } + } + else + { + trackInst->upcomingWave.basePitch = sound->pitch; + } + + /* Volume Variation */ + if (evt->wave.variationFlags & 0x2000) + { + const float rngVolume = ( + FACT_INTERNAL_rng() * + (evt->wave.maxVolume - evt->wave.minVolume) + ) + evt->wave.minVolume; + if (trackInst->activeWave.wave != NULL) + { + /* Variation on Loop */ + if (evt->wave.variationFlags & 0x0200) + { + /* Add/Replace */ + if (evt->wave.variationFlags & 0x0001) + { + trackInst->upcomingWave.baseVolume = + trackInst->activeWave.baseVolume + rngVolume; + } + else + { + trackInst->upcomingWave.baseVolume = ( + rngVolume + + sound->volume + + track->volume + ); + } + } + } + else + { + /* Initial Volume Variation */ + trackInst->upcomingWave.baseVolume = ( + rngVolume + + sound->volume + + track->volume + ); + } + } + else + { + trackInst->upcomingWave.baseVolume = sound->volume + track->volume; + } + + /* Filter Variation, QFactor/Freq are always together */ + if (evt->wave.variationFlags & 0xC000) + { + const float rngQFactor = 1.0f / ( + FACT_INTERNAL_rng() * + (evt->wave.maxQFactor - evt->wave.minQFactor) + + evt->wave.minQFactor + ); + const float rngFrequency = FACT_INTERNAL_CalculateFilterFrequency( + ( + FACT_INTERNAL_rng() * + (evt->wave.maxFrequency - evt->wave.minFrequency) + + evt->wave.minFrequency + ), + cue->parentBank->parentEngine->audio->master->master.inputSampleRate + ); + if (trackInst->activeWave.wave != NULL) + { + /* Variation on Loop */ + if (evt->wave.variationFlags & 0x0C00) + { + /* TODO: Add/Replace */ + /* FIXME: Which is QFactor/Freq? + if (evt->wave.variationFlags & 0x0010) + { + } + else + { + } + if (evt->wave.variationFlags & 0x0040) + { + } + else + { + } + */ + trackInst->upcomingWave.baseQFactor = rngQFactor; + trackInst->upcomingWave.baseFrequency = rngFrequency; + } + } + else + { + /* Initial Filter Variation */ + trackInst->upcomingWave.baseQFactor = rngQFactor; + trackInst->upcomingWave.baseFrequency = rngFrequency; + } + } + else + { + trackInst->upcomingWave.baseQFactor = 1.0f / (track->qfactor / 3.0f); + trackInst->upcomingWave.baseFrequency = FACT_INTERNAL_CalculateFilterFrequency( + track->frequency, + cue->parentBank->parentEngine->audio->master->master.inputSampleRate + ); + } + + /* Try to change loop counter at the very end */ + if (loopCount == 255) + { + /* For infinite loops with no variation, Wave does the work */ + evtInst->loopCount = 0; + } + else if (evtInst->loopCount > 0) + { + evtInst->loopCount -= 1; + } +} + +uint8_t FACT_INTERNAL_CreateSound(FACTCue *cue, uint16_t fadeInMS) +{ + int32_t i, j, k; + float max, next, weight; + const char *wbName; + FACTWaveBank *wb = NULL; + LinkedList *list; + FACTEvent *evt; + FACTEventInstance *evtInst; + FACTSound *baseSound = NULL; + FACTSoundInstance *newSound; + FACTRPC *rpc; + float lastX; + + union + { + float maxf; + uint8_t maxi; + } limitmax; + FACTCue *tmp, *wnr; + uint16_t categoryIndex; + FACTAudioCategory *category; + + if (cue->data->flags & 0x04) + { + /* Sound */ + baseSound = cue->sound; + } + else + { + /* Variation */ + if (cue->variation->flags == 3) + { + /* Interactive */ + if (cue->parentBank->parentEngine->variables[cue->variation->variable].accessibility & 0x04) + { + FACTCue_GetVariable( + cue, + cue->variation->variable, + &next + ); + } + else + { + FACTAudioEngine_GetGlobalVariable( + cue->parentBank->parentEngine, + cue->variation->variable, + &next + ); + } + for (i = 0; i < cue->variation->entryCount; i += 1) + { + if ( next <= cue->variation->entries[i].maxWeight && + next >= cue->variation->entries[i].minWeight ) + { + break; + } + } + + /* This should only happen when the user control + * variable is none of the sound probabilities, in + * which case we are just silent. But, we should still + * claim to be "playing" in the meantime. + */ + if (i == cue->variation->entryCount) + { + return 1; + } + } + else + { + /* Random */ + max = 0.0f; + for (i = 0; i < cue->variation->entryCount; i += 1) + { + max += ( + cue->variation->entries[i].maxWeight - + cue->variation->entries[i].minWeight + ); + } + next = FACT_INTERNAL_rng() * max; + + /* Use > 0, not >= 0. If we hit 0, that's it! */ + for (i = cue->variation->entryCount - 1; i > 0; i -= 1) + { + weight = ( + cue->variation->entries[i].maxWeight - + cue->variation->entries[i].minWeight + ); + if (next > (max - weight)) + { + break; + } + max -= weight; + } + } + + if (cue->variation->isComplex) + { + /* Grab the Sound via the code. FIXME: Do this at load time? */ + for (j = 0; j < cue->parentBank->soundCount; j += 1) + { + if (cue->variation->entries[i].soundCode == cue->parentBank->soundCodes[j]) + { + baseSound = &cue->parentBank->sounds[j]; + break; + } + } + } + else + { + /* Pull in the WaveBank... */ + wbName = cue->parentBank->wavebankNames[ + cue->variation->entries[i].simple.wavebank + ]; + list = cue->parentBank->parentEngine->wbList; + while (list != NULL) + { + wb = (FACTWaveBank*) list->entry; + if (FAudio_strcmp(wbName, wb->name) == 0) + { + break; + } + list = list->next; + } + FAudio_assert(wb != NULL); + + /* Generate the wave... */ + FACTWaveBank_Prepare( + wb, + cue->variation->entries[i].simple.track, + 0, + 0, + 0, + &cue->simpleWave + ); + cue->simpleWave->parentCue = cue; + } + } + + /* Alloc SoundInstance variables */ + if (baseSound != NULL) + { + /* Category Instance Limits */ + categoryIndex = baseSound->category; + if (categoryIndex != FACTCATEGORY_INVALID) + { + category = &cue->parentBank->parentEngine->categories[categoryIndex]; + if (category->instanceCount >= category->instanceLimit) + { + wnr = NULL; + tmp = cue->parentBank->cueList; + if (category->maxInstanceBehavior == 0) /* Fail */ + { + cue->state |= FACT_STATE_STOPPED; + cue->state &= ~( + FACT_STATE_PLAYING | + FACT_STATE_STOPPING | + FACT_STATE_PAUSED + ); + return 0; + } + else if (category->maxInstanceBehavior == 1) /* Queue */ + { + /* FIXME: How is this different from Replace Oldest? */ + while (tmp != NULL) + { + if ( tmp != cue && + tmp->playingSound != NULL && + tmp->playingSound->sound->category == categoryIndex && + !(tmp->state & (FACT_STATE_STOPPING | FACT_STATE_STOPPED)) ) + { + wnr = tmp; + break; + } + tmp = tmp->next; + } + } + else if (category->maxInstanceBehavior == 2) /* Replace Oldest */ + { + while (tmp != NULL) + { + if ( tmp != cue && + tmp->playingSound != NULL && + tmp->playingSound->sound->category == categoryIndex && + !(tmp->state & (FACT_STATE_STOPPING | FACT_STATE_STOPPED)) ) + { + wnr = tmp; + break; + } + tmp = tmp->next; + } + } + else if (category->maxInstanceBehavior == 3) /* Replace Quietest */ + { + limitmax.maxf = FACTVOLUME_MAX; + while (tmp != NULL) + { + if ( tmp != cue && + tmp->playingSound != NULL && + tmp->playingSound->sound->category == categoryIndex && + /*FIXME: tmp->playingSound->volume < limitmax.maxf &&*/ + !(tmp->state & (FACT_STATE_STOPPING | FACT_STATE_STOPPED)) ) + { + wnr = tmp; + /* limitmax.maxf = tmp->playingSound->volume; */ + } + tmp = tmp->next; + } + } + else if (category->maxInstanceBehavior == 4) /* Replace Lowest Priority */ + { + limitmax.maxi = 0xFF; + while (tmp != NULL) + { + if ( tmp != cue && + tmp->playingSound != NULL && + tmp->playingSound->sound->category == categoryIndex && + tmp->playingSound->sound->priority < limitmax.maxi && + !(tmp->state & (FACT_STATE_STOPPING | FACT_STATE_STOPPED)) ) + { + wnr = tmp; + limitmax.maxi = tmp->playingSound->sound->priority; + } + tmp = tmp->next; + } + } + if (wnr != NULL) + { + fadeInMS = category->fadeInMS; + if (wnr->playingSound != NULL) + { + FACT_INTERNAL_BeginFadeOut(wnr->playingSound, category->fadeOutMS); + } + else + { + FACTCue_Stop(wnr, 0); + } + } + } + category->instanceCount += 1; + } + + newSound = (FACTSoundInstance*) cue->parentBank->parentEngine->pMalloc( + sizeof(FACTSoundInstance) + ); + newSound->parentCue = cue; + newSound->sound = baseSound; + newSound->rpcData.rpcVolume = 0.0f; + newSound->rpcData.rpcPitch = 0.0f; + newSound->rpcData.rpcReverbSend = 0.0f; + newSound->rpcData.rpcFilterQFactor = FAUDIO_DEFAULT_FILTER_ONEOVERQ; + newSound->rpcData.rpcFilterFreq = FAUDIO_DEFAULT_FILTER_FREQUENCY; + newSound->fadeType = (fadeInMS > 0); + if (newSound->fadeType) + { + newSound->fadeStart = FAudio_timems(); + newSound->fadeTarget = fadeInMS; + } + else + { + newSound->fadeStart = 0; + newSound->fadeTarget = 0; + } + newSound->tracks = (FACTTrackInstance*) cue->parentBank->parentEngine->pMalloc( + sizeof(FACTTrackInstance) * newSound->sound->trackCount + ); + for (i = 0; i < newSound->sound->trackCount; i += 1) + { + newSound->tracks[i].rpcData.rpcVolume = 0.0f; + newSound->tracks[i].rpcData.rpcPitch = 0.0f; + newSound->tracks[i].rpcData.rpcReverbSend = 0.0f; + newSound->tracks[i].rpcData.rpcFilterQFactor = FAUDIO_DEFAULT_FILTER_ONEOVERQ; + newSound->tracks[i].rpcData.rpcFilterFreq = FAUDIO_DEFAULT_FILTER_FREQUENCY; + + newSound->tracks[i].evtVolume = 0.0f; + newSound->tracks[i].evtPitch = 0.0f; + + newSound->tracks[i].activeWave.wave = NULL; + newSound->tracks[i].activeWave.baseVolume = 0.0f; + newSound->tracks[i].activeWave.basePitch = 0; + newSound->tracks[i].activeWave.baseQFactor = FAUDIO_DEFAULT_FILTER_ONEOVERQ; + newSound->tracks[i].activeWave.baseFrequency = FAUDIO_DEFAULT_FILTER_FREQUENCY; + newSound->tracks[i].upcomingWave.wave = NULL; + newSound->tracks[i].upcomingWave.baseVolume = 0.0f; + newSound->tracks[i].upcomingWave.basePitch = 0; + newSound->tracks[i].upcomingWave.baseQFactor = FAUDIO_DEFAULT_FILTER_ONEOVERQ; + newSound->tracks[i].upcomingWave.baseFrequency = FAUDIO_DEFAULT_FILTER_FREQUENCY; + + newSound->tracks[i].events = (FACTEventInstance*) cue->parentBank->parentEngine->pMalloc( + sizeof(FACTEventInstance) * newSound->sound->tracks[i].eventCount + ); + for (j = 0; j < newSound->sound->tracks[i].eventCount; j += 1) + { + evt = &newSound->sound->tracks[i].events[j]; + + newSound->tracks[i].events[j].timestamp = + newSound->sound->tracks[i].events[j].timestamp; + newSound->tracks[i].events[j].loopCount = 0; + newSound->tracks[i].events[j].finished = 0; + newSound->tracks[i].events[j].value = 0.0f; + + if ( evt->type == FACTEVENT_PLAYWAVE || + evt->type == FACTEVENT_PLAYWAVETRACKVARIATION || + evt->type == FACTEVENT_PLAYWAVEEFFECTVARIATION || + evt->type == FACTEVENT_PLAYWAVETRACKEFFECTVARIATION ) + { + newSound->tracks[i].events[j].loopCount = + newSound->sound->tracks[i].events[j].wave.loopCount; + + evtInst = &newSound->tracks[i].events[j]; + if ( !evt->wave.isComplex || + (evt->wave.complex.variation & 0xF) == 0 ) + { + evtInst->valuei = 0; + } + else + { + max = 0.0f; + for (k = 0; k < evt->wave.complex.trackCount; k += 1) + { + max += evt->wave.complex.weights[k]; + } + next = FACT_INTERNAL_rng() * max; + for (k = evt->wave.complex.trackCount - 1; k >= 0; k -= 1) + { + if (next > (max - evt->wave.complex.weights[k])) + { + evtInst->valuei = k; + break; + } + max -= evt->wave.complex.weights[k]; + } + } + FACT_INTERNAL_GetNextWave( + cue, + newSound->sound, + &newSound->sound->tracks[i], + &newSound->tracks[i], + evt, + evtInst + ); + newSound->tracks[i].waveEvt = evt; + newSound->tracks[i].waveEvtInst = evtInst; + } + else if ( evt->type == FACTEVENT_PITCHREPEATING || + evt->type == FACTEVENT_VOLUMEREPEATING ) + { + newSound->tracks[i].events[j].loopCount = + newSound->sound->tracks[i].events[j].value.repeats; + } + else if (evt->type == FACTEVENT_MARKERREPEATING) + { + newSound->tracks[i].events[j].loopCount = + newSound->sound->tracks[i].events[j].marker.repeats; + } + } + } + + /* Calculate Max RPC Release Time */ + cue->maxRpcReleaseTime = 0; + for (i = 0; i < newSound->sound->trackCount; i += 1) + { + for (j = 0; j < newSound->sound->tracks[i].rpcCodeCount; j += 1) + { + rpc = FACT_INTERNAL_GetRPC( + newSound->parentCue->parentBank->parentEngine, + newSound->sound->tracks[i].rpcCodes[j] + ); + if ( rpc->parameter == RPC_PARAMETER_VOLUME && + cue->parentBank->parentEngine->variables[rpc->variable].accessibility & 0x04 ) + { + if (FAudio_strcmp( + newSound->parentCue->parentBank->parentEngine->variableNames[rpc->variable], + "ReleaseTime" + ) == 0) { + lastX = rpc->points[rpc->pointCount - 1].x; + if (lastX > cue->maxRpcReleaseTime) + { + cue->maxRpcReleaseTime = (uint32_t) lastX /* bleh */; + } + } + } + } + } + + cue->playingSound = newSound; + } + + return 1; +} + +void FACT_INTERNAL_SendCueNotification(FACTCue *cue, FACTNoticationsFlags flag, uint8_t type) +{ + if (cue->parentBank->parentEngine->notifications & flag) + { + FACTNotification note; + + note.type = type; + note.pvContext = cue->parentBank->parentEngine->cue_context; + note.cue.cueIndex = cue->index; + note.cue.pSoundBank = cue->parentBank; + note.cue.pCue = cue; + + cue->parentBank->parentEngine->notificationCallback(¬e); + } +} + +void FACT_INTERNAL_DestroySound(FACTSoundInstance *sound) +{ + uint8_t i; + + sound->parentCue->playingSound = NULL; + for (i = 0; i < sound->sound->trackCount; i += 1) + { + if (sound->tracks[i].activeWave.wave != NULL) + { + FACTWave_Destroy( + sound->tracks[i].activeWave.wave + ); + } + if (sound->tracks[i].upcomingWave.wave != NULL) + { + FACTWave_Destroy( + sound->tracks[i].upcomingWave.wave + ); + } + sound->parentCue->parentBank->parentEngine->pFree( + sound->tracks[i].events + ); + } + sound->parentCue->parentBank->parentEngine->pFree(sound->tracks); + + if (sound->sound->category != FACTCATEGORY_INVALID) + { + sound->parentCue->parentBank->parentEngine->categories[ + sound->sound->category + ].instanceCount -= 1; + } + + /* TODO: if (sound->parentCue->playingSounds == NULL) */ + { + sound->parentCue->state |= FACT_STATE_STOPPED; + sound->parentCue->state &= ~(FACT_STATE_PLAYING | FACT_STATE_STOPPING); + sound->parentCue->data->instanceCount -= 1; + + FACT_INTERNAL_SendCueNotification(sound->parentCue, NOTIFY_CUESTOP, FACTNOTIFICATIONTYPE_CUESTOP); + } + sound->parentCue->parentBank->parentEngine->pFree(sound); +} + +void FACT_INTERNAL_BeginFadeOut(FACTSoundInstance *sound, uint16_t fadeOutMS) +{ + if (fadeOutMS == 0) + { + /* No fade? Screw it, just delete us */ + FACT_INTERNAL_DestroySound(sound); + return; + } + + sound->fadeType = 2; /* Out */ + sound->fadeStart = FAudio_timems(); + sound->fadeTarget = fadeOutMS; + + sound->parentCue->state |= FACT_STATE_STOPPING; +} + +void FACT_INTERNAL_BeginReleaseRPC(FACTSoundInstance *sound, uint16_t releaseMS) +{ + if (releaseMS == 0) + { + /* No release RPC? Screw it, just delete us */ + FACT_INTERNAL_DestroySound(sound); + return; + } + + sound->fadeType = 3; /* Release RPC */ + sound->fadeStart = FAudio_timems(); + sound->fadeTarget = releaseMS; + + sound->parentCue->state |= FACT_STATE_STOPPING; +} + +/* RPC Helper Functions */ + +FACTRPC* FACT_INTERNAL_GetRPC( + FACTAudioEngine *engine, + uint32_t code +) { + uint16_t i; + for (i = 0; i < engine->rpcCount; i += 1) + { + if (engine->rpcCodes[i] == code) + { + return &engine->rpcs[i]; + } + } + + FAudio_assert(0 && "RPC code not found!"); + return NULL; +} + +float FACT_INTERNAL_CalculateRPC( + FACTRPC *rpc, + float var +) { + float result; + uint8_t i; + + /* Min/Max */ + if (var <= rpc->points[0].x) + { + /* Zero to first defined point */ + return rpc->points[0].y; + } + if (var >= rpc->points[rpc->pointCount - 1].x) + { + /* Last defined point to infinity */ + return rpc->points[rpc->pointCount - 1].y; + } + + /* Something between points */ + result = 0.0f; + for (i = 0; i < rpc->pointCount - 1; i += 1) + { + /* y = b */ + result = rpc->points[i].y; + if (var >= rpc->points[i].x && var <= rpc->points[i + 1].x) + { + const float maxX = rpc->points[i + 1].x - rpc->points[i].x; + const float maxY = rpc->points[i + 1].y - rpc->points[i].y; + const float deltaX = (var - rpc->points[i].x); + const float deltaXNormalized = deltaX / maxX; + + if (rpc->points[i].type == 0) /* Linear */ + { + result += maxY * deltaXNormalized; + } + 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)); + } + 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)); + } + 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)); + } + else + { + result += maxY * (1.0f - FAudio_sqrtf(1.0f - FAudio_pow(deltaXNormalized, 2.0f))); + } + } + else + { + FAudio_assert(0 && "Unrecognized curve type!"); + } + + break; + } + } + return result; +} + +void FACT_INTERNAL_UpdateRPCs( + FACTCue *cue, + uint8_t codeCount, + uint32_t *codes, + FACTInstanceRPCData *data, + uint32_t timestamp, + uint32_t elapsedTrack +) { + uint8_t i; + FACTRPC *rpc; + float rpcResult; + float variableValue; + FACTAudioEngine *engine = cue->parentBank->parentEngine; + + if (codeCount > 0) + { + /* Do NOT overwrite Frequency/QFactor! */ + data->rpcVolume = 0.0f; + data->rpcPitch = 0.0f; + data->rpcReverbSend = 0.0f; + for (i = 0; i < codeCount; i += 1) + { + rpc = FACT_INTERNAL_GetRPC( + engine, + codes[i] + ); + if (engine->variables[rpc->variable].accessibility & 0x04) + { + if (FAudio_strcmp( + engine->variableNames[rpc->variable], + "AttackTime" + ) == 0) { + variableValue = (float) elapsedTrack; + } + else if (FAudio_strcmp( + engine->variableNames[rpc->variable], + "ReleaseTime" + ) == 0) { + if (cue->playingSound->fadeType == 3) /* Release RPC */ + { + variableValue = (float) (timestamp - cue->playingSound->fadeStart); + } + else + { + variableValue = 0.0f; + } + } + else + { + variableValue = cue->variableValues[rpc->variable]; + } + + rpcResult = FACT_INTERNAL_CalculateRPC( + rpc, + variableValue + ); + } + else + { + rpcResult = FACT_INTERNAL_CalculateRPC( + rpc, + engine->globalVariableValues[rpc->variable] + ); + } + if (rpc->parameter == RPC_PARAMETER_VOLUME) + { + data->rpcVolume += rpcResult; + } + else if (rpc->parameter == RPC_PARAMETER_PITCH) + { + data->rpcPitch += rpcResult; + } + else if (rpc->parameter == RPC_PARAMETER_REVERBSEND) + { + data->rpcReverbSend += rpcResult; + } + else if (rpc->parameter == RPC_PARAMETER_FILTERFREQUENCY) + { + /* Yes, just overwrite... */ + data->rpcFilterFreq = FACT_INTERNAL_CalculateFilterFrequency( + rpcResult, + engine->audio->master->master.inputSampleRate + ); + } + else if (rpc->parameter == RPC_PARAMETER_FILTERQFACTOR) + { + /* Yes, just overwrite... */ + data->rpcFilterQFactor = 1.0f / rpcResult; + } + else + { + FAudio_assert(0 && "Unhandled RPC parameter type!"); + } + } + } +} + +/* Engine Update Function */ + +void FACT_INTERNAL_UpdateEngine(FACTAudioEngine *engine) +{ + FAudioFXReverbParameters rvbPar; + uint16_t i, j, par; + float rpcResult; + for (i = 0; i < engine->rpcCount; i += 1) + { + if (engine->rpcs[i].parameter >= RPC_PARAMETER_COUNT) + { + /* FIXME: Why did I make this global vars only...? */ + if (!(engine->variables[engine->rpcs[i].variable].accessibility & 0x04)) + { + for (j = 0; j < engine->dspPresetCount; j += 1) + { + /* FIXME: This affects all DSP presets! + * What if there's more than one? + */ + par = engine->rpcs[i].parameter - RPC_PARAMETER_COUNT; + rpcResult = FACT_INTERNAL_CalculateRPC( + &engine->rpcs[i], + engine->globalVariableValues[engine->rpcs[i].variable] + ); + engine->dspPresets[j].parameters[par].value = FAudio_clamp( + rpcResult, + engine->dspPresets[j].parameters[par].minVal, + engine->dspPresets[j].parameters[par].maxVal + ); + } + } + } + } + + /* Set Effect parameters from above RPC changes */ + if (engine->reverbVoice != NULL) + { + rvbPar.WetDryMix = engine->dspPresets[0].parameters[21].value; + rvbPar.ReflectionsDelay = (uint32_t) engine->dspPresets[0].parameters[0].value; + rvbPar.ReverbDelay = (uint8_t) engine->dspPresets[0].parameters[1].value; + rvbPar.RearDelay = (uint8_t) engine->dspPresets[0].parameters[12].value; + rvbPar.PositionLeft = (uint8_t) engine->dspPresets[0].parameters[2].value; + rvbPar.PositionRight = (uint8_t) engine->dspPresets[0].parameters[3].value; + rvbPar.PositionMatrixLeft = (uint8_t) engine->dspPresets[0].parameters[4].value; + rvbPar.PositionMatrixRight = (uint8_t) engine->dspPresets[0].parameters[5].value; + rvbPar.HighEQGain = (uint8_t) engine->dspPresets[0].parameters[10].value; + rvbPar.LowEQCutoff = (uint8_t) engine->dspPresets[0].parameters[9].value; + rvbPar.LowEQGain = (uint8_t) engine->dspPresets[0].parameters[8].value; + rvbPar.LateDiffusion = (uint8_t) engine->dspPresets[0].parameters[7].value; + rvbPar.EarlyDiffusion = (uint8_t) engine->dspPresets[0].parameters[6].value; + rvbPar.HighEQCutoff = (uint8_t) engine->dspPresets[0].parameters[11].value; + rvbPar.RoomFilterMain = engine->dspPresets[0].parameters[14].value; + rvbPar.RoomFilterFreq = engine->dspPresets[0].parameters[13].value; + rvbPar.RoomFilterHF = engine->dspPresets[0].parameters[15].value; + rvbPar.ReflectionsGain = engine->dspPresets[0].parameters[16].value; + rvbPar.ReverbGain = engine->dspPresets[0].parameters[17].value; + rvbPar.DecayTime = engine->dspPresets[0].parameters[18].value; + rvbPar.Density = engine->dspPresets[0].parameters[19].value; + rvbPar.RoomSize = engine->dspPresets[0].parameters[20].value; + + FAudioVoice_SetEffectParameters( + engine->reverbVoice, + 0, + &rvbPar, + sizeof(FAudioFXReverbParameters), + 0 + ); + } +} + +/* Cue Update Functions */ + +static inline void FACT_INTERNAL_StopTrack( + FACTTrack *track, + FACTTrackInstance *trackInst, + uint8_t immediate +) { + uint8_t i; + + /* Stop the wave (may as-authored or immedate */ + if (trackInst->activeWave.wave != NULL) + { + FACTWave_Stop( + trackInst->activeWave.wave, + immediate + ); + } + + /* If there was another sound coming, it ain't now! */ + if (trackInst->upcomingWave.wave != NULL) + { + FACTWave_Destroy(trackInst->upcomingWave.wave); + trackInst->upcomingWave.wave = NULL; + } + + /* Kill the loop count too */ + for (i = 0; i < track->eventCount; i += 1) + { + trackInst->events[i].loopCount = 0; + trackInst->events[i].finished = 1; + } +} + +void FACT_INTERNAL_ActivateEvent( + FACTSoundInstance *sound, + FACTTrack *track, + FACTTrackInstance *trackInst, + FACTEvent *evt, + FACTEventInstance *evtInst, + uint32_t elapsed +) { + uint8_t i; + float svResult; + uint8_t skipLoopCheck = 0; + + /* STOP */ + if (evt->type == FACTEVENT_STOP) + { + /* Stop Cue */ + if (evt->stop.flags & 0x02) + { + if ( evt->stop.flags & 0x01 || + ( sound->parentCue->parentBank->cues[sound->parentCue->index].fadeOutMS == 0 && + sound->parentCue->maxRpcReleaseTime == 0 ) ) + { + for (i = 0; i < sound->sound->trackCount; i += 1) + { + FACT_INTERNAL_StopTrack( + &sound->sound->tracks[i], + &sound->tracks[i], + 1 + ); + } + } + else + { + if (sound->parentCue->parentBank->cues[sound->parentCue->index].fadeOutMS > 0) + { + FACT_INTERNAL_BeginFadeOut( + sound, + sound->parentCue->parentBank->cues[sound->parentCue->index].fadeOutMS + ); + } + else if (sound->parentCue->maxRpcReleaseTime > 0) + { + FACT_INTERNAL_BeginReleaseRPC( + sound, + sound->parentCue->maxRpcReleaseTime + ); + } + else + { + /* Pretty sure this doesn't happen, but just in case? */ + sound->parentCue->state |= FACT_STATE_STOPPING; + } + } + } + + /* Stop track */ + else + { + FACT_INTERNAL_StopTrack( + track, + trackInst, + evt->stop.flags & 0x01 + ); + } + } + + /* PLAYWAVE */ + else if ( evt->type == FACTEVENT_PLAYWAVE || + evt->type == FACTEVENT_PLAYWAVETRACKVARIATION || + evt->type == FACTEVENT_PLAYWAVEEFFECTVARIATION || + evt->type == FACTEVENT_PLAYWAVETRACKEFFECTVARIATION ) + { + FAudio_assert(trackInst->activeWave.wave == NULL); + FAudio_assert(trackInst->upcomingWave.wave != NULL); + FAudio_memcpy( + &trackInst->activeWave, + &trackInst->upcomingWave, + sizeof(trackInst->activeWave) + ); + trackInst->upcomingWave.wave = NULL; + FACTWave_Play(trackInst->activeWave.wave); + } + + /* SETVALUE */ + else if ( evt->type == FACTEVENT_PITCH || + evt->type == FACTEVENT_PITCHREPEATING || + evt->type == FACTEVENT_VOLUME || + evt->type == FACTEVENT_VOLUMEREPEATING ) + { + /* Ramp/Equation */ + if (evt->value.settings & 0x01) + { + /* FIXME: Incorporate 2nd derivative into the interpolated pitch (slopeDelta) */ + skipLoopCheck = elapsed <= (evtInst->timestamp + evt->value.ramp.duration); + svResult = ( + evt->value.ramp.initialSlope * + evt->value.ramp.duration / 1000 * + 10 /* "Slices" */ + ) + evt->value.ramp.initialValue; + svResult = ( + (svResult - evt->value.ramp.initialValue) + ) * FAudio_clamp( + (float) (elapsed - evtInst->timestamp) / evt->value.ramp.duration, + 0.0f, + 1.0f + ) + evt->value.ramp.initialValue; + + evtInst->value = svResult; + } + else + { + /* Value/Random */ + if (evt->value.equation.flags & 0x04) + { + svResult = evt->value.equation.value1; + } + else if (evt->value.equation.flags & 0x08) + { + svResult = evt->value.equation.value1 + FACT_INTERNAL_rng() * ( + evt->value.equation.value2 - + evt->value.equation.value1 + ); + } + else + { + svResult = 0.0f; + FAudio_assert(0 && "Equation flags?"); + } + + /* Add/Replace */ + if (evt->value.equation.flags & 0x01) + { + if( evt->type == FACTEVENT_PITCH || + evt->type == FACTEVENT_PITCHREPEATING ) + { + evtInst->value = trackInst->evtPitch + svResult; + } + else + { + evtInst->value = trackInst->evtVolume + svResult; + } + } + else + { + evtInst->value = svResult; + } + } + + /* Set the result, finally. */ + if ( evt->type == FACTEVENT_PITCH || + evt->type == FACTEVENT_PITCHREPEATING ) + { + trackInst->evtPitch = evtInst->value; + } + else + { + trackInst->evtVolume = evtInst->value; + } + + if (skipLoopCheck) + { + return; + } + if (evtInst->loopCount > 0) + { + if (evtInst->loopCount != 0xFF && evtInst->loopCount != 0xFFFF) + { + evtInst->loopCount -= 1; + } + + evtInst->timestamp += evt->value.frequency; + return; + } + } + + /* MARKER */ + else if ( evt->type == FACTEVENT_MARKER || + evt->type == FACTEVENT_MARKERREPEATING ) + { + /* TODO: FACT_INTERNAL_Marker(evt->marker*) */ + if (evtInst->loopCount > 0) + { + if (evtInst->loopCount != 0xFF) + { + evtInst->loopCount -= 1; + } + + evtInst->timestamp += evt->marker.frequency; + return; + } + } + + /* ??? */ + else + { + FAudio_assert(0 && "Unknown event type!"); + } + + /* If we made it here, we're done! */ + evtInst->finished = 1; +} + +uint8_t FACT_INTERNAL_UpdateSound(FACTSoundInstance *sound, uint32_t timestamp) +{ + uint8_t i, j; + uint32_t waveState; + uint32_t elapsedCue; + FACTEventInstance *evtInst; + FAudioFilterParameters filterParams; + uint8_t finished = 1; + + /* Instance limiting Fade in/out */ + float fadeVolume; + if (sound->fadeType == 1) /* Fade In */ + { + if ((timestamp - sound->fadeStart) >= sound->fadeTarget) + { + /* We've faded in! */ + fadeVolume = 1.0f; + sound->fadeStart = 0; + sound->fadeTarget = 0; + sound->fadeType = 0; + } + else + { + fadeVolume = ( + (float) (timestamp - sound->fadeStart) / + (float) sound->fadeTarget + ); + } + } + else if (sound->fadeType == 2) /* Fade Out */ + { + if ((timestamp - sound->fadeStart) >= sound->fadeTarget) + { + /* We've faded out! */ + return 1; + } + fadeVolume = 1.0f - ( + (float) (timestamp - sound->fadeStart) / + (float) sound->fadeTarget + ); + } + else if (sound->fadeType == 3) /* Release RPC */ + { + if ((timestamp - sound->fadeStart) >= sound->fadeTarget) + { + /* We've faded out! */ + return 1; + } + fadeVolume = 1.0f; + } + else + { + fadeVolume = 1.0f; + } + + /* To get the time on a single Cue, subtract from the global time + * the latest start time minus the total time elapsed (minus pause time) + */ + elapsedCue = timestamp - (sound->parentCue->start - sound->parentCue->elapsed); + + /* RPC updates */ + sound->rpcData.rpcFilterFreq = -1.0f; + sound->rpcData.rpcFilterQFactor = -1.0f; + FACT_INTERNAL_UpdateRPCs( + sound->parentCue, + sound->sound->rpcCodeCount, + sound->sound->rpcCodes, + &sound->rpcData, + timestamp, + elapsedCue - sound->tracks[0].events[0].timestamp + ); + for (i = 0; i < sound->sound->trackCount; i += 1) + { + sound->tracks[i].rpcData.rpcFilterFreq = sound->rpcData.rpcFilterFreq; + sound->tracks[i].rpcData.rpcFilterQFactor = sound->rpcData.rpcFilterQFactor; + FACT_INTERNAL_UpdateRPCs( + sound->parentCue, + sound->sound->tracks[i].rpcCodeCount, + sound->sound->tracks[i].rpcCodes, + &sound->tracks[i].rpcData, + timestamp, + elapsedCue - sound->sound->tracks[i].events[0].timestamp + ); + } + + /* Go through each event for each track */ + for (i = 0; i < sound->sound->trackCount; i += 1) + { + /* Event updates */ + for (j = 0; j < sound->sound->tracks[i].eventCount; j += 1) + { + evtInst = &sound->tracks[i].events[j]; + if (!evtInst->finished) + { + /* Cue's not done yet...! */ + finished = 0; + + /* Trigger events at the right time */ + if (elapsedCue >= evtInst->timestamp) + { + FACT_INTERNAL_ActivateEvent( + sound, + &sound->sound->tracks[i], + &sound->tracks[i], + &sound->sound->tracks[i].events[j], + evtInst, + elapsedCue + ); + } + } + } + + /* Wave updates */ + if (sound->tracks[i].activeWave.wave == NULL) + { + continue; + } + finished = 0; + + /* Clear out Waves as they finish */ + FACTWave_GetState( + sound->tracks[i].activeWave.wave, + &waveState + ); + if (waveState & FACT_STATE_STOPPED) + { + FACTWave_Destroy(sound->tracks[i].activeWave.wave); + FAudio_memcpy( + &sound->tracks[i].activeWave, + &sound->tracks[i].upcomingWave, + sizeof(sound->tracks[i].activeWave) + ); + sound->tracks[i].upcomingWave.wave = NULL; + if (sound->tracks[i].activeWave.wave == NULL) + { + continue; + } + FACTWave_Play(sound->tracks[i].activeWave.wave); + } + + FACTWave_SetVolume( + sound->tracks[i].activeWave.wave, + FACT_INTERNAL_CalculateAmplitudeRatio( + sound->tracks[i].activeWave.baseVolume + + sound->rpcData.rpcVolume + + sound->tracks[i].rpcData.rpcVolume + + sound->tracks[i].evtVolume + ) * sound->parentCue->parentBank->parentEngine->categories[ + sound->sound->category + ].currentVolume * + fadeVolume + ); + FACTWave_SetPitch( + sound->tracks[i].activeWave.wave, + (int16_t) ( + sound->tracks[i].activeWave.basePitch + + sound->rpcData.rpcPitch + + sound->tracks[i].rpcData.rpcPitch + + sound->tracks[i].evtPitch + ) + ); + if (sound->sound->tracks[i].filter != 0xFF) + { + /* FIXME: From what I can gather, filter parameters get + * overwritten by the RPC value if a filter RPC exists. + * Priority is Sound < Sound RPC < Track RPC, I think? + */ + filterParams.Type = (FAudioFilterType) sound->sound->tracks[i].filter; + if (sound->tracks[i].rpcData.rpcFilterFreq >= 0.0f) + { + filterParams.Frequency = sound->tracks[i].rpcData.rpcFilterFreq; + } + else + { + filterParams.Frequency = sound->tracks[i].activeWave.baseFrequency; + } + if (sound->tracks[i].rpcData.rpcFilterQFactor >= 0.0f) + { + filterParams.OneOverQ = sound->tracks[i].rpcData.rpcFilterQFactor; + } + else + { + filterParams.OneOverQ = sound->tracks[i].activeWave.baseQFactor; + } + FAudioVoice_SetFilterParameters( + sound->tracks[i].activeWave.wave->voice, + &filterParams, + 0 + ); + } + /* TODO: Wave updates: + * - ReverbSend (SetOutputMatrix on index 1, submix voice) + */ + } + + return finished; +} + +void FACT_INTERNAL_UpdateCue(FACTCue *cue) +{ + uint32_t i; + float next; + FACTSoundInstance *sound; + + /* Interactive sound selection */ + if (!(cue->data->flags & 0x04) && cue->variation->flags == 3) + { + /* Interactive */ + if (cue->parentBank->parentEngine->variables[cue->variation->variable].accessibility & 0x04) + { + FACTCue_GetVariable( + cue, + cue->variation->variable, + &next + ); + } + else + { + FACTAudioEngine_GetGlobalVariable( + cue->parentBank->parentEngine, + cue->variation->variable, + &next + ); + } + if (next != cue->interactive) + { + cue->interactive = next; + + /* New sound, time for death! */ + if (cue->playingSound != NULL) + { + /* Copy of DestroySound but does not set Cue to STOPPED */ + sound = cue->playingSound; + sound->parentCue->playingSound = NULL; + for (i = 0; i < sound->sound->trackCount; i += 1) + { + if (sound->tracks[i].activeWave.wave != NULL) + { + FACTWave_Destroy( + sound->tracks[i].activeWave.wave + ); + } + if (sound->tracks[i].upcomingWave.wave != NULL) + { + FACTWave_Destroy( + sound->tracks[i].upcomingWave.wave + ); + } + cue->parentBank->parentEngine->pFree( + sound->tracks[i].events + ); + } + cue->parentBank->parentEngine->pFree(sound->tracks); + + if (sound->sound->category != FACTCATEGORY_INVALID) + { + sound->parentCue->parentBank->parentEngine->categories[ + sound->sound->category + ].instanceCount -= 1; + } + } + + /* TODO: Reset cue times? Transition tables...? + cue->start = elapsed; + cue->elapsed = 0; + */ + + FACT_INTERNAL_CreateSound(cue, 0 /* fadeIn */); + } + } +} + +/* FACT Thread */ + +int32_t FACT_INTERNAL_APIThread(void* enginePtr) +{ + FACTAudioEngine *engine = (FACTAudioEngine*) enginePtr; + LinkedList *sbList; + FACTCue *cue, *cBackup; + uint32_t timestamp, updateTime; + + /* Needs to match the audio thread priority, or else the scheduler will + * let this thread sit around with a lock while the audio thread spins + * infinitely! + */ + FAudio_PlatformThreadPriority(FAUDIO_THREAD_PRIORITY_HIGH); + +threadstart: + FAudio_PlatformLockMutex(engine->apiLock); + + /* We want the timestamp to be uniform across all Cues. + * Oftentimes many Cues are played at once with the expectation + * that they will sync, so give them all the same timestamp + * so all the various actions will go together even if it takes + * an extra millisecond to get through the whole Cue list. + */ + timestamp = FAudio_timems(); + + FACT_INTERNAL_UpdateEngine(engine); + + sbList = engine->sbList; + while (sbList != NULL) + { + cue = ((FACTSoundBank*) sbList->entry)->cueList; + while (cue != NULL) + { + FACT_INTERNAL_UpdateCue(cue); + + if (cue->state & FACT_STATE_PAUSED) + { + cue = cue->next; + continue; + } + + if (cue->playingSound != NULL) + { + if (FACT_INTERNAL_UpdateSound(cue->playingSound, timestamp)) + { + FACT_INTERNAL_DestroySound(cue->playingSound); + } + } + + /* Destroy if it's done and not user-handled. */ + if (cue->managed && (cue->state & FACT_STATE_STOPPED)) + { + cBackup = cue->next; + FACTCue_Destroy(cue); + cue = cBackup; + } + else + { + cue = cue->next; + } + } + sbList = sbList->next; + } + + FAudio_PlatformUnlockMutex(engine->apiLock); + + if (engine->initialized) + { + /* FIXME: 10ms is based on the XAudio2 update time...? */ + updateTime = FAudio_timems() - timestamp; + if (updateTime < 10) + { + FAudio_sleep(10 - updateTime); + } + goto threadstart; + } + + return 0; +} + +/* FAudio callbacks */ + +void FACT_INTERNAL_OnBufferEnd(FAudioVoiceCallback *callback, void* pContext) +{ + FAudioBuffer buffer; + FAudioBufferWMA bufferWMA; + FACTWaveCallback *c = (FACTWaveCallback*) callback; + FACTWaveBankEntry *entry; + uint32_t end, left, length; + + entry = &c->wave->parentBank->entries[c->wave->index]; + + /* Calculate total bytes left in this wave iteration */ + if (c->wave->loopCount > 0 && entry->LoopRegion.dwTotalSamples > 0) + { + length = entry->LoopRegion.dwStartSample + entry->LoopRegion.dwTotalSamples; + if (entry->Format.wFormatTag == 0x0) + { + length = ( + length * + entry->Format.nChannels * + (1 << entry->Format.wBitsPerSample) + ); + } + else if (entry->Format.wFormatTag == 0x2) + { + length = ( + length / + /* wSamplesPerBlock */ + ((entry->Format.wBlockAlign + 16) * 2) * + /* nBlockAlign */ + ((entry->Format.wBlockAlign + 22) * entry->Format.nChannels) + ); + } + else + { + length = entry->PlayRegion.dwLength; + } + } + else + { + length = entry->PlayRegion.dwLength; + } + end = entry->PlayRegion.dwOffset + length; + left = length - (c->wave->streamOffset - entry->PlayRegion.dwOffset); + + /* Don't bother if we're EOS or the Wave has stopped */ + if ( (c->wave->streamOffset >= end) || + (c->wave->state & FACT_STATE_STOPPED) ) + { + return; + } + + /* Assign buffer memory */ + buffer.pAudioData = c->wave->streamCache; + buffer.AudioBytes = FAudio_min( + c->wave->streamSize, + left + ); + + /* Read! */ + FACT_INTERNAL_ReadFile( + c->wave->parentBank->parentEngine->pReadFile, + c->wave->parentBank->parentEngine->pGetOverlappedResult, + c->wave->parentBank->io, + c->wave->streamOffset, + c->wave->parentBank->packetSize, + &c->wave->parentBank->packetBuffer, + &c->wave->parentBank->packetBufferLen, + c->wave->parentBank->parentEngine->pRealloc, + c->wave->streamCache, + buffer.AudioBytes + ); + c->wave->streamOffset += buffer.AudioBytes; + + /* Last buffer in the stream? */ + buffer.Flags = 0; + if (c->wave->streamOffset >= end) + { + /* Loop if applicable */ + if (c->wave->loopCount > 0) + { + if (c->wave->loopCount != 255) + { + c->wave->loopCount -= 1; + } + c->wave->streamOffset = entry->PlayRegion.dwOffset; + + /* Loop start */ + if (entry->Format.wFormatTag == 0x0) + { + c->wave->streamOffset += ( + entry->LoopRegion.dwStartSample * + entry->Format.nChannels * + (1 << entry->Format.wBitsPerSample) + ); + } + else if (entry->Format.wFormatTag == 0x2) + { + c->wave->streamOffset += ( + entry->LoopRegion.dwStartSample / + /* wSamplesPerBlock */ + ((entry->Format.wBlockAlign + 16) * 2) * + /* nBlockAlign */ + ((entry->Format.wBlockAlign + 22) * entry->Format.nChannels) + ); + } + } + else + { + buffer.Flags = FAUDIO_END_OF_STREAM; + } + } + + /* Unused properties */ + buffer.PlayBegin = 0; + buffer.PlayLength = 0; + buffer.LoopBegin = 0; + buffer.LoopLength = 0; + buffer.LoopCount = 0; + buffer.pContext = NULL; + + /* Submit, finally. */ + if (entry->Format.wFormatTag == 0x3) + { + bufferWMA.pDecodedPacketCumulativeBytes = + c->wave->parentBank->seekTables[c->wave->index].entries; + bufferWMA.PacketCount = + c->wave->parentBank->seekTables[c->wave->index].entryCount; + FAudioSourceVoice_SubmitSourceBuffer( + c->wave->voice, + &buffer, + &bufferWMA + ); + } + else + { + FAudioSourceVoice_SubmitSourceBuffer( + c->wave->voice, + &buffer, + NULL + ); + } +} + +void FACT_INTERNAL_OnStreamEnd(FAudioVoiceCallback *callback) +{ + FACTWaveCallback *c = (FACTWaveCallback*) callback; + + c->wave->state = FACT_STATE_STOPPED; + + if ( c->wave->parentCue != NULL && + c->wave->parentCue->simpleWave == c->wave ) + { + c->wave->parentCue->state |= FACT_STATE_STOPPED; + c->wave->parentCue->state &= ~( + FACT_STATE_PLAYING | + FACT_STATE_STOPPING + ); + c->wave->parentCue->data->instanceCount -= 1; + } +} + +/* FAudioIOStream functions */ + +int32_t FACTCALL FACT_INTERNAL_DefaultReadFile( + void *hFile, + void *buffer, + uint32_t nNumberOfBytesToRead, + uint32_t *lpNumberOfBytesRead, /* Not referenced! */ + FACTOverlapped *lpOverlapped +) { + FAudioIOStream *io = (FAudioIOStream*) hFile; + lpOverlapped->Internal = (void*) 0x00000103; /* STATUS_PENDING */ + FAudio_PlatformLockMutex((FAudioMutex) io->lock); + io->seek(io->data, (size_t) lpOverlapped->Pointer, FAUDIO_SEEK_SET); + lpOverlapped->InternalHigh = (void*) (size_t) (io->read( + io->data, + buffer, + nNumberOfBytesToRead, + 1 + ) * nNumberOfBytesToRead); + FAudio_PlatformUnlockMutex((FAudioMutex) io->lock); + lpOverlapped->Internal = 0; /* STATUS_SUCCESS */ + return 1; +} + +int32_t FACTCALL FACT_INTERNAL_DefaultGetOverlappedResult( + void *hFile, + FACTOverlapped *lpOverlapped, + uint32_t *lpNumberOfBytesTransferred, + int32_t bWait +) { + *lpNumberOfBytesTransferred = (uint32_t) (size_t) lpOverlapped->InternalHigh; + return 1; +} + +/* Parsing functions */ + +#define READ_FUNC(type, size, bitsize, suffix) \ + static inline type read_##suffix(uint8_t **ptr, const uint8_t swapendian) \ + { \ + type result = *((type*) *ptr); \ + *ptr += size; \ + return swapendian ? \ + FAudio_swap##bitsize##BE(result) : \ + FAudio_swap##bitsize##LE(result); \ + } + +static inline uint8_t read_u8(uint8_t **ptr) +{ + uint8_t result = *((uint8_t*) *ptr); + *ptr += 1; + return result; +} +READ_FUNC(uint16_t, 2, 16, u16) +READ_FUNC(uint32_t, 4, 32, u32) +READ_FUNC(int16_t, 2, 16, s16) +READ_FUNC(int32_t, 4, 32, s32) +static inline float read_f32(uint8_t **ptr, const uint8_t swapendian) +{ + float result = *((float*) *ptr); + *ptr += 4; + return result; +} + +#undef READ_FUNC + +static inline float read_volbyte(uint8_t **ptr) +{ + /* FIXME: This magnificent beauty came from Mathematica! + * The byte values for all possible input dB values from the .xap are here: + * http://www.flibitijibibo.com/XACTVolume.txt + * Yes, this is actually what the XACT builder really does. + * + * Thanks to Kenny for plotting all that data. + * -flibit + */ + return (float) ((3969.0 * FAudio_log10(read_u8(ptr) / 28240.0)) + 8715.0); +} + +uint32_t FACT_INTERNAL_ParseAudioEngine( + FACTAudioEngine *pEngine, + const FACTRuntimeParameters *pParams +) { + uint32_t categoryOffset, + variableOffset, + blob1Offset, + categoryNameIndexOffset, + blob2Offset, + variableNameIndexOffset, + categoryNameOffset, + variableNameOffset, + rpcOffset, + dspPresetOffset, + dspParameterOffset; + uint16_t blob1Count, blob2Count; + uint8_t version, tool; + uint8_t se; + uint32_t magic; + size_t memsize; + uint16_t i, j; + + uint8_t *ptr = (uint8_t*) pParams->pGlobalSettingsBuffer; + uint8_t *start = ptr; + + magic = read_u32(&ptr, 0); + se = magic == 0x58475346; /* Swap Endian */ + if (magic != 0x46534758 && magic != 0x58475346) /* 'XGSF' */ + { + return -1; /* TODO: NOT XACT FILE */ + } + + if (!FACT_INTERNAL_SupportedContent(read_u16(&ptr, se))) + { + return -2; + } + + tool = read_u16(&ptr, se); /* Tool version */ + if (tool != 42) + { + return -3; + } + + ptr += 2; /* Unknown value */ + + /* Last modified, unused */ + ptr += 8; + + /* XACT Version (Windows == 3, Xbox == 7) */ + version = read_u8(&ptr); + if ( version != 3 && + version != 7 ) + { + return -4; /* TODO: VERSION TOO OLD */ + } + + /* Object counts */ + pEngine->categoryCount = read_u16(&ptr, se); + pEngine->variableCount = read_u16(&ptr, se); + blob1Count = read_u16(&ptr, se); + blob2Count = read_u16(&ptr, se); + pEngine->rpcCount = read_u16(&ptr, se); + pEngine->dspPresetCount = read_u16(&ptr, se); + pEngine->dspParameterCount = read_u16(&ptr, se); + + /* Object offsets */ + categoryOffset = read_u32(&ptr, se); + variableOffset = read_u32(&ptr, se); + blob1Offset = read_u32(&ptr, se); + categoryNameIndexOffset = read_u32(&ptr, se); + blob2Offset = read_u32(&ptr, se); + variableNameIndexOffset = read_u32(&ptr, se); + categoryNameOffset = read_u32(&ptr, se); + variableNameOffset = read_u32(&ptr, se); + rpcOffset = read_u32(&ptr, se); + dspPresetOffset = read_u32(&ptr, se); + dspParameterOffset = read_u32(&ptr, se); + + /* Category data */ + FAudio_assert((ptr - start) == categoryOffset); + pEngine->categories = (FACTAudioCategory*) pEngine->pMalloc( + sizeof(FACTAudioCategory) * pEngine->categoryCount + ); + for (i = 0; i < pEngine->categoryCount; i += 1) + { + pEngine->categories[i].instanceLimit = read_u8(&ptr); + pEngine->categories[i].fadeInMS = read_u16(&ptr, se); + pEngine->categories[i].fadeOutMS = read_u16(&ptr, se); + pEngine->categories[i].maxInstanceBehavior = read_u8(&ptr) >> 3; + pEngine->categories[i].parentCategory = read_u16(&ptr, se); + pEngine->categories[i].volume = FACT_INTERNAL_CalculateAmplitudeRatio( + read_volbyte(&ptr) + ); + pEngine->categories[i].visibility = read_u8(&ptr); + pEngine->categories[i].instanceCount = 0; + pEngine->categories[i].currentVolume = 1.0f; + } + + /* Variable data */ + FAudio_assert((ptr - start) == variableOffset); + pEngine->variables = (FACTVariable*) pEngine->pMalloc( + sizeof(FACTVariable) * pEngine->variableCount + ); + for (i = 0; i < pEngine->variableCount; i += 1) + { + pEngine->variables[i].accessibility = read_u8(&ptr); + pEngine->variables[i].initialValue = read_f32(&ptr, se); + pEngine->variables[i].minValue = read_f32(&ptr, se); + pEngine->variables[i].maxValue = read_f32(&ptr, se); + } + + /* Global variable storage. Some unused data for non-global vars */ + pEngine->globalVariableValues = (float*) pEngine->pMalloc( + sizeof(float) * pEngine->variableCount + ); + for (i = 0; i < pEngine->variableCount; i += 1) + { + pEngine->globalVariableValues[i] = pEngine->variables[i].initialValue; + } + + /* RPC data */ + if (pEngine->rpcCount > 0) + { + FAudio_assert((ptr - start) == rpcOffset); + pEngine->rpcs = (FACTRPC*) pEngine->pMalloc( + sizeof(FACTRPC) * + pEngine->rpcCount + ); + pEngine->rpcCodes = (uint32_t*) pEngine->pMalloc( + sizeof(uint32_t) * + pEngine->rpcCount + ); + for (i = 0; i < pEngine->rpcCount; i += 1) + { + pEngine->rpcCodes[i] = (uint32_t) (ptr - start); + pEngine->rpcs[i].variable = read_u16(&ptr, se); + pEngine->rpcs[i].pointCount = read_u8(&ptr); + pEngine->rpcs[i].parameter = read_u16(&ptr, se); + pEngine->rpcs[i].points = (FACTRPCPoint*) pEngine->pMalloc( + sizeof(FACTRPCPoint) * + pEngine->rpcs[i].pointCount + ); + for (j = 0; j < pEngine->rpcs[i].pointCount; j += 1) + { + pEngine->rpcs[i].points[j].x = read_f32(&ptr, se); + pEngine->rpcs[i].points[j].y = read_f32(&ptr, se); + pEngine->rpcs[i].points[j].type = read_u8(&ptr); + } + } + } + + /* DSP Preset data */ + if (pEngine->dspPresetCount > 0) + { + FAudio_assert((ptr - start) == dspPresetOffset); + pEngine->dspPresets = (FACTDSPPreset*) pEngine->pMalloc( + sizeof(FACTDSPPreset) * + pEngine->dspPresetCount + ); + pEngine->dspPresetCodes = (uint32_t*) pEngine->pMalloc( + sizeof(uint32_t) * + pEngine->dspPresetCount + ); + for (i = 0; i < pEngine->dspPresetCount; i += 1) + { + pEngine->dspPresetCodes[i] = (uint32_t) (ptr - start); + pEngine->dspPresets[i].accessibility = read_u8(&ptr); + pEngine->dspPresets[i].parameterCount = read_u16(&ptr, se); + ptr += 2; /* Unknown value */ + pEngine->dspPresets[i].parameters = (FACTDSPParameter*) pEngine->pMalloc( + sizeof(FACTDSPParameter) * + pEngine->dspPresets[i].parameterCount + ); /* This will be filled in just a moment... */ + } + + /* DSP Parameter data */ + FAudio_assert((ptr - start) == dspParameterOffset); + for (i = 0; i < pEngine->dspPresetCount; i += 1) + { + for (j = 0; j < pEngine->dspPresets[i].parameterCount; j += 1) + { + pEngine->dspPresets[i].parameters[j].type = read_u8(&ptr); + pEngine->dspPresets[i].parameters[j].value = read_f32(&ptr, se); + pEngine->dspPresets[i].parameters[j].minVal = read_f32(&ptr, se); + pEngine->dspPresets[i].parameters[j].maxVal = read_f32(&ptr, se); + pEngine->dspPresets[i].parameters[j].unknown = read_u16(&ptr, se); + } + } + } + + /* Blob #1, no idea what this is... */ + FAudio_assert((ptr - start) == blob1Offset); + ptr += blob1Count * 2; + + /* Category Name Index data */ + FAudio_assert((ptr - start) == categoryNameIndexOffset); + ptr += pEngine->categoryCount * 6; /* FIXME: index as assert value? */ + + /* Category Name data */ + FAudio_assert((ptr - start) == categoryNameOffset); + pEngine->categoryNames = (char**) pEngine->pMalloc( + sizeof(char*) * + pEngine->categoryCount + ); + for (i = 0; i < pEngine->categoryCount; i += 1) + { + memsize = FAudio_strlen((char*) ptr) + 1; /* Dastardly! */ + pEngine->categoryNames[i] = (char*) pEngine->pMalloc(memsize); + FAudio_memcpy(pEngine->categoryNames[i], ptr, memsize); + ptr += memsize; + } + + /* Blob #2, no idea what this is... */ + FAudio_assert((ptr - start) == blob2Offset); + ptr += blob2Count * 2; + + /* Variable Name Index data */ + FAudio_assert((ptr - start) == variableNameIndexOffset); + ptr += pEngine->variableCount * 6; /* FIXME: index as assert value? */ + + /* Variable Name data */ + FAudio_assert((ptr - start) == variableNameOffset); + pEngine->variableNames = (char**) pEngine->pMalloc( + sizeof(char*) * + pEngine->variableCount + ); + for (i = 0; i < pEngine->variableCount; i += 1) + { + memsize = FAudio_strlen((char*) ptr) + 1; /* Dastardly! */ + pEngine->variableNames[i] = (char*) pEngine->pMalloc(memsize); + FAudio_memcpy(pEngine->variableNames[i], ptr, memsize); + ptr += memsize; + } + + /* Store this pointer in case we're asked to free it */ + if (pParams->globalSettingsFlags & FACT_FLAG_MANAGEDATA) + { + pEngine->settings = pParams->pGlobalSettingsBuffer; + } + + /* Finally. */ + FAudio_assert((ptr - start) == pParams->globalSettingsBufferSize); + return 0; +} + +void FACT_INTERNAL_ParseTrackEvents( + uint8_t **ptr, + uint8_t se, + FACTTrack *track, + FAudioMallocFunc pMalloc +) { + uint32_t evtInfo; + uint8_t minWeight, maxWeight, separator; + uint8_t i; + uint16_t j; + + track->eventCount = read_u8(ptr); + track->events = (FACTEvent*) pMalloc( + sizeof(FACTEvent) * + track->eventCount + ); + FAudio_zero(track->events, sizeof(FACTEvent) * track->eventCount); + for (i = 0; i < track->eventCount; i += 1) + { + evtInfo = read_u32(ptr, se); + track->events[i].randomOffset = read_u16(ptr, se); + + track->events[i].type = evtInfo & 0x001F; + track->events[i].timestamp = (evtInfo >> 5) & 0xFFFF; + + separator = read_u8(ptr); + FAudio_assert(separator == 0xFF); /* Separator? */ + + #define EVTTYPE(t) (track->events[i].type == t) + if (EVTTYPE(FACTEVENT_STOP)) + { + track->events[i].stop.flags = read_u8(ptr); + } + else if (EVTTYPE(FACTEVENT_PLAYWAVE)) + { + /* Basic Wave */ + track->events[i].wave.isComplex = 0; + track->events[i].wave.flags = read_u8(ptr); + track->events[i].wave.simple.track = read_u16(ptr, se); + track->events[i].wave.simple.wavebank = read_u8(ptr); + track->events[i].wave.loopCount = read_u8(ptr); + track->events[i].wave.position = read_u16(ptr, se); + track->events[i].wave.angle = read_u16(ptr, se); + + /* No Effect Variation */ + track->events[i].wave.variationFlags = 0; + } + else if (EVTTYPE(FACTEVENT_PLAYWAVETRACKVARIATION)) + { + /* Complex Wave */ + track->events[i].wave.isComplex = 1; + track->events[i].wave.flags = read_u8(ptr); + track->events[i].wave.loopCount = read_u8(ptr); + track->events[i].wave.position = read_u16(ptr, se); + track->events[i].wave.angle = read_u16(ptr, se); + + /* Track Variation */ + evtInfo = read_u32(ptr, se); + track->events[i].wave.complex.trackCount = evtInfo & 0xFFFF; + track->events[i].wave.complex.variation = (evtInfo >> 16) & 0xFFFF; + *ptr += 4; /* Unknown values */ + track->events[i].wave.complex.tracks = (uint16_t*) pMalloc( + sizeof(uint16_t) * + track->events[i].wave.complex.trackCount + ); + track->events[i].wave.complex.wavebanks = (uint8_t*) pMalloc( + sizeof(uint8_t) * + track->events[i].wave.complex.trackCount + ); + track->events[i].wave.complex.weights = (uint8_t*) pMalloc( + sizeof(uint8_t) * + track->events[i].wave.complex.trackCount + ); + for (j = 0; j < track->events[i].wave.complex.trackCount; j += 1) + { + track->events[i].wave.complex.tracks[j] = read_u16(ptr, se); + track->events[i].wave.complex.wavebanks[j] = read_u8(ptr); + minWeight = read_u8(ptr); + maxWeight = read_u8(ptr); + track->events[i].wave.complex.weights[j] = ( + maxWeight - minWeight + ); + } + + /* No Effect Variation */ + track->events[i].wave.variationFlags = 0; + } + else if (EVTTYPE(FACTEVENT_PLAYWAVEEFFECTVARIATION)) + { + /* Basic Wave */ + track->events[i].wave.isComplex = 0; + track->events[i].wave.flags = read_u8(ptr); + track->events[i].wave.simple.track = read_u16(ptr, se); + track->events[i].wave.simple.wavebank = read_u8(ptr); + track->events[i].wave.loopCount = read_u8(ptr); + track->events[i].wave.position = read_u16(ptr, se); + track->events[i].wave.angle = read_u16(ptr, se); + + /* Effect Variation */ + track->events[i].wave.minPitch = read_s16(ptr, se); + track->events[i].wave.maxPitch = read_s16(ptr, se); + track->events[i].wave.minVolume = read_volbyte(ptr); + track->events[i].wave.maxVolume = read_volbyte(ptr); + track->events[i].wave.minFrequency = read_f32(ptr, se); + track->events[i].wave.maxFrequency = read_f32(ptr, se); + track->events[i].wave.minQFactor = read_f32(ptr, se); + track->events[i].wave.maxQFactor = read_f32(ptr, se); + track->events[i].wave.variationFlags = read_u16(ptr, se); + } + else if (EVTTYPE(FACTEVENT_PLAYWAVETRACKEFFECTVARIATION)) + { + /* Complex Wave */ + track->events[i].wave.isComplex = 1; + track->events[i].wave.flags = read_u8(ptr); + track->events[i].wave.loopCount = read_u8(ptr); + track->events[i].wave.position = read_u16(ptr, se); + track->events[i].wave.angle = read_u16(ptr, se); + + /* Effect Variation */ + track->events[i].wave.minPitch = read_s16(ptr, se); + track->events[i].wave.maxPitch = read_s16(ptr, se); + track->events[i].wave.minVolume = read_volbyte(ptr); + track->events[i].wave.maxVolume = read_volbyte(ptr); + track->events[i].wave.minFrequency = read_f32(ptr, se); + track->events[i].wave.maxFrequency = read_f32(ptr, se); + track->events[i].wave.minQFactor = read_f32(ptr, se); + track->events[i].wave.maxQFactor = read_f32(ptr, se); + track->events[i].wave.variationFlags = read_u16(ptr, se); + + /* Track Variation */ + evtInfo = read_u32(ptr, se); + track->events[i].wave.complex.trackCount = evtInfo & 0xFFFF; + track->events[i].wave.complex.variation = (evtInfo >> 16) & 0xFFFF; + *ptr += 4; /* Unknown values */ + track->events[i].wave.complex.tracks = (uint16_t*) pMalloc( + sizeof(uint16_t) * + track->events[i].wave.complex.trackCount + ); + track->events[i].wave.complex.wavebanks = (uint8_t*) pMalloc( + sizeof(uint8_t) * + track->events[i].wave.complex.trackCount + ); + track->events[i].wave.complex.weights = (uint8_t*) pMalloc( + sizeof(uint8_t) * + track->events[i].wave.complex.trackCount + ); + for (j = 0; j < track->events[i].wave.complex.trackCount; j += 1) + { + track->events[i].wave.complex.tracks[j] = read_u16(ptr, se); + track->events[i].wave.complex.wavebanks[j] = read_u8(ptr); + minWeight = read_u8(ptr); + maxWeight = read_u8(ptr); + track->events[i].wave.complex.weights[j] = ( + maxWeight - minWeight + ); + } + } + else if ( EVTTYPE(FACTEVENT_PITCH) || + EVTTYPE(FACTEVENT_VOLUME) || + EVTTYPE(FACTEVENT_PITCHREPEATING) || + EVTTYPE(FACTEVENT_VOLUMEREPEATING) ) + { + track->events[i].value.settings = read_u8(ptr); + if (track->events[i].value.settings & 1) /* Ramp */ + { + track->events[i].value.repeats = 0; + track->events[i].value.ramp.initialValue = read_f32(ptr, se); + track->events[i].value.ramp.initialSlope = read_f32(ptr, se) * 100; + track->events[i].value.ramp.slopeDelta = read_f32(ptr, se); + track->events[i].value.ramp.duration = read_u16(ptr, se); + } + else /* Equation */ + { + track->events[i].value.equation.flags = read_u8(ptr); + + /* SetValue, SetRandomValue, anything else? */ + FAudio_assert(track->events[i].value.equation.flags & 0x0C); + + track->events[i].value.equation.value1 = read_f32(ptr, se); + track->events[i].value.equation.value2 = read_f32(ptr, se); + + *ptr += 5; /* Unknown values */ + + if ( EVTTYPE(FACTEVENT_PITCHREPEATING) || + EVTTYPE(FACTEVENT_VOLUMEREPEATING) ) + { + track->events[i].value.repeats = read_u16(ptr, se); + track->events[i].value.frequency = read_u16(ptr, se); + } + else + { + track->events[i].value.repeats = 0; + } + } + } + else if (EVTTYPE(FACTEVENT_MARKER)) + { + track->events[i].marker.marker = read_u32(ptr, se); + track->events[i].marker.repeats = 0; + track->events[i].marker.frequency = 0; + } + else if (EVTTYPE(FACTEVENT_MARKERREPEATING)) + { + track->events[i].marker.marker = read_u32(ptr, se); + track->events[i].marker.repeats = read_u16(ptr, se); + track->events[i].marker.frequency = read_u16(ptr, se); + } + else + { + FAudio_assert(0 && "Unknown event type!"); + } + #undef EVTTYPE + } +} + +uint32_t FACT_INTERNAL_ParseSoundBank( + FACTAudioEngine *pEngine, + const void *pvBuffer, + uint32_t dwSize, + FACTSoundBank **ppSoundBank +) { + FACTSoundBank *sb; + uint16_t contentVersion, + cueSimpleCount, + cueComplexCount, + cueTotalAlign; + int32_t cueSimpleOffset, + cueComplexOffset, + cueNameOffset, + variationOffset, + transitionOffset, + wavebankNameOffset, + cueHashOffset, + cueNameIndexOffset, + soundOffset; + uint32_t entryCountAndFlags; + uint16_t filterData; + uint8_t platform; + size_t memsize; + uint16_t i, j, k, cur, tool; + uint8_t *ptrBookmark; + + uint8_t *ptr = (uint8_t*) pvBuffer; + uint8_t *start = ptr; + + uint32_t magic = read_u32(&ptr, 0); + uint8_t se = magic == 0x5344424B; /* Swap Endian */ + + if (magic != 0x4B424453 && magic != 0x5344424B) /* 'SDBK' */ + { + return -1; /* TODO: NOT XACT FILE */ + } + + contentVersion = read_u16(&ptr, se); + if (!FACT_INTERNAL_SupportedContent(contentVersion)) + { + return -2; + } + + tool = read_u16(&ptr, se); /* Tool version */ + if (tool != 43) + { + return -3; + } + + /* CRC, unused */ + ptr += 2; + + /* Last modified, unused */ + ptr += 8; + + /* Windows == 1, Xbox == 3. 0 is unknown, probably old content */ + platform = read_u8(&ptr); + if ( platform != 0 && + platform != 1 && + platform != 3 ) + { + return -4; /* TODO: WRONG PLATFORM */ + } + + sb = (FACTSoundBank*) pEngine->pMalloc(sizeof(FACTSoundBank)); + sb->parentEngine = pEngine; + sb->cueList = NULL; + sb->notifyOnDestroy = 0; + sb->usercontext = NULL; + + cueSimpleCount = read_u16(&ptr, se); + cueComplexCount = read_u16(&ptr, se); + + ptr += 2; /* Unknown value */ + + cueTotalAlign = read_u16(&ptr, se); /* FIXME: Why? */ + sb->cueCount = cueSimpleCount + cueComplexCount; + sb->wavebankCount = read_u8(&ptr); + sb->soundCount = read_u16(&ptr, se); + + /* Cue name length, unused */ + ptr += 2; + + ptr += 2; /* Unknown value */ + + cueSimpleOffset = read_s32(&ptr, se); + cueComplexOffset = read_s32(&ptr, se); + cueNameOffset = read_s32(&ptr, se); + + ptr += 4; /* Unknown value */ + + variationOffset = read_s32(&ptr, se); + transitionOffset = read_s32(&ptr, se); + wavebankNameOffset = read_s32(&ptr, se); + cueHashOffset = read_s32(&ptr, se); + cueNameIndexOffset = read_s32(&ptr, se); + soundOffset = read_s32(&ptr, se); + + /* SoundBank Name */ + memsize = FAudio_strlen((char*) ptr) + 1; /* Dastardly! */ + sb->name = (char*) pEngine->pMalloc(memsize); + FAudio_memcpy(sb->name, ptr, memsize); + ptr += 64; + + /* WaveBank Name data */ + FAudio_assert((ptr - start) == wavebankNameOffset); + sb->wavebankNames = (char**) pEngine->pMalloc( + sizeof(char*) * + sb->wavebankCount + ); + for (i = 0; i < sb->wavebankCount; i += 1) + { + memsize = FAudio_strlen((char*) ptr) + 1; + sb->wavebankNames[i] = (char*) pEngine->pMalloc(memsize); + FAudio_memcpy(sb->wavebankNames[i], ptr, memsize); + ptr += 64; + } + + /* Sound data */ + FAudio_assert((ptr - start) == soundOffset); + sb->sounds = (FACTSound*) pEngine->pMalloc( + sizeof(FACTSound) * + sb->soundCount + ); + sb->soundCodes = (uint32_t*) pEngine->pMalloc( + sizeof(uint32_t) * + sb->soundCount + ); + for (i = 0; i < sb->soundCount; i += 1) + { + sb->soundCodes[i] = (uint32_t) (ptr - start); + sb->sounds[i].flags = read_u8(&ptr); + sb->sounds[i].category = read_u16(&ptr, se); + sb->sounds[i].volume = read_volbyte(&ptr); + sb->sounds[i].pitch = read_s16(&ptr, se); + sb->sounds[i].priority = read_u8(&ptr); + + /* Length of sound entry, unused */ + ptr += 2; + + /* Simple/Complex Track data */ + if (sb->sounds[i].flags & 0x01) + { + sb->sounds[i].trackCount = read_u8(&ptr); + memsize = sizeof(FACTTrack) * sb->sounds[i].trackCount; + sb->sounds[i].tracks = (FACTTrack*) pEngine->pMalloc(memsize); + FAudio_zero(sb->sounds[i].tracks, memsize); + } + else + { + sb->sounds[i].trackCount = 1; + memsize = sizeof(FACTTrack) * sb->sounds[i].trackCount; + sb->sounds[i].tracks = (FACTTrack*) pEngine->pMalloc(memsize); + FAudio_zero(sb->sounds[i].tracks, memsize); + sb->sounds[i].tracks[0].volume = 0.0f; + sb->sounds[i].tracks[0].filter = 0xFF; + sb->sounds[i].tracks[0].eventCount = 1; + sb->sounds[i].tracks[0].events = (FACTEvent*) pEngine->pMalloc( + sizeof(FACTEvent) + ); + FAudio_zero( + sb->sounds[i].tracks[0].events, + sizeof(FACTEvent) + ); + sb->sounds[i].tracks[0].events[0].type = FACTEVENT_PLAYWAVE; + sb->sounds[i].tracks[0].events[0].wave.position = 0; /* FIXME */ + sb->sounds[i].tracks[0].events[0].wave.angle = 0; /* FIXME */ + sb->sounds[i].tracks[0].events[0].wave.simple.track = read_u16(&ptr, se); + sb->sounds[i].tracks[0].events[0].wave.simple.wavebank = read_u8(&ptr); + } + + /* RPC Code data */ + if (sb->sounds[i].flags & 0x0E) + { + const uint16_t rpcDataLength = read_u16(&ptr, se); + ptrBookmark = ptr - 2; + + #define COPYRPCBLOCK(loc) \ + loc.rpcCodeCount = read_u8(&ptr); \ + memsize = sizeof(uint32_t) * loc.rpcCodeCount; \ + loc.rpcCodes = (uint32_t*) pEngine->pMalloc(memsize); \ + for (k = 0; k < loc.rpcCodeCount; k += 1) \ + { \ + loc.rpcCodes[k] = read_u32(&ptr, se); \ + } \ + + /* Sound has attached RPCs */ + if (sb->sounds[i].flags & 0x02) + { + COPYRPCBLOCK(sb->sounds[i]) + } + else + { + sb->sounds[i].rpcCodeCount = 0; + sb->sounds[i].rpcCodes = NULL; + } + + /* Tracks have attached RPCs */ + if (sb->sounds[i].flags & 0x04) + { + for (j = 0; j < sb->sounds[i].trackCount; j += 1) + { + COPYRPCBLOCK(sb->sounds[i].tracks[j]) + } + } + else + { + for (j = 0; j < sb->sounds[i].trackCount; j += 1) + { + sb->sounds[i].tracks[j].rpcCodeCount = 0; + sb->sounds[i].tracks[j].rpcCodes = NULL; + } + } + + #undef COPYRPCBLOCK + + /* FIXME: Does 0x08 mean something for RPCs...? */ + FAudio_assert((ptr - ptrBookmark) == rpcDataLength); + } + else + { + sb->sounds[i].rpcCodeCount = 0; + sb->sounds[i].rpcCodes = NULL; + for (j = 0; j < sb->sounds[i].trackCount; j += 1) + { + sb->sounds[i].tracks[j].rpcCodeCount = 0; + sb->sounds[i].tracks[j].rpcCodes = NULL; + } + } + + /* DSP Preset Code data */ + if (sb->sounds[i].flags & 0x10) + { + /* DSP presets length, unused */ + ptr += 2; + + sb->sounds[i].dspCodeCount = read_u8(&ptr); + memsize = sizeof(uint32_t) * sb->sounds[i].dspCodeCount; + sb->sounds[i].dspCodes = (uint32_t*) pEngine->pMalloc(memsize); + for (j = 0; j < sb->sounds[i].dspCodeCount; j += 1) + { + sb->sounds[i].dspCodes[j] = read_u32(&ptr, se); + } + } + else + { + sb->sounds[i].dspCodeCount = 0; + sb->sounds[i].dspCodes = NULL; + } + + /* Track data */ + if (sb->sounds[i].flags & 0x01) + { + for (j = 0; j < sb->sounds[i].trackCount; j += 1) + { + sb->sounds[i].tracks[j].volume = read_volbyte(&ptr); + + sb->sounds[i].tracks[j].code = read_u32(&ptr, se); + + if (contentVersion == FACT_CONTENT_VERSION_3_0) + { + /* 3.0 doesn't have track filter data */ + sb->sounds[i].tracks[j].filter = 0xFF; + sb->sounds[i].tracks[j].qfactor = 0; + sb->sounds[i].tracks[j].frequency = 0; + continue; + } + + filterData = read_u16(&ptr, se); + if (filterData & 0x0001) + { + sb->sounds[i].tracks[j].filter = + (filterData >> 1) & 0x02; + } + else + { + /* Huh...? */ + sb->sounds[i].tracks[j].filter = 0xFF; + } + sb->sounds[i].tracks[j].qfactor = (filterData >> 8) & 0xFF; + sb->sounds[i].tracks[j].frequency = read_u16(&ptr, se); + } + + /* All Track events are stored at the end of the block */ + for (j = 0; j < sb->sounds[i].trackCount; j += 1) + { + FAudio_assert((ptr - start) == sb->sounds[i].tracks[j].code); + FACT_INTERNAL_ParseTrackEvents( + &ptr, + se, + &sb->sounds[i].tracks[j], + pEngine->pMalloc + ); + } + } + } + + /* All Cue data */ + sb->variationCount = 0; + sb->transitionCount = 0; + sb->cues = (FACTCueData*) pEngine->pMalloc( + sizeof(FACTCueData) * + sb->cueCount + ); + cur = 0; + + /* Simple Cue data */ + FAudio_assert(cueSimpleCount == 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) + { + 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; + } + } + + /* Variation data */ + if (sb->variationCount > 0) + { + FAudio_assert((ptr - start) == variationOffset); + sb->variations = (FACTVariationTable*) pEngine->pMalloc( + sizeof(FACTVariationTable) * + sb->variationCount + ); + sb->variationCodes = (uint32_t*) pEngine->pMalloc( + sizeof(uint32_t) * + sb->variationCount + ); + } + else + { + sb->variations = NULL; + sb->variationCodes = NULL; + } + for (i = 0; i < sb->variationCount; i += 1) + { + sb->variationCodes[i] = (uint32_t) (ptr - start); + entryCountAndFlags = read_u32(&ptr, se); + sb->variations[i].entryCount = entryCountAndFlags & 0xFFFF; + sb->variations[i].flags = (entryCountAndFlags >> (16 + 3)) & 0x07; + ptr += 2; /* Unknown value */ + sb->variations[i].variable = read_s16(&ptr, se); + memsize = sizeof(FACTVariation) * sb->variations[i].entryCount; + sb->variations[i].entries = (FACTVariation*) pEngine->pMalloc( + memsize + ); + FAudio_zero(sb->variations[i].entries, memsize); + + if (sb->variations[i].flags == 0) + { + /* Wave with byte min/max */ + sb->variations[i].isComplex = 0; + for (j = 0; j < sb->variations[i].entryCount; j += 1) + { + sb->variations[i].entries[j].simple.track = read_u16(&ptr, se); + sb->variations[i].entries[j].simple.wavebank = read_u8(&ptr); + sb->variations[i].entries[j].minWeight = read_u8(&ptr) / 255.0f; + sb->variations[i].entries[j].maxWeight = read_u8(&ptr) / 255.0f; + } + } + else if (sb->variations[i].flags == 1) + { + /* Complex with byte min/max */ + sb->variations[i].isComplex = 1; + for (j = 0; j < sb->variations[i].entryCount; j += 1) + { + sb->variations[i].entries[j].soundCode = read_u32(&ptr, se); + sb->variations[i].entries[j].minWeight = read_u8(&ptr) / 255.0f; + sb->variations[i].entries[j].maxWeight = read_u8(&ptr) / 255.0f; + } + } + else if (sb->variations[i].flags == 3) + { + /* Complex Interactive Variation with float min/max */ + sb->variations[i].isComplex = 1; + for (j = 0; j < sb->variations[i].entryCount; j += 1) + { + sb->variations[i].entries[j].soundCode = read_u32(&ptr, se); + sb->variations[i].entries[j].minWeight = read_f32(&ptr, se); + sb->variations[i].entries[j].maxWeight = read_f32(&ptr, se); + sb->variations[i].entries[j].linger = read_u32(&ptr, se); + } + } + else if (sb->variations[i].flags == 4) + { + /* Compact Wave */ + sb->variations[i].isComplex = 0; + for (j = 0; j < sb->variations[i].entryCount; j += 1) + { + sb->variations[i].entries[j].simple.track = read_u16(&ptr, se); + sb->variations[i].entries[j].simple.wavebank = read_u8(&ptr); + sb->variations[i].entries[j].minWeight = 0.0f; + sb->variations[i].entries[j].maxWeight = 1.0f; + } + } + else + { + FAudio_assert(0 && "Unknown variation type!"); + } + } + + /* Transition data */ + if (sb->transitionCount > 0) + { + FAudio_assert((ptr - start) == transitionOffset); + sb->transitions = (FACTTransitionTable*) pEngine->pMalloc( + sizeof(FACTTransitionTable) * + sb->transitionCount + ); + sb->transitionCodes = (uint32_t*) pEngine->pMalloc( + sizeof(uint32_t) * + sb->transitionCount + ); + } + else + { + sb->transitions = NULL; + sb->transitionCodes = NULL; + } + for (i = 0; i < sb->transitionCount; i += 1) + { + sb->transitionCodes[i] = (uint32_t) (ptr - start); + sb->transitions[i].entryCount = read_u32(&ptr, se); + memsize = sizeof(FACTTransition) * sb->transitions[i].entryCount; + sb->transitions[i].entries = (FACTTransition*) pEngine->pMalloc( + memsize + ); + FAudio_zero(sb->transitions[i].entries, memsize); + for (j = 0; j < sb->transitions[i].entryCount; j += 1) + { + sb->transitions[i].entries[j].soundCode = read_s32(&ptr, se); + sb->transitions[i].entries[j].srcMarkerMin = read_u32(&ptr, se); + sb->transitions[i].entries[j].srcMarkerMax = read_u32(&ptr, se); + sb->transitions[i].entries[j].dstMarkerMin = read_u32(&ptr, se); + sb->transitions[i].entries[j].dstMarkerMax = read_u32(&ptr, se); + sb->transitions[i].entries[j].fadeIn = read_u16(&ptr, se); + sb->transitions[i].entries[j].fadeOut = read_u16(&ptr, se); + sb->transitions[i].entries[j].flags = read_u16(&ptr, se); + } + } + + /* Cue Hash data? No idea what this is... */ + if (cueHashOffset != -1) + { + FAudio_assert((ptr - start) == cueHashOffset); + ptr += 2 * cueTotalAlign; + } + + /* Cue Name Index data */ + if (cueNameIndexOffset != -1) + { + FAudio_assert((ptr - start) == cueNameIndexOffset); + ptr += 6 * sb->cueCount; /* FIXME: index as assert value? */ + } + + /* Cue Name data */ + if (cueNameOffset != -1) + { + FAudio_assert((ptr - start) == cueNameOffset); + sb->cueNames = (char**) pEngine->pMalloc( + sizeof(char*) * + sb->cueCount + ); + for (i = 0; i < sb->cueCount; i += 1) + { + memsize = FAudio_strlen((char*) ptr) + 1; + sb->cueNames[i] = (char*) pEngine->pMalloc(memsize); + FAudio_memcpy(sb->cueNames[i], ptr, memsize); + ptr += memsize; + } + } + else + { + sb->cueNames = NULL; + } + + /* Add to the Engine SoundBank list */ + LinkedList_AddEntry( + &pEngine->sbList, + sb, + pEngine->sbLock, + pEngine->pMalloc + ); + + /* Finally. */ + FAudio_assert((ptr - start) == dwSize); + *ppSoundBank = sb; + return 0; +} + +/* This parser is based on the unxwb project, written by Luigi Auriemma. + * + * While the unxwb project was released under the GPL, Luigi has given us + * permission to use the unxwb sources under the zlib license. + * + * The unxwb website can be found here: + * + * http://aluigi.altervista.org/papers.htm#xbox + */ +uint32_t FACT_INTERNAL_ParseWaveBank( + FACTAudioEngine *pEngine, + void* io, + uint32_t offset, + uint32_t packetSize, + FACTReadFileCallback pRead, + FACTGetOverlappedResultCallback pOverlap, + uint16_t isStreaming, + FACTWaveBank **ppWaveBank +) { + uint8_t se = 0; /* Swap Endian */ + FACTWaveBank *wb; + size_t memsize; + uint32_t i, j; + FACTWaveBankHeader header; + FACTWaveBankData wbinfo; + uint32_t compactEntry; + int32_t seekTableOffset; + uint32_t fileOffset; + uint8_t *packetBuffer = NULL; + uint32_t packetBufferLen = 0; + uint16_t *pcm; + + #define SEEKSET(loc) \ + fileOffset = offset + loc; + #define SEEKCUR(loc) \ + fileOffset += loc; + #define READ(dst, size) \ + FACT_INTERNAL_ReadFile( \ + pRead, \ + pOverlap, \ + io, \ + fileOffset, \ + packetSize, \ + &packetBuffer, \ + &packetBufferLen, \ + pEngine->pRealloc, \ + dst, \ + size \ + ); \ + SEEKCUR(size) + + #define DOSWAP_16(x) x = FAudio_swap16BE(x) + #define DOSWAP_32(x) x = FAudio_swap32BE(x) + #define DOSWAP_64(x) x = FAudio_swap64BE(x) + + fileOffset = offset; + READ(&header, sizeof(header)) + se = header.dwSignature == 0x57424E44; + if (se) + { + DOSWAP_32(header.dwSignature); + DOSWAP_32(header.dwVersion); + DOSWAP_32(header.dwHeaderVersion); + for (i = 0; i < FACT_WAVEBANK_SEGIDX_COUNT; i += 1) + { + DOSWAP_32(header.Segments[i].dwOffset); + DOSWAP_32(header.Segments[i].dwLength); + } + } + if (header.dwSignature != 0x444E4257) + { + return -1; /* TODO: NOT XACT FILE */ + } + + if (!FACT_INTERNAL_SupportedContent(header.dwVersion)) + { + return -2; + } + + if (!FACT_INTERNAL_SupportedWBContent(header.dwHeaderVersion)) + { + return -3; + } + + wb = (FACTWaveBank*) pEngine->pMalloc(sizeof(FACTWaveBank)); + wb->parentEngine = pEngine; + wb->waveList = NULL; + wb->waveLock = FAudio_PlatformCreateMutex(); + wb->packetSize = packetSize; + wb->io = io; + wb->notifyOnDestroy = 0; + wb->usercontext = NULL; + + /* WaveBank Data */ + SEEKSET(header.Segments[FACT_WAVEBANK_SEGIDX_BANKDATA].dwOffset) + READ(&wbinfo, sizeof(wbinfo)) + if (se) + { + DOSWAP_32(wbinfo.dwFlags); + DOSWAP_32(wbinfo.dwEntryCount); + DOSWAP_32(wbinfo.dwEntryMetaDataElementSize); + DOSWAP_32(wbinfo.dwEntryNameElementSize); + DOSWAP_32(wbinfo.dwAlignment); + DOSWAP_32(wbinfo.CompactFormat.dwValue); + DOSWAP_64(wbinfo.BuildTime); + } + wb->streaming = (wbinfo.dwFlags & FACT_WAVEBANK_TYPE_STREAMING); + wb->entryCount = wbinfo.dwEntryCount; + memsize = FAudio_strlen(wbinfo.szBankName) + 1; + wb->name = (char*) pEngine->pMalloc(memsize); + FAudio_memcpy(wb->name, wbinfo.szBankName, memsize); + memsize = sizeof(FACTWaveBankEntry) * wbinfo.dwEntryCount; + wb->entries = (FACTWaveBankEntry*) pEngine->pMalloc(memsize); + FAudio_zero(wb->entries, memsize); + memsize = sizeof(uint32_t) * wbinfo.dwEntryCount; + wb->entryRefs = (uint32_t*) pEngine->pMalloc(memsize); + FAudio_zero(wb->entryRefs, memsize); + + /* FIXME: How much do we care about this? */ + FAudio_assert(wb->streaming == isStreaming); + wb->streaming = isStreaming; + + /* WaveBank Entry Metadata */ + SEEKSET(header.Segments[FACT_WAVEBANK_SEGIDX_ENTRYMETADATA].dwOffset) + if (wbinfo.dwFlags & FACT_WAVEBANK_FLAGS_COMPACT) + { + for (i = 0; i < wbinfo.dwEntryCount - 1; i += 1) + { + READ(&compactEntry, sizeof(compactEntry)) + if (se) + { + DOSWAP_32(compactEntry); + } + wb->entries[i].PlayRegion.dwOffset = ( + (compactEntry & ((1 << 21) - 1)) * + wbinfo.dwAlignment + ); + wb->entries[i].PlayRegion.dwLength = ( + (compactEntry >> 21) & ((1 << 11) - 1) + ); + + /* TODO: Deviation table */ + SEEKCUR(wbinfo.dwEntryMetaDataElementSize) + wb->entries[i].PlayRegion.dwLength = ( + (compactEntry & ((1 << 21) - 1)) * + wbinfo.dwAlignment + ) - wb->entries[i].PlayRegion.dwOffset; + + wb->entries[i].PlayRegion.dwOffset += + header.Segments[FACT_WAVEBANK_SEGIDX_ENTRYWAVEDATA].dwOffset; + } + + READ(&compactEntry, sizeof(compactEntry)) + if (se) + { + DOSWAP_32(compactEntry); + } + wb->entries[i].PlayRegion.dwOffset = ( + (compactEntry & ((1 << 21) - 1)) * + wbinfo.dwAlignment + ); + + /* TODO: Deviation table */ + SEEKCUR(wbinfo.dwEntryMetaDataElementSize) + wb->entries[i].PlayRegion.dwLength = ( + header.Segments[FACT_WAVEBANK_SEGIDX_ENTRYWAVEDATA].dwLength - + wb->entries[i].PlayRegion.dwOffset + ); + + wb->entries[i].PlayRegion.dwOffset += + header.Segments[FACT_WAVEBANK_SEGIDX_ENTRYWAVEDATA].dwOffset; + } + else + { + for (i = 0; i < wbinfo.dwEntryCount; i += 1) + { + READ(&wb->entries[i], wbinfo.dwEntryMetaDataElementSize) + if (se) + { + DOSWAP_32(wb->entries[i].dwFlagsAndDuration); + DOSWAP_32(wb->entries[i].Format.dwValue); + DOSWAP_32(wb->entries[i].PlayRegion.dwOffset); + DOSWAP_32(wb->entries[i].PlayRegion.dwLength); + DOSWAP_32(wb->entries[i].LoopRegion.dwStartSample); + DOSWAP_32(wb->entries[i].LoopRegion.dwTotalSamples); + } + wb->entries[i].PlayRegion.dwOffset += + header.Segments[FACT_WAVEBANK_SEGIDX_ENTRYWAVEDATA].dwOffset; + + /* If it's in-memory big-endian PCM, swap! */ + if ( se && + !wb->streaming && + wb->entries[i].Format.wFormatTag == 0x0 && + wb->entries[i].Format.wBitsPerSample == 1 ) + { + pcm = (uint16_t*) FAudio_memptr( + (FAudioIOStream*) wb->io, + wb->entries[i].PlayRegion.dwOffset + ); + for (j = 0; j < wb->entries[i].PlayRegion.dwLength; j += 2, pcm += 1) + { + DOSWAP_16(*pcm); + } + } + } + + /* FIXME: This is a bit hacky. */ + if (wbinfo.dwEntryMetaDataElementSize < 24) + { + for (i = 0; i < wbinfo.dwEntryCount; i += 1) + { + wb->entries[i].PlayRegion.dwLength = + header.Segments[FACT_WAVEBANK_SEGIDX_ENTRYWAVEDATA].dwLength; + } + } + } + + /* WaveBank Seek Tables */ + if ( wbinfo.dwFlags & FACT_WAVEBANK_FLAGS_SEEKTABLES && + header.Segments[FACT_WAVEBANK_SEGIDX_SEEKTABLES].dwLength > 0 ) + { + /* The seek table data layout is an absolute disaster! */ + wb->seekTables = (FACTSeekTable*) pEngine->pMalloc( + wbinfo.dwEntryCount * sizeof(FACTSeekTable) + ); + for (i = 0; i < wbinfo.dwEntryCount; i += 1) + { + /* Get the table offset... */ + SEEKSET( + header.Segments[FACT_WAVEBANK_SEGIDX_SEEKTABLES].dwOffset + + i * sizeof(uint32_t) + ) + READ(&seekTableOffset, sizeof(int32_t)) + if (se) + { + DOSWAP_32(seekTableOffset); + } + + /* If the offset is -1, this wave needs no table */ + if (seekTableOffset == -1) + { + wb->seekTables[i].entryCount = 0; + wb->seekTables[i].entries = NULL; + continue; + } + + /* Go to the table offset, after the offset table... */ + SEEKSET( + header.Segments[FACT_WAVEBANK_SEGIDX_SEEKTABLES].dwOffset + + (wbinfo.dwEntryCount * sizeof(uint32_t)) + + seekTableOffset + ) + + /* Read the table, finally. */ + READ(&wb->seekTables[i].entryCount, sizeof(uint32_t)) + if (se) + { + DOSWAP_32(wb->seekTables[i].entryCount); + } + wb->seekTables[i].entries = (uint32_t*) pEngine->pMalloc( + wb->seekTables[i].entryCount * sizeof(uint32_t) + ); + READ( + wb->seekTables[i].entries, + wb->seekTables[i].entryCount * sizeof(uint32_t) + ) + if (se) + { + for (j = 0; j < wb->seekTables[i].entryCount; j += 1) + { + DOSWAP_32(wb->seekTables[i].entries[j]); + } + } + } + } + else + { + wb->seekTables = NULL; + } + + /* WaveBank Entry Names */ + if (wbinfo.dwFlags & FACT_WAVEBANK_FLAGS_ENTRYNAMES) + { + SEEKSET(header.Segments[FACT_WAVEBANK_SEGIDX_ENTRYNAMES].dwOffset) + wb->waveBankNames = (char*) pEngine->pMalloc(64 * wbinfo.dwEntryCount); + READ(wb->waveBankNames, 64 * wbinfo.dwEntryCount); + } + else + { + wb->waveBankNames = NULL; + } + + /* Add to the Engine WaveBank list */ + LinkedList_AddEntry( + &pEngine->wbList, + wb, + pEngine->wbLock, + pEngine->pMalloc + ); + + /* Finally. */ + wb->packetBuffer = packetBuffer; + wb->packetBufferLen = packetBufferLen; + *ppWaveBank = wb; + return 0; +} + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/src/FACT_internal.h b/libs/faudio/src/FACT_internal.h new file mode 100644 index 00000000000..6bf522e64c4 --- /dev/null +++ b/libs/faudio/src/FACT_internal.h @@ -0,0 +1,644 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * Ethan "flibitijibibo" Lee + * + */ + +#include "FACT.h" +#include "FACT3D.h" +#include "FAudio_internal.h" + +/* Internal AudioEngine Types */ + +typedef struct FACTAudioCategory +{ + uint8_t instanceLimit; + uint16_t fadeInMS; + uint16_t fadeOutMS; + uint8_t maxInstanceBehavior; + int16_t parentCategory; + float volume; + uint8_t visibility; + + uint8_t instanceCount; + float currentVolume; +} FACTAudioCategory; + +typedef struct FACTVariable +{ + uint8_t accessibility; + float initialValue; + float minValue; + float maxValue; +} FACTVariable; + +typedef struct FACTRPCPoint +{ + float x; + float y; + uint8_t type; +} FACTRPCPoint; + +typedef enum FACTRPCParameter +{ + RPC_PARAMETER_VOLUME, + RPC_PARAMETER_PITCH, + RPC_PARAMETER_REVERBSEND, + RPC_PARAMETER_FILTERFREQUENCY, + RPC_PARAMETER_FILTERQFACTOR, + RPC_PARAMETER_COUNT /* If >=, DSP Parameter! */ +} FACTRPCParameter; + +typedef struct FACTRPC +{ + uint16_t variable; + uint8_t pointCount; + uint16_t parameter; + FACTRPCPoint *points; +} FACTRPC; + +typedef struct FACTDSPParameter +{ + uint8_t type; + float value; + float minVal; + float maxVal; + uint16_t unknown; +} FACTDSPParameter; + +typedef struct FACTDSPPreset +{ + uint8_t accessibility; + uint16_t parameterCount; + FACTDSPParameter *parameters; +} FACTDSPPreset; + +typedef enum FACTNoticationsFlags +{ + NOTIFY_CUEPREPARED = 0x00000001, + NOTIFY_CUEPLAY = 0x00000002, + NOTIFY_CUESTOP = 0x00000004, + NOTIFY_CUEDESTROY = 0x00000008, + NOTIFY_MARKER = 0x00000010, + NOTIFY_SOUNDBANKDESTROY = 0x00000020, + NOTIFY_WAVEBANKDESTROY = 0x00000040, + NOTIFY_LOCALVARIABLECHANGED = 0x00000080, + NOTIFY_GLOBALVARIABLECHANGED = 0x00000100, + NOTIFY_GUICONNECTED = 0x00000200, + NOTIFY_GUIDISCONNECTED = 0x00000400, + NOTIFY_WAVEPREPARED = 0x00000800, + NOTIFY_WAVEPLAY = 0x00001000, + NOTIFY_WAVESTOP = 0x00002000, + NOTIFY_WAVELOOPED = 0x00004000, + NOTIFY_WAVEDESTROY = 0x00008000, + NOTIFY_WAVEBANKPREPARED = 0x00010000 +} FACTNoticationsFlags; + +/* Internal SoundBank Types */ + +typedef enum +{ + FACTEVENT_STOP = 0, + FACTEVENT_PLAYWAVE = 1, + FACTEVENT_PLAYWAVETRACKVARIATION = 3, + FACTEVENT_PLAYWAVEEFFECTVARIATION = 4, + FACTEVENT_PLAYWAVETRACKEFFECTVARIATION = 6, + FACTEVENT_PITCH = 7, + FACTEVENT_VOLUME = 8, + FACTEVENT_MARKER = 9, + FACTEVENT_PITCHREPEATING = 16, + FACTEVENT_VOLUMEREPEATING = 17, + FACTEVENT_MARKERREPEATING = 18 +} FACTEventType; + +typedef struct FACTEvent +{ + uint16_t type; + uint16_t timestamp; + uint16_t randomOffset; + FAUDIONAMELESS union + { + /* Play Wave Event */ + struct + { + uint8_t flags; + uint8_t loopCount; + uint16_t position; + uint16_t angle; + + /* Track Variation */ + uint8_t isComplex; + FAUDIONAMELESS union + { + struct + { + uint16_t track; + uint8_t wavebank; + } simple; + struct + { + uint16_t variation; + uint16_t trackCount; + uint16_t *tracks; + uint8_t *wavebanks; + uint8_t *weights; + } complex; + }; + + /* Effect Variation */ + int16_t minPitch; + int16_t maxPitch; + float minVolume; + float maxVolume; + float minFrequency; + float maxFrequency; + float minQFactor; + float maxQFactor; + uint16_t variationFlags; + } wave; + /* Set Pitch/Volume Event */ + struct + { + uint8_t settings; + uint16_t repeats; + uint16_t frequency; + FAUDIONAMELESS union + { + struct + { + float initialValue; + float initialSlope; + float slopeDelta; + uint16_t duration; + } ramp; + struct + { + uint8_t flags; + float value1; + float value2; + } equation; + }; + } value; + /* Stop Event */ + struct + { + uint8_t flags; + } stop; + /* Marker Event */ + struct + { + uint32_t marker; + uint16_t repeats; + uint16_t frequency; + } marker; + }; +} FACTEvent; + +typedef struct FACTTrack +{ + uint32_t code; + + float volume; + uint8_t filter; + uint8_t qfactor; + uint16_t frequency; + + uint8_t rpcCodeCount; + uint32_t *rpcCodes; + + uint8_t eventCount; + FACTEvent *events; +} FACTTrack; + +typedef struct FACTSound +{ + uint8_t flags; + uint16_t category; + float volume; + int16_t pitch; + uint8_t priority; + + uint8_t trackCount; + uint8_t rpcCodeCount; + uint8_t dspCodeCount; + + FACTTrack *tracks; + uint32_t *rpcCodes; + uint32_t *dspCodes; +} FACTSound; + +typedef struct FACTCueData +{ + uint8_t flags; + uint32_t sbCode; + uint32_t transitionOffset; + uint8_t instanceLimit; + uint16_t fadeInMS; + uint16_t fadeOutMS; + uint8_t maxInstanceBehavior; + uint8_t instanceCount; +} FACTCueData; + +typedef struct FACTVariation +{ + FAUDIONAMELESS union + { + struct + { + uint16_t track; + uint8_t wavebank; + } simple; + uint32_t soundCode; + }; + float minWeight; + float maxWeight; + uint32_t linger; +} FACTVariation; + +typedef struct FACTVariationTable +{ + uint8_t flags; + int16_t variable; + uint8_t isComplex; + + uint16_t entryCount; + FACTVariation *entries; +} FACTVariationTable; + +typedef struct FACTTransition +{ + int32_t soundCode; + uint32_t srcMarkerMin; + uint32_t srcMarkerMax; + uint32_t dstMarkerMin; + uint32_t dstMarkerMax; + uint16_t fadeIn; + uint16_t fadeOut; + uint16_t flags; +} FACTTransition; + +typedef struct FACTTransitionTable +{ + uint32_t entryCount; + FACTTransition *entries; +} FACTTransitionTable; + +/* Internal WaveBank Types */ + +typedef struct FACTSeekTable +{ + uint32_t entryCount; + uint32_t *entries; +} FACTSeekTable; + +/* Internal Cue Types */ + +typedef struct FACTInstanceRPCData +{ + float rpcVolume; + float rpcPitch; + float rpcReverbSend; + float rpcFilterFreq; + float rpcFilterQFactor; +} FACTInstanceRPCData; + +typedef struct FACTEventInstance +{ + uint32_t timestamp; + uint16_t loopCount; + uint8_t finished; + FAUDIONAMELESS union + { + float value; + uint32_t valuei; + }; +} FACTEventInstance; + +typedef struct FACTTrackInstance +{ + /* Tracks which events have fired */ + FACTEventInstance *events; + + /* RPC instance data */ + FACTInstanceRPCData rpcData; + + /* SetPitch/SetVolume data */ + float evtPitch; + float evtVolume; + + /* Wave playback */ + struct + { + FACTWave *wave; + float baseVolume; + int16_t basePitch; + float baseQFactor; + float baseFrequency; + } activeWave, upcomingWave; + FACTEvent *waveEvt; + FACTEventInstance *waveEvtInst; +} FACTTrackInstance; + +typedef struct FACTSoundInstance +{ + /* Base Sound reference */ + FACTSound *sound; + + /* Per-instance track information */ + FACTTrackInstance *tracks; + + /* RPC instance data */ + FACTInstanceRPCData rpcData; + + /* Fade data */ + uint32_t fadeStart; + uint16_t fadeTarget; + uint8_t fadeType; /* In (1), Out (2), Release RPC (3) */ + + /* Engine references */ + FACTCue *parentCue; +} FACTSoundInstance; + +/* Internal Wave Types */ + +typedef struct FACTWaveCallback +{ + FAudioVoiceCallback callback; + FACTWave *wave; +} FACTWaveCallback; + +/* Public XACT Types */ + +struct FACTAudioEngine +{ + uint32_t refcount; + FACTNotificationCallback notificationCallback; + FACTReadFileCallback pReadFile; + FACTGetOverlappedResultCallback pGetOverlappedResult; + + uint16_t categoryCount; + uint16_t variableCount; + uint16_t rpcCount; + uint16_t dspPresetCount; + uint16_t dspParameterCount; + + char **categoryNames; + char **variableNames; + uint32_t *rpcCodes; + uint32_t *dspPresetCodes; + + FACTAudioCategory *categories; + FACTVariable *variables; + FACTRPC *rpcs; + FACTDSPPreset *dspPresets; + + /* Engine references */ + LinkedList *sbList; + LinkedList *wbList; + FAudioMutex sbLock; + FAudioMutex wbLock; + float *globalVariableValues; + + /* FAudio references */ + FAudio *audio; + FAudioMasteringVoice *master; + FAudioSubmixVoice *reverbVoice; + + /* Engine thread */ + FAudioThread apiThread; + FAudioMutex apiLock; + uint8_t initialized; + + /* Allocator callbacks */ + FAudioMallocFunc pMalloc; + FAudioFreeFunc pFree; + FAudioReallocFunc pRealloc; + + /* Peristent Notifications */ + FACTNoticationsFlags notifications; + void *cue_context; + void *sb_context; + void *wb_context; + void *wave_context; + + /* Settings handle */ + void *settings; +}; + +struct FACTSoundBank +{ + /* Engine references */ + FACTAudioEngine *parentEngine; + FACTCue *cueList; + uint8_t notifyOnDestroy; + void *usercontext; + + /* Array sizes */ + uint16_t cueCount; + uint8_t wavebankCount; + uint16_t soundCount; + uint16_t variationCount; + uint16_t transitionCount; + + /* Strings, strings everywhere! */ + char **wavebankNames; + char **cueNames; + + /* Actual SoundBank information */ + char *name; + FACTCueData *cues; + FACTSound *sounds; + uint32_t *soundCodes; + FACTVariationTable *variations; + uint32_t *variationCodes; + FACTTransitionTable *transitions; + uint32_t *transitionCodes; +}; + +struct FACTWaveBank +{ + /* Engine references */ + FACTAudioEngine *parentEngine; + LinkedList *waveList; + FAudioMutex waveLock; + uint8_t notifyOnDestroy; + void *usercontext; + + /* Actual WaveBank information */ + char *name; + uint32_t entryCount; + FACTWaveBankEntry *entries; + uint32_t *entryRefs; + FACTSeekTable *seekTables; + char *waveBankNames; + + /* I/O information */ + uint32_t packetSize; + uint16_t streaming; + uint8_t *packetBuffer; + uint32_t packetBufferLen; + void* io; +}; + +struct FACTWave +{ + /* Engine references */ + FACTWaveBank *parentBank; + FACTCue *parentCue; + uint16_t index; + uint8_t notifyOnDestroy; + void *usercontext; + + /* Playback */ + uint32_t state; + float volume; + int16_t pitch; + uint8_t loopCount; + + /* Stream data */ + uint32_t streamSize; + uint32_t streamOffset; + uint8_t *streamCache; + + /* FAudio references */ + uint16_t srcChannels; + FAudioSourceVoice *voice; + FACTWaveCallback callback; +}; + +struct FACTCue +{ + /* Engine references */ + FACTSoundBank *parentBank; + FACTCue *next; + uint8_t managed; + uint16_t index; + uint8_t notifyOnDestroy; + void *usercontext; + + /* Sound data */ + FACTCueData *data; + FAUDIONAMELESS union + { + FACTVariationTable *variation; + + /* This is only used in scenarios where there is only one + * Sound; XACT does not generate variation tables for + * Cues with only one Sound. + */ + FACTSound *sound; + }; + + /* Instance data */ + float *variableValues; + float interactive; + + /* Playback */ + uint32_t state; + FACTWave *simpleWave; + FACTSoundInstance *playingSound; + FACTVariation *playingVariation; + uint32_t maxRpcReleaseTime; + + /* 3D Data */ + uint8_t active3D; + uint32_t srcChannels; + uint32_t dstChannels; + float matrixCoefficients[2 * 8]; /* Stereo input, 7.1 output */ + + /* Timer */ + uint32_t start; + uint32_t elapsed; +}; + +/* Internal functions */ + +void FACT_INTERNAL_GetNextWave( + FACTCue *cue, + FACTSound *sound, + FACTTrack *track, + FACTTrackInstance *trackInst, + FACTEvent *evt, + FACTEventInstance *evtInst +); +uint8_t FACT_INTERNAL_CreateSound(FACTCue *cue, uint16_t fadeInMS); +void FACT_INTERNAL_DestroySound(FACTSoundInstance *sound); +void FACT_INTERNAL_BeginFadeOut(FACTSoundInstance *sound, uint16_t fadeOutMS); +void FACT_INTERNAL_BeginReleaseRPC(FACTSoundInstance *sound, uint16_t releaseMS); + +void FACT_INTERNAL_SendCueNotification(FACTCue *cue, FACTNoticationsFlags flag, uint8_t type); + +/* RPC Helper Functions */ + +FACTRPC* FACT_INTERNAL_GetRPC(FACTAudioEngine *engine, uint32_t code); + +/* FACT Thread */ + +int32_t FAUDIOCALL FACT_INTERNAL_APIThread(void* enginePtr); + +/* FAudio callbacks */ + +void FACT_INTERNAL_OnBufferEnd(FAudioVoiceCallback *callback, void* pContext); +void FACT_INTERNAL_OnStreamEnd(FAudioVoiceCallback *callback); + +/* FAudioIOStream functions */ + +int32_t FACTCALL FACT_INTERNAL_DefaultReadFile( + void *hFile, + void *buffer, + uint32_t nNumberOfBytesToRead, + uint32_t *lpNumberOfBytesRead, + FACTOverlapped *lpOverlapped +); + +int32_t FACTCALL FACT_INTERNAL_DefaultGetOverlappedResult( + void *hFile, + FACTOverlapped *lpOverlapped, + uint32_t *lpNumberOfBytesTransferred, + int32_t bWait +); + +/* Parsing functions */ + +uint32_t FACT_INTERNAL_ParseAudioEngine( + FACTAudioEngine *pEngine, + const FACTRuntimeParameters *pParams +); +uint32_t FACT_INTERNAL_ParseSoundBank( + FACTAudioEngine *pEngine, + const void *pvBuffer, + uint32_t dwSize, + FACTSoundBank **ppSoundBank +); +uint32_t FACT_INTERNAL_ParseWaveBank( + FACTAudioEngine *pEngine, + void* io, + uint32_t offset, + uint32_t packetSize, + FACTReadFileCallback pRead, + FACTGetOverlappedResultCallback pOverlap, + uint16_t isStreaming, + FACTWaveBank **ppWaveBank +); + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/src/FAPOBase.c b/libs/faudio/src/FAPOBase.c new file mode 100644 index 00000000000..91b294ddab2 --- /dev/null +++ b/libs/faudio/src/FAPOBase.c @@ -0,0 +1,451 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * Ethan "flibitijibibo" Lee + * + */ + +#include "FAPOBase.h" +#include "FAudio_internal.h" + +/* FAPOBase Interface */ + +void CreateFAPOBase( + FAPOBase *fapo, + const FAPORegistrationProperties *pRegistrationProperties, + uint8_t *pParameterBlocks, + uint32_t uParameterBlockByteSize, + uint8_t fProducer +) { + CreateFAPOBaseWithCustomAllocatorEXT( + fapo, + pRegistrationProperties, + pParameterBlocks, + uParameterBlockByteSize, + fProducer, + FAudio_malloc, + FAudio_free, + FAudio_realloc + ); +} + +void CreateFAPOBaseWithCustomAllocatorEXT( + FAPOBase *fapo, + const FAPORegistrationProperties *pRegistrationProperties, + uint8_t *pParameterBlocks, + uint32_t uParameterBlockByteSize, + uint8_t fProducer, + FAudioMallocFunc customMalloc, + FAudioFreeFunc customFree, + FAudioReallocFunc customRealloc +) { + /* Base Classes/Interfaces */ + #define ASSIGN_VT(name) \ + fapo->base.name = (name##Func) FAPOBase_##name; + ASSIGN_VT(AddRef) + ASSIGN_VT(Release) + ASSIGN_VT(GetRegistrationProperties) + ASSIGN_VT(IsInputFormatSupported) + ASSIGN_VT(IsOutputFormatSupported) + ASSIGN_VT(Initialize) + ASSIGN_VT(Reset) + ASSIGN_VT(LockForProcess) + ASSIGN_VT(UnlockForProcess) + ASSIGN_VT(CalcInputFrames) + ASSIGN_VT(CalcOutputFrames) + ASSIGN_VT(SetParameters) + ASSIGN_VT(GetParameters) + #undef ASSIGN_VT + + /* Public Virtual Functions */ + fapo->OnSetParameters = (OnSetParametersFunc) + FAPOBase_OnSetParameters; + + /* Private Variables */ + fapo->m_pRegistrationProperties = pRegistrationProperties; /* FIXME */ + fapo->m_pfnMatrixMixFunction = NULL; /* FIXME */ + fapo->m_pfl32MatrixCoefficients = NULL; /* FIXME */ + fapo->m_nSrcFormatType = 0; /* FIXME */ + fapo->m_fIsScalarMatrix = 0; /* FIXME: */ + fapo->m_fIsLocked = 0; + fapo->m_pParameterBlocks = pParameterBlocks; + fapo->m_pCurrentParameters = pParameterBlocks; + fapo->m_pCurrentParametersInternal = pParameterBlocks; + fapo->m_uCurrentParametersIndex = 0; + fapo->m_uParameterBlockByteSize = uParameterBlockByteSize; + fapo->m_fNewerResultsReady = 0; + fapo->m_fProducer = fProducer; + + /* Allocator Callbacks */ + fapo->pMalloc = customMalloc; + fapo->pFree = customFree; + fapo->pRealloc = customRealloc; + + /* Protected Variables */ + fapo->m_lReferenceCount = 1; +} + +int32_t FAPOBase_AddRef(FAPOBase *fapo) +{ + fapo->m_lReferenceCount += 1; + return fapo->m_lReferenceCount; +} + +int32_t FAPOBase_Release(FAPOBase *fapo) +{ + fapo->m_lReferenceCount -= 1; + if (fapo->m_lReferenceCount == 0) + { + fapo->Destructor(fapo); + return 0; + } + return fapo->m_lReferenceCount; +} + +uint32_t FAPOBase_GetRegistrationProperties( + FAPOBase *fapo, + FAPORegistrationProperties **ppRegistrationProperties +) { + *ppRegistrationProperties = (FAPORegistrationProperties*) fapo->pMalloc( + sizeof(FAPORegistrationProperties) + ); + FAudio_memcpy( + *ppRegistrationProperties, + fapo->m_pRegistrationProperties, + sizeof(FAPORegistrationProperties) + ); + return 0; +} + +uint32_t FAPOBase_IsInputFormatSupported( + FAPOBase *fapo, + const FAudioWaveFormatEx *pOutputFormat, + const FAudioWaveFormatEx *pRequestedInputFormat, + FAudioWaveFormatEx **ppSupportedInputFormat +) { + if ( pRequestedInputFormat->wFormatTag != FAPOBASE_DEFAULT_FORMAT_TAG || + pRequestedInputFormat->nChannels < FAPOBASE_DEFAULT_FORMAT_MIN_CHANNELS || + pRequestedInputFormat->nChannels > FAPOBASE_DEFAULT_FORMAT_MAX_CHANNELS || + pRequestedInputFormat->nSamplesPerSec < FAPOBASE_DEFAULT_FORMAT_MIN_FRAMERATE || + pRequestedInputFormat->nSamplesPerSec > FAPOBASE_DEFAULT_FORMAT_MAX_FRAMERATE || + pRequestedInputFormat->wBitsPerSample != FAPOBASE_DEFAULT_FORMAT_BITSPERSAMPLE ) + { + if (ppSupportedInputFormat != NULL) + { + (*ppSupportedInputFormat)->wFormatTag = + FAPOBASE_DEFAULT_FORMAT_TAG; + (*ppSupportedInputFormat)->nChannels = FAudio_clamp( + pRequestedInputFormat->nChannels, + FAPOBASE_DEFAULT_FORMAT_MIN_CHANNELS, + FAPOBASE_DEFAULT_FORMAT_MAX_CHANNELS + ); + (*ppSupportedInputFormat)->nSamplesPerSec = FAudio_clamp( + pRequestedInputFormat->nSamplesPerSec, + FAPOBASE_DEFAULT_FORMAT_MIN_FRAMERATE, + FAPOBASE_DEFAULT_FORMAT_MAX_FRAMERATE + ); + (*ppSupportedInputFormat)->wBitsPerSample = + FAPOBASE_DEFAULT_FORMAT_BITSPERSAMPLE; + } + return FAPO_E_FORMAT_UNSUPPORTED; + } + return 0; +} + +uint32_t FAPOBase_IsOutputFormatSupported( + FAPOBase *fapo, + const FAudioWaveFormatEx *pInputFormat, + const FAudioWaveFormatEx *pRequestedOutputFormat, + FAudioWaveFormatEx **ppSupportedOutputFormat +) { + if ( pRequestedOutputFormat->wFormatTag != FAPOBASE_DEFAULT_FORMAT_TAG || + pRequestedOutputFormat->nChannels < FAPOBASE_DEFAULT_FORMAT_MIN_CHANNELS || + pRequestedOutputFormat->nChannels > FAPOBASE_DEFAULT_FORMAT_MAX_CHANNELS || + pRequestedOutputFormat->nSamplesPerSec < FAPOBASE_DEFAULT_FORMAT_MIN_FRAMERATE || + pRequestedOutputFormat->nSamplesPerSec > FAPOBASE_DEFAULT_FORMAT_MAX_FRAMERATE || + pRequestedOutputFormat->wBitsPerSample != FAPOBASE_DEFAULT_FORMAT_BITSPERSAMPLE ) + { + if (ppSupportedOutputFormat != NULL) + { + (*ppSupportedOutputFormat)->wFormatTag = + FAPOBASE_DEFAULT_FORMAT_TAG; + (*ppSupportedOutputFormat)->nChannels = FAudio_clamp( + pRequestedOutputFormat->nChannels, + FAPOBASE_DEFAULT_FORMAT_MIN_CHANNELS, + FAPOBASE_DEFAULT_FORMAT_MAX_CHANNELS + ); + (*ppSupportedOutputFormat)->nSamplesPerSec = FAudio_clamp( + pRequestedOutputFormat->nSamplesPerSec, + FAPOBASE_DEFAULT_FORMAT_MIN_FRAMERATE, + FAPOBASE_DEFAULT_FORMAT_MAX_FRAMERATE + ); + (*ppSupportedOutputFormat)->wBitsPerSample = + FAPOBASE_DEFAULT_FORMAT_BITSPERSAMPLE; + } + return FAPO_E_FORMAT_UNSUPPORTED; + } + return 0; +} + +uint32_t FAPOBase_Initialize( + FAPOBase *fapo, + const void* pData, + uint32_t DataByteSize +) { + return 0; +} + +void FAPOBase_Reset(FAPOBase *fapo) +{ +} + +uint32_t FAPOBase_LockForProcess( + FAPOBase *fapo, + uint32_t InputLockedParameterCount, + const FAPOLockForProcessBufferParameters *pInputLockedParameters, + uint32_t OutputLockedParameterCount, + const FAPOLockForProcessBufferParameters *pOutputLockedParameters +) { + /* Verify parameter counts... */ + if ( InputLockedParameterCount < fapo->m_pRegistrationProperties->MinInputBufferCount || + InputLockedParameterCount > fapo->m_pRegistrationProperties->MaxInputBufferCount || + OutputLockedParameterCount < fapo->m_pRegistrationProperties->MinOutputBufferCount || + OutputLockedParameterCount > fapo->m_pRegistrationProperties->MaxOutputBufferCount ) + { + return FAUDIO_E_INVALID_ARG; + } + + + /* Validate input/output formats */ + #define VERIFY_FORMAT_FLAG(flag, prop) \ + if ( (fapo->m_pRegistrationProperties->Flags & flag) && \ + (pInputLockedParameters->pFormat->prop != pOutputLockedParameters->pFormat->prop) ) \ + { \ + return FAUDIO_E_INVALID_ARG; \ + } + VERIFY_FORMAT_FLAG(FAPO_FLAG_CHANNELS_MUST_MATCH, nChannels) + VERIFY_FORMAT_FLAG(FAPO_FLAG_FRAMERATE_MUST_MATCH, nSamplesPerSec) + VERIFY_FORMAT_FLAG(FAPO_FLAG_BITSPERSAMPLE_MUST_MATCH, wBitsPerSample) + #undef VERIFY_FORMAT_FLAG + if ( (fapo->m_pRegistrationProperties->Flags & FAPO_FLAG_BUFFERCOUNT_MUST_MATCH) && + (InputLockedParameterCount != OutputLockedParameterCount) ) + { + return FAUDIO_E_INVALID_ARG; + } + fapo->m_fIsLocked = 1; + return 0; +} + +void FAPOBase_UnlockForProcess(FAPOBase *fapo) +{ + fapo->m_fIsLocked = 0; +} + +uint32_t FAPOBase_CalcInputFrames(FAPOBase *fapo, uint32_t OutputFrameCount) +{ + return OutputFrameCount; +} + +uint32_t FAPOBase_CalcOutputFrames(FAPOBase *fapo, uint32_t InputFrameCount) +{ + return InputFrameCount; +} + +uint32_t FAPOBase_ValidateFormatDefault( + FAPOBase *fapo, + FAudioWaveFormatEx *pFormat, + uint8_t fOverwrite +) { + if ( pFormat->wFormatTag != FAPOBASE_DEFAULT_FORMAT_TAG || + pFormat->nChannels < FAPOBASE_DEFAULT_FORMAT_MIN_CHANNELS || + pFormat->nChannels > FAPOBASE_DEFAULT_FORMAT_MAX_CHANNELS || + pFormat->nSamplesPerSec < FAPOBASE_DEFAULT_FORMAT_MIN_FRAMERATE || + pFormat->nSamplesPerSec > FAPOBASE_DEFAULT_FORMAT_MAX_FRAMERATE || + pFormat->wBitsPerSample != FAPOBASE_DEFAULT_FORMAT_BITSPERSAMPLE ) + { + if (fOverwrite) + { + pFormat->wFormatTag = + FAPOBASE_DEFAULT_FORMAT_TAG; + pFormat->nChannels = FAudio_clamp( + pFormat->nChannels, + FAPOBASE_DEFAULT_FORMAT_MIN_CHANNELS, + FAPOBASE_DEFAULT_FORMAT_MAX_CHANNELS + ); + pFormat->nSamplesPerSec = FAudio_clamp( + pFormat->nSamplesPerSec, + FAPOBASE_DEFAULT_FORMAT_MIN_FRAMERATE, + FAPOBASE_DEFAULT_FORMAT_MAX_FRAMERATE + ); + pFormat->wBitsPerSample = + FAPOBASE_DEFAULT_FORMAT_BITSPERSAMPLE; + } + return FAPO_E_FORMAT_UNSUPPORTED; + } + return 0; +} + +uint32_t FAPOBase_ValidateFormatPair( + FAPOBase *fapo, + const FAudioWaveFormatEx *pSupportedFormat, + FAudioWaveFormatEx *pRequestedFormat, + uint8_t fOverwrite +) { + if ( pRequestedFormat->wFormatTag != FAPOBASE_DEFAULT_FORMAT_TAG || + pRequestedFormat->nChannels < FAPOBASE_DEFAULT_FORMAT_MIN_CHANNELS || + pRequestedFormat->nChannels > FAPOBASE_DEFAULT_FORMAT_MAX_CHANNELS || + pRequestedFormat->nSamplesPerSec < FAPOBASE_DEFAULT_FORMAT_MIN_FRAMERATE || + pRequestedFormat->nSamplesPerSec > FAPOBASE_DEFAULT_FORMAT_MAX_FRAMERATE || + pRequestedFormat->wBitsPerSample != FAPOBASE_DEFAULT_FORMAT_BITSPERSAMPLE ) + { + if (fOverwrite) + { + pRequestedFormat->wFormatTag = + FAPOBASE_DEFAULT_FORMAT_TAG; + pRequestedFormat->nChannels = FAudio_clamp( + pRequestedFormat->nChannels, + FAPOBASE_DEFAULT_FORMAT_MIN_CHANNELS, + FAPOBASE_DEFAULT_FORMAT_MAX_CHANNELS + ); + pRequestedFormat->nSamplesPerSec = FAudio_clamp( + pRequestedFormat->nSamplesPerSec, + FAPOBASE_DEFAULT_FORMAT_MIN_FRAMERATE, + FAPOBASE_DEFAULT_FORMAT_MAX_FRAMERATE + ); + pRequestedFormat->wBitsPerSample = + FAPOBASE_DEFAULT_FORMAT_BITSPERSAMPLE; + } + return FAPO_E_FORMAT_UNSUPPORTED; + } + return 0; +} + +void FAPOBase_ProcessThru( + FAPOBase *fapo, + void* pInputBuffer, + float *pOutputBuffer, + uint32_t FrameCount, + uint16_t InputChannelCount, + uint16_t OutputChannelCount, + uint8_t MixWithOutput +) { + uint32_t i, co, ci; + float *input = (float*) pInputBuffer; + + if (MixWithOutput) + { + /* TODO: SSE */ + for (i = 0; i < FrameCount; i += 1) + for (co = 0; co < OutputChannelCount; co += 1) + for (ci = 0; ci < InputChannelCount; ci += 1) + { + /* Add, don't overwrite! */ + pOutputBuffer[i * OutputChannelCount + co] += + input[i * InputChannelCount + ci]; + } + } + else + { + /* TODO: SSE */ + for (i = 0; i < FrameCount; i += 1) + for (co = 0; co < OutputChannelCount; co += 1) + for (ci = 0; ci < InputChannelCount; ci += 1) + { + /* Overwrite, don't add! */ + pOutputBuffer[i * OutputChannelCount + co] = + input[i * InputChannelCount + ci]; + } + } +} + +void FAPOBase_SetParameters( + FAPOBase *fapo, + const void* pParameters, + uint32_t ParameterByteSize +) { + FAudio_assert(!fapo->m_fProducer); + + /* User callback for validation */ + fapo->OnSetParameters( + fapo, + pParameters, + ParameterByteSize + ); + + /* Increment parameter block index... */ + fapo->m_uCurrentParametersIndex += 1; + if (fapo->m_uCurrentParametersIndex == 3) + { + fapo->m_uCurrentParametersIndex = 0; + } + fapo->m_pCurrentParametersInternal = fapo->m_pParameterBlocks + ( + fapo->m_uParameterBlockByteSize * + fapo->m_uCurrentParametersIndex + ); + + /* Copy to what will eventually be the next parameter update */ + FAudio_memcpy( + fapo->m_pCurrentParametersInternal, + pParameters, + ParameterByteSize + ); +} + +void FAPOBase_GetParameters( + FAPOBase *fapo, + void* pParameters, + uint32_t ParameterByteSize +) { + /* Copy what's current as of the last Process */ + FAudio_memcpy( + pParameters, + fapo->m_pCurrentParameters, + ParameterByteSize + ); +} + +void FAPOBase_OnSetParameters( + FAPOBase *fapo, + const void* parameters, + uint32_t parametersSize +) { +} + +uint8_t FAPOBase_ParametersChanged(FAPOBase *fapo) +{ + /* Internal will get updated when SetParameters is called */ + return fapo->m_pCurrentParametersInternal != fapo->m_pCurrentParameters; +} + +uint8_t* FAPOBase_BeginProcess(FAPOBase *fapo) +{ + /* Set the latest block as "current", this is what Process will use now */ + fapo->m_pCurrentParameters = fapo->m_pCurrentParametersInternal; + return fapo->m_pCurrentParameters; +} + +void FAPOBase_EndProcess(FAPOBase *fapo) +{ + /* I'm 100% sure my parameter block increment is wrong... */ +} + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/src/FAPOFX.c b/libs/faudio/src/FAPOFX.c new file mode 100644 index 00000000000..70739bd0614 --- /dev/null +++ b/libs/faudio/src/FAPOFX.c @@ -0,0 +1,89 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * Ethan "flibitijibibo" Lee + * + */ + +#include "FAPOFX.h" +#include "FAudio_internal.h" + +uint32_t FAPOFX_CreateFX( + const FAudioGUID *clsid, + FAPO **pEffect, + const void *pInitData, + uint32_t InitDataByteSize +) { + return FAPOFX_CreateFXWithCustomAllocatorEXT( + clsid, + pEffect, + pInitData, + InitDataByteSize, + FAudio_malloc, + FAudio_free, + FAudio_realloc + ); +} + +uint32_t FAPOFX_CreateFXWithCustomAllocatorEXT( + const FAudioGUID *clsid, + FAPO **pEffect, + const void *pInitData, + uint32_t InitDataByteSize, + FAudioMallocFunc customMalloc, + FAudioFreeFunc customFree, + FAudioReallocFunc customRealloc +) { + #define CHECK_AND_RETURN(effect) \ + if (FAudio_memcmp(clsid, &FAPOFX_CLSID_FX##effect, sizeof(FAudioGUID)) == 0) \ + { \ + return FAPOFXCreate##effect( \ + pEffect, \ + pInitData, \ + InitDataByteSize, \ + customMalloc, \ + customFree, \ + customRealloc, \ + 0 \ + ); \ + } \ + else if (FAudio_memcmp(clsid, &FAPOFX_CLSID_FX##effect##_LEGACY, sizeof(FAudioGUID)) == 0) \ + { \ + return FAPOFXCreate##effect( \ + pEffect, \ + pInitData, \ + InitDataByteSize, \ + customMalloc, \ + customFree, \ + customRealloc, \ + 1 \ + ); \ + } + CHECK_AND_RETURN(EQ) + CHECK_AND_RETURN(MasteringLimiter) + CHECK_AND_RETURN(Reverb) + CHECK_AND_RETURN(Echo) + #undef CHECK_AND_RETURN + return -1; +} + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/src/FAPOFX_echo.c b/libs/faudio/src/FAPOFX_echo.c new file mode 100644 index 00000000000..d87d5c5b95b --- /dev/null +++ b/libs/faudio/src/FAPOFX_echo.c @@ -0,0 +1,248 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * Ethan "flibitijibibo" Lee + * + */ + +#include "FAPOFX.h" +#include "FAudio_internal.h" + +/* FXEcho FAPO Implementation */ + +const FAudioGUID FAPOFX_CLSID_FXEcho = +{ + 0x5039D740, + 0xF736, + 0x449A, + { + 0x84, + 0xD3, + 0xA5, + 0x62, + 0x02, + 0x55, + 0x7B, + 0x87 + } +}; + +static FAPORegistrationProperties FXEchoProperties = +{ + /* .clsid = */ {0}, + /* .FriendlyName = */ + { + 'F', 'X', 'E', 'c', 'h', 'o', '\0' + }, + /*.CopyrightInfo = */ + { + 'C', 'o', 'p', 'y', 'r', 'i', 'g', 'h', 't', ' ', '(', 'c', ')', + 'E', 't', 'h', 'a', 'n', ' ', 'L', 'e', 'e', '\0' + }, + /*.MajorVersion = */ 0, + /*.MinorVersion = */ 0, + /*.Flags = */( + FAPO_FLAG_FRAMERATE_MUST_MATCH | + FAPO_FLAG_BITSPERSAMPLE_MUST_MATCH | + FAPO_FLAG_BUFFERCOUNT_MUST_MATCH | + FAPO_FLAG_INPLACE_SUPPORTED | + FAPO_FLAG_INPLACE_REQUIRED + ), + /*.MinInputBufferCount = */ 1, + /*.MaxInputBufferCount = */ 1, + /*.MinOutputBufferCount = */ 1, + /*.MaxOutputBufferCount =*/ 1 +}; + +const FAudioGUID FAPOFX_CLSID_FXEcho_LEGACY = +{ + 0xA90BC001, + 0xE897, + 0xE897, + { + 0x74, + 0x39, + 0x43, + 0x55, + 0x00, + 0x00, + 0x00, + 0x03 + } +}; + +static FAPORegistrationProperties FXEchoProperties_LEGACY = +{ + /* .clsid = */ {0}, + /* .FriendlyName = */ + { + 'F', 'X', 'E', 'c', 'h', 'o', '\0' + }, + /*.CopyrightInfo = */ + { + 'C', 'o', 'p', 'y', 'r', 'i', 'g', 'h', 't', ' ', '(', 'c', ')', + 'E', 't', 'h', 'a', 'n', ' ', 'L', 'e', 'e', '\0' + }, + /*.MajorVersion = */ 0, + /*.MinorVersion = */ 0, + /*.Flags = */( + FAPO_FLAG_FRAMERATE_MUST_MATCH | + FAPO_FLAG_BITSPERSAMPLE_MUST_MATCH | + FAPO_FLAG_BUFFERCOUNT_MUST_MATCH | + FAPO_FLAG_INPLACE_SUPPORTED | + FAPO_FLAG_INPLACE_REQUIRED + ), + /*.MinInputBufferCount = */ 1, + /*.MaxInputBufferCount = */ 1, + /*.MinOutputBufferCount = */ 1, + /*.MaxOutputBufferCount =*/ 1 +}; + +typedef struct FAPOFXEcho +{ + FAPOBase base; + + /* TODO */ +} FAPOFXEcho; + +uint32_t FAPOFXEcho_Initialize( + FAPOFXEcho *fapo, + const void* pData, + uint32_t DataByteSize +) { + #define INITPARAMS(offset) \ + FAudio_memcpy( \ + fapo->base.m_pParameterBlocks + DataByteSize * offset, \ + pData, \ + DataByteSize \ + ); + INITPARAMS(0) + INITPARAMS(1) + INITPARAMS(2) + #undef INITPARAMS + return 0; +} + +void FAPOFXEcho_Process( + FAPOFXEcho *fapo, + uint32_t InputProcessParameterCount, + const FAPOProcessBufferParameters* pInputProcessParameters, + uint32_t OutputProcessParameterCount, + FAPOProcessBufferParameters* pOutputProcessParameters, + int32_t IsEnabled +) { + FAPOBase_BeginProcess(&fapo->base); + + /* TODO */ + + FAPOBase_EndProcess(&fapo->base); +} + +void FAPOFXEcho_Free(void* fapo) +{ + FAPOFXEcho *echo = (FAPOFXEcho*) fapo; + echo->base.pFree(echo->base.m_pParameterBlocks); + echo->base.pFree(fapo); +} + +/* Public API */ + +uint32_t FAPOFXCreateEcho( + FAPO **pEffect, + const void *pInitData, + uint32_t InitDataByteSize, + FAudioMallocFunc customMalloc, + FAudioFreeFunc customFree, + FAudioReallocFunc customRealloc, + uint8_t legacy +) { + const FAPOFXEchoParameters fxdefault = + { + FAPOFXECHO_DEFAULT_WETDRYMIX, + FAPOFXECHO_DEFAULT_FEEDBACK, + FAPOFXECHO_DEFAULT_DELAY + }; + + /* Allocate... */ + FAPOFXEcho *result = (FAPOFXEcho*) customMalloc( + sizeof(FAPOFXEcho) + ); + uint8_t *params = (uint8_t*) customMalloc( + sizeof(FAPOFXEchoParameters) * 3 + ); + if (pInitData == NULL) + { + FAudio_zero(params, sizeof(FAPOFXEchoParameters) * 3); + #define INITPARAMS(offset) \ + FAudio_memcpy( \ + params + sizeof(FAPOFXEchoParameters) * offset, \ + &fxdefault, \ + sizeof(FAPOFXEchoParameters) \ + ); + INITPARAMS(0) + INITPARAMS(1) + INITPARAMS(2) + #undef INITPARAMS + } + else + { + FAudio_assert(InitDataByteSize == sizeof(FAPOFXEchoParameters)); + FAudio_memcpy(params, pInitData, InitDataByteSize); + FAudio_memcpy(params + InitDataByteSize, pInitData, InitDataByteSize); + FAudio_memcpy(params + (InitDataByteSize * 2), pInitData, InitDataByteSize); + } + + /* Initialize... */ + FAudio_memcpy( + &FXEchoProperties_LEGACY.clsid, + &FAPOFX_CLSID_FXEcho_LEGACY, + sizeof(FAudioGUID) + ); + FAudio_memcpy( + &FXEchoProperties.clsid, + &FAPOFX_CLSID_FXEcho, + sizeof(FAudioGUID) + ); + CreateFAPOBaseWithCustomAllocatorEXT( + &result->base, + legacy ? &FXEchoProperties_LEGACY : &FXEchoProperties, + params, + sizeof(FAPOFXEchoParameters), + 0, + customMalloc, + customFree, + customRealloc + ); + + /* Function table... */ + result->base.base.Initialize = (InitializeFunc) + FAPOFXEcho_Initialize; + result->base.base.Process = (ProcessFunc) + FAPOFXEcho_Process; + result->base.Destructor = FAPOFXEcho_Free; + + /* Finally. */ + *pEffect = &result->base.base; + return 0; +} + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/src/FAPOFX_eq.c b/libs/faudio/src/FAPOFX_eq.c new file mode 100644 index 00000000000..4ac45728aa4 --- /dev/null +++ b/libs/faudio/src/FAPOFX_eq.c @@ -0,0 +1,257 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * Ethan "flibitijibibo" Lee + * + */ + +#include "FAPOFX.h" +#include "FAudio_internal.h" + +/* FXEQ FAPO Implementation */ + +const FAudioGUID FAPOFX_CLSID_FXEQ = +{ + 0xF5E01117, + 0xD6C4, + 0x485A, + { + 0xA3, + 0xF5, + 0x69, + 0x51, + 0x96, + 0xF3, + 0xDB, + 0xFA + } +}; + +static FAPORegistrationProperties FXEQProperties = +{ + /* .clsid = */ {0}, + /* .FriendlyName = */ + { + 'F', 'X', 'E', 'Q', '\0' + }, + /*.CopyrightInfo = */ + { + 'C', 'o', 'p', 'y', 'r', 'i', 'g', 'h', 't', ' ', '(', 'c', ')', + 'E', 't', 'h', 'a', 'n', ' ', 'L', 'e', 'e', '\0' + }, + /*.MajorVersion = */ 0, + /*.MinorVersion = */ 0, + /*.Flags = */( + FAPO_FLAG_FRAMERATE_MUST_MATCH | + FAPO_FLAG_BITSPERSAMPLE_MUST_MATCH | + FAPO_FLAG_BUFFERCOUNT_MUST_MATCH | + FAPO_FLAG_INPLACE_SUPPORTED | + FAPO_FLAG_INPLACE_REQUIRED + ), + /*.MinInputBufferCount = */ 1, + /*.MaxInputBufferCount = */ 1, + /*.MinOutputBufferCount = */ 1, + /*.MaxOutputBufferCount =*/ 1 +}; + +const FAudioGUID FAPOFX_CLSID_FXEQ_LEGACY = +{ + 0xA90BC001, + 0xE897, + 0xE897, + { + 0x74, + 0x39, + 0x43, + 0x55, + 0x00, + 0x00, + 0x00, + 0x00 + } +}; + +static FAPORegistrationProperties FXEQProperties_LEGACY = +{ + /* .clsid = */ {0}, + /* .FriendlyName = */ + { + 'F', 'X', 'E', 'Q', '\0' + }, + /*.CopyrightInfo = */ + { + 'C', 'o', 'p', 'y', 'r', 'i', 'g', 'h', 't', ' ', '(', 'c', ')', + 'E', 't', 'h', 'a', 'n', ' ', 'L', 'e', 'e', '\0' + }, + /*.MajorVersion = */ 0, + /*.MinorVersion = */ 0, + /*.Flags = */( + FAPO_FLAG_FRAMERATE_MUST_MATCH | + FAPO_FLAG_BITSPERSAMPLE_MUST_MATCH | + FAPO_FLAG_BUFFERCOUNT_MUST_MATCH | + FAPO_FLAG_INPLACE_SUPPORTED | + FAPO_FLAG_INPLACE_REQUIRED + ), + /*.MinInputBufferCount = */ 1, + /*.MaxInputBufferCount = */ 1, + /*.MinOutputBufferCount = */ 1, + /*.MaxOutputBufferCount =*/ 1 +}; + +typedef struct FAPOFXEQ +{ + FAPOBase base; + + /* TODO */ +} FAPOFXEQ; + +uint32_t FAPOFXEQ_Initialize( + FAPOFXEQ *fapo, + const void* pData, + uint32_t DataByteSize +) { + #define INITPARAMS(offset) \ + FAudio_memcpy( \ + fapo->base.m_pParameterBlocks + DataByteSize * offset, \ + pData, \ + DataByteSize \ + ); + INITPARAMS(0) + INITPARAMS(1) + INITPARAMS(2) + #undef INITPARAMS + return 0; +} + +void FAPOFXEQ_Process( + FAPOFXEQ *fapo, + uint32_t InputProcessParameterCount, + const FAPOProcessBufferParameters* pInputProcessParameters, + uint32_t OutputProcessParameterCount, + FAPOProcessBufferParameters* pOutputProcessParameters, + int32_t IsEnabled +) { + FAPOBase_BeginProcess(&fapo->base); + + /* TODO */ + + FAPOBase_EndProcess(&fapo->base); +} + +void FAPOFXEQ_Free(void* fapo) +{ + FAPOFXEQ *eq = (FAPOFXEQ*) fapo; + eq->base.pFree(eq->base.m_pParameterBlocks); + eq->base.pFree(fapo); +} + +/* Public API */ + +uint32_t FAPOFXCreateEQ( + FAPO **pEffect, + const void *pInitData, + uint32_t InitDataByteSize, + FAudioMallocFunc customMalloc, + FAudioFreeFunc customFree, + FAudioReallocFunc customRealloc, + uint8_t legacy +) { + const FAPOFXEQParameters fxdefault = + { + FAPOFXEQ_DEFAULT_FREQUENCY_CENTER_0, + FAPOFXEQ_DEFAULT_GAIN, + FAPOFXEQ_DEFAULT_BANDWIDTH, + FAPOFXEQ_DEFAULT_FREQUENCY_CENTER_1, + FAPOFXEQ_DEFAULT_GAIN, + FAPOFXEQ_DEFAULT_BANDWIDTH, + FAPOFXEQ_DEFAULT_FREQUENCY_CENTER_2, + FAPOFXEQ_DEFAULT_GAIN, + FAPOFXEQ_DEFAULT_BANDWIDTH, + FAPOFXEQ_DEFAULT_FREQUENCY_CENTER_3, + FAPOFXEQ_DEFAULT_GAIN, + FAPOFXEQ_DEFAULT_BANDWIDTH + }; + + /* Allocate... */ + FAPOFXEQ *result = (FAPOFXEQ*) customMalloc( + sizeof(FAPOFXEQ) + ); + uint8_t *params = (uint8_t*) customMalloc( + sizeof(FAPOFXEQParameters) * 3 + ); + if (pInitData == NULL) + { + FAudio_zero(params, sizeof(FAPOFXEQParameters) * 3); + #define INITPARAMS(offset) \ + FAudio_memcpy( \ + params + sizeof(FAPOFXEQParameters) * offset, \ + &fxdefault, \ + sizeof(FAPOFXEQParameters) \ + ); + INITPARAMS(0) + INITPARAMS(1) + INITPARAMS(2) + #undef INITPARAMS + } + else + { + FAudio_assert(InitDataByteSize == sizeof(FAPOFXEQParameters)); + FAudio_memcpy(params, pInitData, InitDataByteSize); + FAudio_memcpy(params + InitDataByteSize, pInitData, InitDataByteSize); + FAudio_memcpy(params + (InitDataByteSize * 2), pInitData, InitDataByteSize); + } + + /* Initialize... */ + FAudio_memcpy( + &FXEQProperties_LEGACY.clsid, + &FAPOFX_CLSID_FXEQ_LEGACY, + sizeof(FAudioGUID) + ); + FAudio_memcpy( + &FXEQProperties.clsid, + &FAPOFX_CLSID_FXEQ, + sizeof(FAudioGUID) + ); + CreateFAPOBaseWithCustomAllocatorEXT( + &result->base, + legacy ? &FXEQProperties_LEGACY : &FXEQProperties, + params, + sizeof(FAPOFXEQParameters), + 0, + customMalloc, + customFree, + customRealloc + ); + + /* Function table... */ + result->base.base.Initialize = (InitializeFunc) + FAPOFXEQ_Initialize; + result->base.base.Process = (ProcessFunc) + FAPOFXEQ_Process; + result->base.Destructor = FAPOFXEQ_Free; + + /* Finally. */ + *pEffect = &result->base.base; + return 0; +} + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/src/FAPOFX_masteringlimiter.c b/libs/faudio/src/FAPOFX_masteringlimiter.c new file mode 100644 index 00000000000..1b02ffe90ae --- /dev/null +++ b/libs/faudio/src/FAPOFX_masteringlimiter.c @@ -0,0 +1,247 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * Ethan "flibitijibibo" Lee + * + */ + +#include "FAPOFX.h" +#include "FAudio_internal.h" + +/* FXMasteringLimiter FAPO Implementation */ + +const FAudioGUID FAPOFX_CLSID_FXMasteringLimiter = +{ + 0xC4137916, + 0x2BE1, + 0x46FD, + { + 0x85, + 0x99, + 0x44, + 0x15, + 0x36, + 0xF4, + 0x98, + 0x56 + } +}; + +static FAPORegistrationProperties FXMasteringLimiterProperties = +{ + /* .clsid = */ {0}, + /* .FriendlyName = */ + { + 'F', 'X', 'M', 'a', 's', 't', 'e', 'r', 'i', 'n', 'g', 'L', 'i', 'm', 'i', 't', 'e', 'r', '\0' + }, + /*.CopyrightInfo = */ + { + 'C', 'o', 'p', 'y', 'r', 'i', 'g', 'h', 't', ' ', '(', 'c', ')', + 'E', 't', 'h', 'a', 'n', ' ', 'L', 'e', 'e', '\0' + }, + /*.MajorVersion = */ 0, + /*.MinorVersion = */ 0, + /*.Flags = */( + FAPO_FLAG_FRAMERATE_MUST_MATCH | + FAPO_FLAG_BITSPERSAMPLE_MUST_MATCH | + FAPO_FLAG_BUFFERCOUNT_MUST_MATCH | + FAPO_FLAG_INPLACE_SUPPORTED | + FAPO_FLAG_INPLACE_REQUIRED + ), + /*.MinInputBufferCount = */ 1, + /*.MaxInputBufferCount = */ 1, + /*.MinOutputBufferCount = */ 1, + /*.MaxOutputBufferCount =*/ 1 +}; + +const FAudioGUID FAPOFX_CLSID_FXMasteringLimiter_LEGACY = +{ + 0xA90BC001, + 0xE897, + 0xE897, + { + 0x74, + 0x39, + 0x43, + 0x55, + 0x00, + 0x00, + 0x00, + 0x01 + } +}; + +static FAPORegistrationProperties FXMasteringLimiterProperties_LEGACY = +{ + /* .clsid = */ {0}, + /* .FriendlyName = */ + { + 'F', 'X', 'M', 'a', 's', 't', 'e', 'r', 'i', 'n', 'g', 'L', 'i', 'm', 'i', 't', 'e', 'r', '\0' + }, + /*.CopyrightInfo = */ + { + 'C', 'o', 'p', 'y', 'r', 'i', 'g', 'h', 't', ' ', '(', 'c', ')', + 'E', 't', 'h', 'a', 'n', ' ', 'L', 'e', 'e', '\0' + }, + /*.MajorVersion = */ 0, + /*.MinorVersion = */ 0, + /*.Flags = */( + FAPO_FLAG_FRAMERATE_MUST_MATCH | + FAPO_FLAG_BITSPERSAMPLE_MUST_MATCH | + FAPO_FLAG_BUFFERCOUNT_MUST_MATCH | + FAPO_FLAG_INPLACE_SUPPORTED | + FAPO_FLAG_INPLACE_REQUIRED + ), + /*.MinInputBufferCount = */ 1, + /*.MaxInputBufferCount = */ 1, + /*.MinOutputBufferCount = */ 1, + /*.MaxOutputBufferCount =*/ 1 +}; + +typedef struct FAPOFXMasteringLimiter +{ + FAPOBase base; + + /* TODO */ +} FAPOFXMasteringLimiter; + +uint32_t FAPOFXMasteringLimiter_Initialize( + FAPOFXMasteringLimiter *fapo, + const void* pData, + uint32_t DataByteSize +) { + #define INITPARAMS(offset) \ + FAudio_memcpy( \ + fapo->base.m_pParameterBlocks + DataByteSize * offset, \ + pData, \ + DataByteSize \ + ); + INITPARAMS(0) + INITPARAMS(1) + INITPARAMS(2) + #undef INITPARAMS + return 0; +} + +void FAPOFXMasteringLimiter_Process( + FAPOFXMasteringLimiter *fapo, + uint32_t InputProcessParameterCount, + const FAPOProcessBufferParameters* pInputProcessParameters, + uint32_t OutputProcessParameterCount, + FAPOProcessBufferParameters* pOutputProcessParameters, + int32_t IsEnabled +) { + FAPOBase_BeginProcess(&fapo->base); + + /* TODO */ + + FAPOBase_EndProcess(&fapo->base); +} + +void FAPOFXMasteringLimiter_Free(void* fapo) +{ + FAPOFXMasteringLimiter *limiter = (FAPOFXMasteringLimiter*) fapo; + limiter->base.pFree(limiter->base.m_pParameterBlocks); + limiter->base.pFree(fapo); +} + +/* Public API */ + +uint32_t FAPOFXCreateMasteringLimiter( + FAPO **pEffect, + const void *pInitData, + uint32_t InitDataByteSize, + FAudioMallocFunc customMalloc, + FAudioFreeFunc customFree, + FAudioReallocFunc customRealloc, + uint8_t legacy +) { + const FAPOFXMasteringLimiterParameters fxdefault = + { + FAPOFXMASTERINGLIMITER_DEFAULT_RELEASE, + FAPOFXMASTERINGLIMITER_DEFAULT_LOUDNESS + }; + + /* Allocate... */ + FAPOFXMasteringLimiter *result = (FAPOFXMasteringLimiter*) customMalloc( + sizeof(FAPOFXMasteringLimiter) + ); + uint8_t *params = (uint8_t*) customMalloc( + sizeof(FAPOFXMasteringLimiterParameters) * 3 + ); + if (pInitData == NULL) + { + FAudio_zero(params, sizeof(FAPOFXMasteringLimiterParameters) * 3); + #define INITPARAMS(offset) \ + FAudio_memcpy( \ + params + sizeof(FAPOFXMasteringLimiterParameters) * offset, \ + &fxdefault, \ + sizeof(FAPOFXMasteringLimiterParameters) \ + ); + INITPARAMS(0) + INITPARAMS(1) + INITPARAMS(2) + #undef INITPARAMS + } + else + { + FAudio_assert(InitDataByteSize == sizeof(FAPOFXMasteringLimiterParameters)); + FAudio_memcpy(params, pInitData, InitDataByteSize); + FAudio_memcpy(params + InitDataByteSize, pInitData, InitDataByteSize); + FAudio_memcpy(params + (InitDataByteSize * 2), pInitData, InitDataByteSize); + } + + /* Initialize... */ + FAudio_memcpy( + &FXMasteringLimiterProperties_LEGACY.clsid, + &FAPOFX_CLSID_FXMasteringLimiter_LEGACY, + sizeof(FAudioGUID) + ); + FAudio_memcpy( + &FXMasteringLimiterProperties.clsid, + &FAPOFX_CLSID_FXMasteringLimiter, + sizeof(FAudioGUID) + ); + CreateFAPOBaseWithCustomAllocatorEXT( + &result->base, + legacy ? &FXMasteringLimiterProperties_LEGACY : &FXMasteringLimiterProperties, + params, + sizeof(FAPOFXMasteringLimiterParameters), + 0, + customMalloc, + customFree, + customRealloc + ); + + /* Function table... */ + result->base.base.Initialize = (InitializeFunc) + FAPOFXMasteringLimiter_Initialize; + result->base.base.Process = (ProcessFunc) + FAPOFXMasteringLimiter_Process; + result->base.Destructor = FAPOFXMasteringLimiter_Free; + + /* Finally. */ + *pEffect = &result->base.base; + return 0; +} + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/src/FAPOFX_reverb.c b/libs/faudio/src/FAPOFX_reverb.c new file mode 100644 index 00000000000..782dddafa25 --- /dev/null +++ b/libs/faudio/src/FAPOFX_reverb.c @@ -0,0 +1,247 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * Ethan "flibitijibibo" Lee + * + */ + +#include "FAPOFX.h" +#include "FAudio_internal.h" + +/* FXReverb FAPO Implementation */ + +const FAudioGUID FAPOFX_CLSID_FXReverb = +{ + 0x7D9ACA56, + 0xCB68, + 0x4807, + { + 0xB6, + 0x32, + 0xB1, + 0x37, + 0x35, + 0x2E, + 0x85, + 0x96 + } +}; + +static FAPORegistrationProperties FXReverbProperties = +{ + /* .clsid = */ {0}, + /* .FriendlyName = */ + { + 'F', 'X', 'R', 'e', 'v', 'e', 'r', 'b', '\0' + }, + /*.CopyrightInfo = */ + { + 'C', 'o', 'p', 'y', 'r', 'i', 'g', 'h', 't', ' ', '(', 'c', ')', + 'E', 't', 'h', 'a', 'n', ' ', 'L', 'e', 'e', '\0' + }, + /*.MajorVersion = */ 0, + /*.MinorVersion = */ 0, + /*.Flags = */( + FAPO_FLAG_FRAMERATE_MUST_MATCH | + FAPO_FLAG_BITSPERSAMPLE_MUST_MATCH | + FAPO_FLAG_BUFFERCOUNT_MUST_MATCH | + FAPO_FLAG_INPLACE_SUPPORTED | + FAPO_FLAG_INPLACE_REQUIRED + ), + /*.MinInputBufferCount = */ 1, + /*.MaxInputBufferCount = */ 1, + /*.MinOutputBufferCount = */ 1, + /*.MaxOutputBufferCount =*/ 1 +}; + +const FAudioGUID FAPOFX_CLSID_FXReverb_LEGACY = +{ + 0xA90BC001, + 0xE897, + 0xE897, + { + 0x74, + 0x39, + 0x43, + 0x55, + 0x00, + 0x00, + 0x00, + 0x02 + } +}; + +static FAPORegistrationProperties FXReverbProperties_LEGACY = +{ + /* .clsid = */ {0}, + /* .FriendlyName = */ + { + 'F', 'X', 'R', 'e', 'v', 'e', 'r', 'b', '\0' + }, + /*.CopyrightInfo = */ + { + 'C', 'o', 'p', 'y', 'r', 'i', 'g', 'h', 't', ' ', '(', 'c', ')', + 'E', 't', 'h', 'a', 'n', ' ', 'L', 'e', 'e', '\0' + }, + /*.MajorVersion = */ 0, + /*.MinorVersion = */ 0, + /*.Flags = */( + FAPO_FLAG_FRAMERATE_MUST_MATCH | + FAPO_FLAG_BITSPERSAMPLE_MUST_MATCH | + FAPO_FLAG_BUFFERCOUNT_MUST_MATCH | + FAPO_FLAG_INPLACE_SUPPORTED | + FAPO_FLAG_INPLACE_REQUIRED + ), + /*.MinInputBufferCount = */ 1, + /*.MaxInputBufferCount = */ 1, + /*.MinOutputBufferCount = */ 1, + /*.MaxOutputBufferCount =*/ 1 +}; + +typedef struct FAPOFXReverb +{ + FAPOBase base; + + /* TODO */ +} FAPOFXReverb; + +uint32_t FAPOFXReverb_Initialize( + FAPOFXReverb *fapo, + const void* pData, + uint32_t DataByteSize +) { + #define INITPARAMS(offset) \ + FAudio_memcpy( \ + fapo->base.m_pParameterBlocks + DataByteSize * offset, \ + pData, \ + DataByteSize \ + ); + INITPARAMS(0) + INITPARAMS(1) + INITPARAMS(2) + #undef INITPARAMS + return 0; +} + +void FAPOFXReverb_Process( + FAPOFXReverb *fapo, + uint32_t InputProcessParameterCount, + const FAPOProcessBufferParameters* pInputProcessParameters, + uint32_t OutputProcessParameterCount, + FAPOProcessBufferParameters* pOutputProcessParameters, + int32_t IsEnabled +) { + FAPOBase_BeginProcess(&fapo->base); + + /* TODO */ + + FAPOBase_EndProcess(&fapo->base); +} + +void FAPOFXReverb_Free(void* fapo) +{ + FAPOFXReverb *reverb = (FAPOFXReverb*) fapo; + reverb->base.pFree(reverb->base.m_pParameterBlocks); + reverb->base.pFree(fapo); +} + +/* Public API */ + +uint32_t FAPOFXCreateReverb( + FAPO **pEffect, + const void *pInitData, + uint32_t InitDataByteSize, + FAudioMallocFunc customMalloc, + FAudioFreeFunc customFree, + FAudioReallocFunc customRealloc, + uint8_t legacy +) { + const FAPOFXReverbParameters fxdefault = + { + FAPOFXREVERB_DEFAULT_DIFFUSION, + FAPOFXREVERB_DEFAULT_ROOMSIZE, + }; + + /* Allocate... */ + FAPOFXReverb *result = (FAPOFXReverb*) customMalloc( + sizeof(FAPOFXReverb) + ); + uint8_t *params = (uint8_t*) customMalloc( + sizeof(FAPOFXReverbParameters) * 3 + ); + if (pInitData == NULL) + { + FAudio_zero(params, sizeof(FAPOFXReverbParameters) * 3); + #define INITPARAMS(offset) \ + FAudio_memcpy( \ + params + sizeof(FAPOFXReverbParameters) * offset, \ + &fxdefault, \ + sizeof(FAPOFXReverbParameters) \ + ); + INITPARAMS(0) + INITPARAMS(1) + INITPARAMS(2) + #undef INITPARAMS + } + else + { + FAudio_assert(InitDataByteSize == sizeof(FAPOFXReverbParameters)); + FAudio_memcpy(params, pInitData, InitDataByteSize); + FAudio_memcpy(params + InitDataByteSize, pInitData, InitDataByteSize); + FAudio_memcpy(params + (InitDataByteSize * 2), pInitData, InitDataByteSize); + } + + /* Initialize... */ + FAudio_memcpy( + &FXReverbProperties_LEGACY.clsid, + &FAPOFX_CLSID_FXReverb_LEGACY, + sizeof(FAudioGUID) + ); + FAudio_memcpy( + &FXReverbProperties.clsid, + &FAPOFX_CLSID_FXReverb, + sizeof(FAudioGUID) + ); + CreateFAPOBaseWithCustomAllocatorEXT( + &result->base, + legacy ? &FXReverbProperties_LEGACY : &FXReverbProperties, + params, + sizeof(FAPOFXReverbParameters), + 0, + customMalloc, + customFree, + customRealloc + ); + + /* Function table... */ + result->base.base.Initialize = (InitializeFunc) + FAPOFXReverb_Initialize; + result->base.base.Process = (ProcessFunc) + FAPOFXReverb_Process; + result->base.Destructor = FAPOFXReverb_Free; + + /* Finally. */ + *pEffect = &result->base.base; + return 0; +} + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/src/FAudio.c b/libs/faudio/src/FAudio.c new file mode 100644 index 00000000000..81363b983c7 --- /dev/null +++ b/libs/faudio/src/FAudio.c @@ -0,0 +1,3201 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * Ethan "flibitijibibo" Lee + * + */ + +#include "FAudio_internal.h" + +#define MAKE_SUBFORMAT_GUID(guid, fmt) \ + FAudioGUID DATAFORMAT_SUBTYPE_##guid = \ + { \ + (uint16_t) (fmt), \ + 0x0000, \ + 0x0010, \ + { \ + 0x80, \ + 0x00, \ + 0x00, \ + 0xAA, \ + 0x00, \ + 0x38, \ + 0x9B, \ + 0x71 \ + } \ + } +MAKE_SUBFORMAT_GUID(PCM, 1); +MAKE_SUBFORMAT_GUID(ADPCM, 2); +MAKE_SUBFORMAT_GUID(IEEE_FLOAT, 3); +MAKE_SUBFORMAT_GUID(XMAUDIO2, FAUDIO_FORMAT_XMAUDIO2); +MAKE_SUBFORMAT_GUID(WMAUDIO2, FAUDIO_FORMAT_WMAUDIO2); +MAKE_SUBFORMAT_GUID(WMAUDIO3, FAUDIO_FORMAT_WMAUDIO3); +MAKE_SUBFORMAT_GUID(WMAUDIO_LOSSLESS, FAUDIO_FORMAT_WMAUDIO_LOSSLESS); +#undef MAKE_SUBFORMAT_GUID + +#ifdef FAUDIO_DUMP_VOICES +static void FAudio_DUMPVOICE_Init(const FAudioSourceVoice *voice); +static void FAudio_DUMPVOICE_Finalize(const FAudioSourceVoice *voice); +static void FAudio_DUMPVOICE_WriteBuffer( + const FAudioSourceVoice *voice, + const FAudioBuffer *pBuffer, + const FAudioBufferWMA *pBufferWMA, + const uint32_t playBegin, + const uint32_t playLength +); +#endif /* FAUDIO_DUMP_VOICES */ + +/* FAudio Version */ + +uint32_t FAudioLinkedVersion(void) +{ + return FAUDIO_COMPILED_VERSION; +} + +/* FAudio Interface */ + +uint32_t FAudioCreate( + FAudio **ppFAudio, + uint32_t Flags, + FAudioProcessor XAudio2Processor +) { + FAudioCOMConstructEXT(ppFAudio, FAUDIO_TARGET_VERSION); + FAudio_Initialize(*ppFAudio, Flags, XAudio2Processor); + return 0; +} + +uint32_t FAudioCOMConstructEXT(FAudio **ppFAudio, uint8_t version) +{ + return FAudioCOMConstructWithCustomAllocatorEXT( + ppFAudio, + version, + FAudio_malloc, + FAudio_free, + FAudio_realloc + ); +} + +uint32_t FAudioCreateWithCustomAllocatorEXT( + FAudio **ppFAudio, + uint32_t Flags, + FAudioProcessor XAudio2Processor, + FAudioMallocFunc customMalloc, + FAudioFreeFunc customFree, + FAudioReallocFunc customRealloc +) { + FAudioCOMConstructWithCustomAllocatorEXT( + ppFAudio, + FAUDIO_TARGET_VERSION, + customMalloc, + customFree, + customRealloc + ); + FAudio_Initialize(*ppFAudio, Flags, XAudio2Processor); + return 0; +} + +uint32_t FAudioCOMConstructWithCustomAllocatorEXT( + FAudio **ppFAudio, + uint8_t version, + FAudioMallocFunc customMalloc, + FAudioFreeFunc customFree, + FAudioReallocFunc customRealloc +) { +#ifndef FAUDIO_DISABLE_DEBUGCONFIGURATION + FAudioDebugConfiguration debugInit = {0}; +#endif /* FAUDIO_DISABLE_DEBUGCONFIGURATION */ + FAudio_PlatformAddRef(); + *ppFAudio = (FAudio*) customMalloc(sizeof(FAudio)); + FAudio_zero(*ppFAudio, sizeof(FAudio)); + (*ppFAudio)->version = version; +#ifndef FAUDIO_DISABLE_DEBUGCONFIGURATION + FAudio_SetDebugConfiguration(*ppFAudio, &debugInit, NULL); +#endif /* FAUDIO_DISABLE_DEBUGCONFIGURATION */ + (*ppFAudio)->sourceLock = FAudio_PlatformCreateMutex(); + LOG_MUTEX_CREATE((*ppFAudio), (*ppFAudio)->sourceLock) + (*ppFAudio)->submixLock = FAudio_PlatformCreateMutex(); + LOG_MUTEX_CREATE((*ppFAudio), (*ppFAudio)->submixLock) + (*ppFAudio)->callbackLock = FAudio_PlatformCreateMutex(); + LOG_MUTEX_CREATE((*ppFAudio), (*ppFAudio)->callbackLock) + (*ppFAudio)->operationLock = FAudio_PlatformCreateMutex(); + LOG_MUTEX_CREATE((*ppFAudio), (*ppFAudio)->operationLock) + (*ppFAudio)->pMalloc = customMalloc; + (*ppFAudio)->pFree = customFree; + (*ppFAudio)->pRealloc = customRealloc; + (*ppFAudio)->refcount = 1; + return 0; +} + +uint32_t FAudio_AddRef(FAudio *audio) +{ + LOG_API_ENTER(audio) + audio->refcount += 1; + LOG_API_EXIT(audio) + return audio->refcount; +} + +uint32_t FAudio_Release(FAudio *audio) +{ + uint32_t refcount; + LOG_API_ENTER(audio) + audio->refcount -= 1; + refcount = audio->refcount; + if (audio->refcount == 0) + { + FAudio_OPERATIONSET_ClearAll(audio); + FAudio_StopEngine(audio); + audio->pFree(audio->decodeCache); + audio->pFree(audio->resampleCache); + audio->pFree(audio->effectChainCache); + LOG_MUTEX_DESTROY(audio, audio->sourceLock) + FAudio_PlatformDestroyMutex(audio->sourceLock); + LOG_MUTEX_DESTROY(audio, audio->submixLock) + FAudio_PlatformDestroyMutex(audio->submixLock); + LOG_MUTEX_DESTROY(audio, audio->callbackLock) + FAudio_PlatformDestroyMutex(audio->callbackLock); + LOG_MUTEX_DESTROY(audio, audio->operationLock) + FAudio_PlatformDestroyMutex(audio->operationLock); + audio->pFree(audio); + FAudio_PlatformRelease(); + } + else + { + LOG_API_EXIT(audio) + } + return refcount; +} + +uint32_t FAudio_GetDeviceCount(FAudio *audio, uint32_t *pCount) +{ + LOG_API_ENTER(audio) + *pCount = FAudio_PlatformGetDeviceCount(); + LOG_API_EXIT(audio) + return 0; +} + +uint32_t FAudio_GetDeviceDetails( + FAudio *audio, + uint32_t Index, + FAudioDeviceDetails *pDeviceDetails +) { + uint32_t result; + LOG_API_ENTER(audio) + result = FAudio_PlatformGetDeviceDetails(Index, pDeviceDetails); + LOG_API_EXIT(audio) + return result; +} + +uint32_t FAudio_Initialize( + FAudio *audio, + uint32_t Flags, + FAudioProcessor XAudio2Processor +) { + LOG_API_ENTER(audio) + FAudio_assert(Flags == 0 || Flags == FAUDIO_DEBUG_ENGINE); + FAudio_assert(XAudio2Processor == FAUDIO_DEFAULT_PROCESSOR); + + audio->initFlags = Flags; + + /* FIXME: This is lazy... */ + audio->decodeCache = (float*) audio->pMalloc(sizeof(float)); + audio->resampleCache = (float*) audio->pMalloc(sizeof(float)); + audio->decodeSamples = 1; + audio->resampleSamples = 1; + + FAudio_StartEngine(audio); + LOG_API_EXIT(audio) + return 0; +} + +uint32_t FAudio_RegisterForCallbacks( + FAudio *audio, + FAudioEngineCallback *pCallback +) { + LOG_API_ENTER(audio) + LinkedList_AddEntry( + &audio->callbacks, + pCallback, + audio->callbackLock, + audio->pMalloc + ); + LOG_API_EXIT(audio) + return 0; +} + +void FAudio_UnregisterForCallbacks( + FAudio *audio, + FAudioEngineCallback *pCallback +) { + LOG_API_ENTER(audio) + LinkedList_RemoveEntry( + &audio->callbacks, + pCallback, + audio->callbackLock, + audio->pFree + ); + LOG_API_EXIT(audio) +} + +uint32_t FAudio_CreateSourceVoice( + FAudio *audio, + FAudioSourceVoice **ppSourceVoice, + const FAudioWaveFormatEx *pSourceFormat, + uint32_t Flags, + float MaxFrequencyRatio, + FAudioVoiceCallback *pCallback, + const FAudioVoiceSends *pSendList, + const FAudioEffectChain *pEffectChain +) { + uint32_t i; + + LOG_API_ENTER(audio) + LOG_FORMAT(audio, pSourceFormat) + + *ppSourceVoice = (FAudioSourceVoice*) audio->pMalloc(sizeof(FAudioVoice)); + FAudio_zero(*ppSourceVoice, sizeof(FAudioSourceVoice)); + (*ppSourceVoice)->audio = audio; + (*ppSourceVoice)->type = FAUDIO_VOICE_SOURCE; + (*ppSourceVoice)->flags = Flags; + (*ppSourceVoice)->filter.Type = FAUDIO_DEFAULT_FILTER_TYPE; + (*ppSourceVoice)->filter.Frequency = FAUDIO_DEFAULT_FILTER_FREQUENCY; + (*ppSourceVoice)->filter.OneOverQ = FAUDIO_DEFAULT_FILTER_ONEOVERQ; + (*ppSourceVoice)->sendLock = FAudio_PlatformCreateMutex(); + LOG_MUTEX_CREATE(audio, (*ppSourceVoice)->sendLock) + (*ppSourceVoice)->effectLock = FAudio_PlatformCreateMutex(); + LOG_MUTEX_CREATE(audio, (*ppSourceVoice)->effectLock) + (*ppSourceVoice)->filterLock = FAudio_PlatformCreateMutex(); + LOG_MUTEX_CREATE(audio, (*ppSourceVoice)->filterLock) + (*ppSourceVoice)->volumeLock = FAudio_PlatformCreateMutex(); + LOG_MUTEX_CREATE(audio, (*ppSourceVoice)->volumeLock) + + /* Source Properties */ + FAudio_assert(MaxFrequencyRatio <= FAUDIO_MAX_FREQ_RATIO); + (*ppSourceVoice)->src.maxFreqRatio = MaxFrequencyRatio; + + if ( pSourceFormat->wFormatTag == FAUDIO_FORMAT_PCM || + pSourceFormat->wFormatTag == FAUDIO_FORMAT_IEEE_FLOAT || + pSourceFormat->wFormatTag == FAUDIO_FORMAT_WMAUDIO2 ) + { + FAudioWaveFormatExtensible *fmtex = (FAudioWaveFormatExtensible*) audio->pMalloc( + sizeof(FAudioWaveFormatExtensible) + ); + /* convert PCM to EXTENSIBLE */ + fmtex->Format.wFormatTag = FAUDIO_FORMAT_EXTENSIBLE; + fmtex->Format.nChannels = pSourceFormat->nChannels; + fmtex->Format.nSamplesPerSec = pSourceFormat->nSamplesPerSec; + fmtex->Format.nAvgBytesPerSec = pSourceFormat->nAvgBytesPerSec; + fmtex->Format.nBlockAlign = pSourceFormat->nBlockAlign; + fmtex->Format.wBitsPerSample = pSourceFormat->wBitsPerSample; + fmtex->Format.cbSize = sizeof(FAudioWaveFormatExtensible) - sizeof(FAudioWaveFormatEx); + fmtex->Samples.wValidBitsPerSample = pSourceFormat->wBitsPerSample; + fmtex->dwChannelMask = 0; + if (pSourceFormat->wFormatTag == FAUDIO_FORMAT_PCM) + { + FAudio_memcpy(&fmtex->SubFormat, &DATAFORMAT_SUBTYPE_PCM, sizeof(FAudioGUID)); + } + else if (pSourceFormat->wFormatTag == FAUDIO_FORMAT_IEEE_FLOAT) + { + FAudio_memcpy(&fmtex->SubFormat, &DATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(FAudioGUID)); + } + else if (pSourceFormat->wFormatTag == FAUDIO_FORMAT_WMAUDIO2) + { + FAudio_memcpy(&fmtex->SubFormat, &DATAFORMAT_SUBTYPE_WMAUDIO2, sizeof(FAudioGUID)); + } + (*ppSourceVoice)->src.format = &fmtex->Format; + } + else if (pSourceFormat->wFormatTag == FAUDIO_FORMAT_MSADPCM) + { + FAudioADPCMWaveFormat *fmtex = (FAudioADPCMWaveFormat*) audio->pMalloc( + sizeof(FAudioADPCMWaveFormat) + ); + + /* Copy what we can, ideally the sizes match! */ + size_t cbSize = sizeof(FAudioWaveFormatEx) + pSourceFormat->cbSize; + FAudio_memcpy( + fmtex, + pSourceFormat, + FAudio_min(cbSize, sizeof(FAudioADPCMWaveFormat)) + ); + if (cbSize < sizeof(FAudioADPCMWaveFormat)) + { + FAudio_zero( + ((uint8_t*) fmtex) + cbSize, + sizeof(FAudioADPCMWaveFormat) - cbSize + ); + } + + /* XAudio2 does not validate this input! */ + fmtex->wfx.cbSize = sizeof(FAudioADPCMWaveFormat) - sizeof(FAudioWaveFormatEx); + fmtex->wSamplesPerBlock = (( + fmtex->wfx.nBlockAlign / fmtex->wfx.nChannels + ) - 6) * 2; + (*ppSourceVoice)->src.format = &fmtex->wfx; + } + else if (pSourceFormat->wFormatTag == FAUDIO_FORMAT_XMAUDIO2) + { + FAudioXMA2WaveFormat *fmtex = (FAudioXMA2WaveFormat*) audio->pMalloc( + sizeof(FAudioXMA2WaveFormat) + ); + + /* Copy what we can, ideally the sizes match! */ + size_t cbSize = sizeof(FAudioWaveFormatEx) + pSourceFormat->cbSize; + FAudio_memcpy( + fmtex, + pSourceFormat, + FAudio_min(cbSize, sizeof(FAudioXMA2WaveFormat)) + ); + if (cbSize < sizeof(FAudioXMA2WaveFormat)) + { + FAudio_zero( + ((uint8_t*) fmtex) + cbSize, + sizeof(FAudioADPCMWaveFormat) - cbSize + ); + } + + /* Does XAudio2 validate this input?! */ + fmtex->wfx.cbSize = sizeof(FAudioXMA2WaveFormat) - sizeof(FAudioWaveFormatEx); + (*ppSourceVoice)->src.format = &fmtex->wfx; + } + else + { + /* direct copy anything else */ + (*ppSourceVoice)->src.format = (FAudioWaveFormatEx*) audio->pMalloc( + sizeof(FAudioWaveFormatEx) + pSourceFormat->cbSize + ); + FAudio_memcpy( + (*ppSourceVoice)->src.format, + pSourceFormat, + sizeof(FAudioWaveFormatEx) + pSourceFormat->cbSize + ); + } + + (*ppSourceVoice)->src.callback = pCallback; + (*ppSourceVoice)->src.active = 0; + (*ppSourceVoice)->src.freqRatio = 1.0f; + (*ppSourceVoice)->src.totalSamples = 0; + (*ppSourceVoice)->src.bufferList = NULL; + (*ppSourceVoice)->src.flushList = NULL; + (*ppSourceVoice)->src.bufferLock = FAudio_PlatformCreateMutex(); + LOG_MUTEX_CREATE(audio, (*ppSourceVoice)->src.bufferLock) + + if ((*ppSourceVoice)->src.format->wFormatTag == FAUDIO_FORMAT_EXTENSIBLE) + { + FAudioWaveFormatExtensible *fmtex = (FAudioWaveFormatExtensible*) (*ppSourceVoice)->src.format; + + #define COMPARE_GUID(type) \ + (FAudio_memcmp( \ + &fmtex->SubFormat, \ + &DATAFORMAT_SUBTYPE_##type, \ + sizeof(FAudioGUID) \ + ) == 0) + if (COMPARE_GUID(PCM)) + { + #define DECODER(bit) \ + if (fmtex->Format.wBitsPerSample == bit) \ + { \ + (*ppSourceVoice)->src.decode = FAudio_INTERNAL_DecodePCM##bit; \ + } + DECODER(16) + else DECODER(8) + else DECODER(24) + else DECODER(32) + else + { + LOG_ERROR( + audio, + "Unrecognized wBitsPerSample: %d", + fmtex->Format.wBitsPerSample + ) + FAudio_assert(0 && "Unrecognized wBitsPerSample!"); + } + #undef DECODER + } + else if (COMPARE_GUID(IEEE_FLOAT)) + { + /* FIXME: Weird behavior! + * Prototype creates a source with the IEEE_FLOAT tag, + * but it's actually PCM16. It seems to prioritize + * wBitsPerSample over the format tag. Not sure if we + * should fold this section into the section above...? + * -flibit + */ + if (fmtex->Format.wBitsPerSample == 16) + { + (*ppSourceVoice)->src.decode = FAudio_INTERNAL_DecodePCM16; + } + else + { + (*ppSourceVoice)->src.decode = FAudio_INTERNAL_DecodePCM32F; + } + } + else if ( COMPARE_GUID(WMAUDIO2) || + COMPARE_GUID(WMAUDIO3) || + COMPARE_GUID(WMAUDIO_LOSSLESS) ) + { +#ifdef HAVE_WMADEC + if (FAudio_WMADEC_init(*ppSourceVoice, fmtex->SubFormat.Data1) != 0) + { + (*ppSourceVoice)->src.decode = FAudio_INTERNAL_DecodeWMAERROR; + } +#else + FAudio_assert(0 && "xWMA is not supported!"); + (*ppSourceVoice)->src.decode = FAudio_INTERNAL_DecodeWMAERROR; +#endif /* HAVE_WMADEC */ + } + else + { + FAudio_assert(0 && "Unsupported WAVEFORMATEXTENSIBLE subtype!"); + } + #undef COMPARE_GUID + } + else if ((*ppSourceVoice)->src.format->wFormatTag == FAUDIO_FORMAT_XMAUDIO2) + { +#ifdef HAVE_WMADEC + if (FAudio_WMADEC_init(*ppSourceVoice, FAUDIO_FORMAT_XMAUDIO2) != 0) + { + (*ppSourceVoice)->src.decode = FAudio_INTERNAL_DecodeWMAERROR; + } +#else + FAudio_assert(0 && "XMA2 is not supported!"); + (*ppSourceVoice)->src.decode = FAudio_INTERNAL_DecodeWMAERROR; +#endif /* HAVE_WMADEC */ + } + else if ((*ppSourceVoice)->src.format->wFormatTag == FAUDIO_FORMAT_MSADPCM) + { + (*ppSourceVoice)->src.decode = ((*ppSourceVoice)->src.format->nChannels == 2) ? + FAudio_INTERNAL_DecodeStereoMSADPCM : + FAudio_INTERNAL_DecodeMonoMSADPCM; + } + else + { + FAudio_assert(0 && "Unsupported format tag!"); + } + + if ((*ppSourceVoice)->src.format->nChannels == 1) + { + (*ppSourceVoice)->src.resample = FAudio_INTERNAL_ResampleMono; + } + else if ((*ppSourceVoice)->src.format->nChannels == 2) + { + (*ppSourceVoice)->src.resample = FAudio_INTERNAL_ResampleStereo; + } + else + { + (*ppSourceVoice)->src.resample = FAudio_INTERNAL_ResampleGeneric; + } + + (*ppSourceVoice)->src.curBufferOffset = 0; + + /* Sends/Effects */ + FAudio_INTERNAL_VoiceOutputFrequency(*ppSourceVoice, pSendList); + FAudioVoice_SetEffectChain(*ppSourceVoice, pEffectChain); + + /* Default Levels */ + (*ppSourceVoice)->volume = 1.0f; + (*ppSourceVoice)->channelVolume = (float*) audio->pMalloc( + sizeof(float) * (*ppSourceVoice)->outputChannels + ); + for (i = 0; i < (*ppSourceVoice)->outputChannels; i += 1) + { + (*ppSourceVoice)->channelVolume[i] = 1.0f; + } + + FAudioVoice_SetOutputVoices(*ppSourceVoice, pSendList); + + /* Filters */ + if (Flags & FAUDIO_VOICE_USEFILTER) + { + (*ppSourceVoice)->filterState = (FAudioFilterState*) audio->pMalloc( + sizeof(FAudioFilterState) * (*ppSourceVoice)->src.format->nChannels + ); + FAudio_zero( + (*ppSourceVoice)->filterState, + sizeof(FAudioFilterState) * (*ppSourceVoice)->src.format->nChannels + ); + } + + /* Sample Storage */ + (*ppSourceVoice)->src.decodeSamples = (uint32_t) (FAudio_ceil( + (double) audio->updateSize * + (double) MaxFrequencyRatio * + (double) (*ppSourceVoice)->src.format->nSamplesPerSec / + (double) audio->master->master.inputSampleRate + )) + EXTRA_DECODE_PADDING * (*ppSourceVoice)->src.format->nChannels; + FAudio_INTERNAL_ResizeDecodeCache( + audio, + ((*ppSourceVoice)->src.decodeSamples + EXTRA_DECODE_PADDING) * (*ppSourceVoice)->src.format->nChannels + ); + + LOG_INFO(audio, "-> %p", (void*) (*ppSourceVoice)) + + /* Add to list, finally. */ + LinkedList_PrependEntry( + &audio->sources, + *ppSourceVoice, + audio->sourceLock, + audio->pMalloc + ); + FAudio_AddRef(audio); + +#ifdef FAUDIO_DUMP_VOICES + FAudio_DUMPVOICE_Init(*ppSourceVoice); +#endif /* FAUDIO_DUMP_VOICES */ + + LOG_API_EXIT(audio) + return 0; +} + +uint32_t FAudio_CreateSubmixVoice( + FAudio *audio, + FAudioSubmixVoice **ppSubmixVoice, + uint32_t InputChannels, + uint32_t InputSampleRate, + uint32_t Flags, + uint32_t ProcessingStage, + const FAudioVoiceSends *pSendList, + const FAudioEffectChain *pEffectChain +) { + uint32_t i; + + LOG_API_ENTER(audio) + + *ppSubmixVoice = (FAudioSubmixVoice*) audio->pMalloc(sizeof(FAudioVoice)); + FAudio_zero(*ppSubmixVoice, sizeof(FAudioSubmixVoice)); + (*ppSubmixVoice)->audio = audio; + (*ppSubmixVoice)->type = FAUDIO_VOICE_SUBMIX; + (*ppSubmixVoice)->flags = Flags; + (*ppSubmixVoice)->filter.Type = FAUDIO_DEFAULT_FILTER_TYPE; + (*ppSubmixVoice)->filter.Frequency = FAUDIO_DEFAULT_FILTER_FREQUENCY; + (*ppSubmixVoice)->filter.OneOverQ = FAUDIO_DEFAULT_FILTER_ONEOVERQ; + (*ppSubmixVoice)->sendLock = FAudio_PlatformCreateMutex(); + LOG_MUTEX_CREATE(audio, (*ppSubmixVoice)->sendLock) + (*ppSubmixVoice)->effectLock = FAudio_PlatformCreateMutex(); + LOG_MUTEX_CREATE(audio, (*ppSubmixVoice)->effectLock) + (*ppSubmixVoice)->filterLock = FAudio_PlatformCreateMutex(); + LOG_MUTEX_CREATE(audio, (*ppSubmixVoice)->filterLock) + (*ppSubmixVoice)->volumeLock = FAudio_PlatformCreateMutex(); + LOG_MUTEX_CREATE(audio, (*ppSubmixVoice)->volumeLock) + + /* Submix Properties */ + (*ppSubmixVoice)->mix.inputChannels = InputChannels; + (*ppSubmixVoice)->mix.inputSampleRate = InputSampleRate; + (*ppSubmixVoice)->mix.processingStage = ProcessingStage; + + /* Resampler */ + if (InputChannels == 1) + { + (*ppSubmixVoice)->mix.resample = FAudio_INTERNAL_ResampleMono; + } + else if (InputChannels == 2) + { + (*ppSubmixVoice)->mix.resample = FAudio_INTERNAL_ResampleStereo; + } + else + { + (*ppSubmixVoice)->mix.resample = FAudio_INTERNAL_ResampleGeneric; + } + + /* Sample Storage */ + (*ppSubmixVoice)->mix.inputSamples = ((uint32_t) FAudio_ceil( + audio->updateSize * + (double) InputSampleRate / + (double) audio->master->master.inputSampleRate + ) + EXTRA_DECODE_PADDING) * InputChannels; + (*ppSubmixVoice)->mix.inputCache = (float*) audio->pMalloc( + sizeof(float) * (*ppSubmixVoice)->mix.inputSamples + ); + FAudio_zero( /* Zero this now, for the first update */ + (*ppSubmixVoice)->mix.inputCache, + sizeof(float) * (*ppSubmixVoice)->mix.inputSamples + ); + + /* Sends/Effects */ + FAudio_INTERNAL_VoiceOutputFrequency(*ppSubmixVoice, pSendList); + FAudioVoice_SetEffectChain(*ppSubmixVoice, pEffectChain); + + /* Default Levels */ + (*ppSubmixVoice)->volume = 1.0f; + (*ppSubmixVoice)->channelVolume = (float*) audio->pMalloc( + sizeof(float) * (*ppSubmixVoice)->outputChannels + ); + for (i = 0; i < (*ppSubmixVoice)->outputChannels; i += 1) + { + (*ppSubmixVoice)->channelVolume[i] = 1.0f; + } + + FAudioVoice_SetOutputVoices(*ppSubmixVoice, pSendList); + + /* Filters */ + if (Flags & FAUDIO_VOICE_USEFILTER) + { + (*ppSubmixVoice)->filterState = (FAudioFilterState*) audio->pMalloc( + sizeof(FAudioFilterState) * InputChannels + ); + FAudio_zero( + (*ppSubmixVoice)->filterState, + sizeof(FAudioFilterState) * InputChannels + ); + } + + /* Add to list, finally. */ + FAudio_INTERNAL_InsertSubmixSorted( + &audio->submixes, + *ppSubmixVoice, + audio->submixLock, + audio->pMalloc + ); + FAudio_AddRef(audio); + + LOG_API_EXIT(audio) + return 0; +} + +uint32_t FAudio_CreateMasteringVoice( + FAudio *audio, + FAudioMasteringVoice **ppMasteringVoice, + uint32_t InputChannels, + uint32_t InputSampleRate, + uint32_t Flags, + uint32_t DeviceIndex, + const FAudioEffectChain *pEffectChain +) { + FAudioDeviceDetails details; + + LOG_API_ENTER(audio) + + /* For now we only support one allocated master voice at a time */ + FAudio_assert(audio->master == NULL); + + if (FAudio_GetDeviceDetails(audio, DeviceIndex, &details) != 0) + { + return FAUDIO_E_INVALID_CALL; + } + + *ppMasteringVoice = (FAudioMasteringVoice*) audio->pMalloc(sizeof(FAudioVoice)); + FAudio_zero(*ppMasteringVoice, sizeof(FAudioMasteringVoice)); + (*ppMasteringVoice)->audio = audio; + (*ppMasteringVoice)->type = FAUDIO_VOICE_MASTER; + (*ppMasteringVoice)->flags = Flags; + (*ppMasteringVoice)->effectLock = FAudio_PlatformCreateMutex(); + LOG_MUTEX_CREATE(audio, (*ppMasteringVoice)->effectLock) + (*ppMasteringVoice)->volumeLock = FAudio_PlatformCreateMutex(); + LOG_MUTEX_CREATE(audio, (*ppMasteringVoice)->volumeLock) + + /* Default Levels */ + (*ppMasteringVoice)->volume = 1.0f; + + /* Master Properties */ + (*ppMasteringVoice)->master.inputChannels = (InputChannels == FAUDIO_DEFAULT_CHANNELS) ? + details.OutputFormat.Format.nChannels : + InputChannels; + (*ppMasteringVoice)->master.inputSampleRate = (InputSampleRate == FAUDIO_DEFAULT_SAMPLERATE) ? + details.OutputFormat.Format.nSamplesPerSec : + InputSampleRate; + + /* Sends/Effects */ + FAudio_zero(&(*ppMasteringVoice)->sends, sizeof(FAudioVoiceSends)); + FAudioVoice_SetEffectChain(*ppMasteringVoice, pEffectChain); + + /* This is now safe enough to assign */ + audio->master = *ppMasteringVoice; + + /* Build the device format. + * The most unintuitive part of this is the use of outputChannels + * instead of master.inputChannels. Bizarrely, the effect chain can + * dictate the _actual_ output channel count, and when the channel count + * mismatches, we have to add a staging buffer for effects to process on + * before ultimately copying the final result to the device. ARGH. + */ + WriteWaveFormatExtensible( + &audio->mixFormat, + audio->master->outputChannels, + audio->master->master.inputSampleRate, + &DATAFORMAT_SUBTYPE_IEEE_FLOAT + ); + + /* Platform Device */ + FAudio_AddRef(audio); + FAudio_PlatformInit( + audio, + audio->initFlags, + DeviceIndex, + &audio->mixFormat, + &audio->updateSize, + &audio->platform + ); + if (audio->platform == NULL) + { + FAudioVoice_DestroyVoice(*ppMasteringVoice); + *ppMasteringVoice = NULL; + + /* Not the best code, but it's probably true? */ + return FAUDIO_E_DEVICE_INVALIDATED; + } + audio->master->outputChannels = audio->mixFormat.Format.nChannels; + audio->master->master.inputSampleRate = audio->mixFormat.Format.nSamplesPerSec; + + /* Effect Chain Cache */ + if ((*ppMasteringVoice)->master.inputChannels != (*ppMasteringVoice)->outputChannels) + { + (*ppMasteringVoice)->master.effectCache = (float*) audio->pMalloc( + sizeof(float) * + audio->updateSize * + (*ppMasteringVoice)->master.inputChannels + ); + } + + LOG_API_EXIT(audio) + return 0; +} + +uint32_t FAudio_CreateMasteringVoice8( + FAudio *audio, + FAudioMasteringVoice **ppMasteringVoice, + uint32_t InputChannels, + uint32_t InputSampleRate, + uint32_t Flags, + uint16_t *szDeviceId, + const FAudioEffectChain *pEffectChain, + FAudioStreamCategory StreamCategory +) { + uint32_t DeviceIndex, retval; + + LOG_API_ENTER(audio) + + /* Eventually, we'll want the old CreateMastering to call the new one. + * That will depend on us being able to use DeviceID though. + * For now, use our little ID hack to turn szDeviceId into DeviceIndex. + * -flibit + */ + if (szDeviceId == NULL || szDeviceId[0] == 0) + { + DeviceIndex = 0; + } + else + { + DeviceIndex = szDeviceId[0] - L'0'; + if (DeviceIndex > FAudio_PlatformGetDeviceCount()) + { + DeviceIndex = 0; + } + } + + /* Note that StreamCategory is ignored! */ + retval = FAudio_CreateMasteringVoice( + audio, + ppMasteringVoice, + InputChannels, + InputSampleRate, + Flags, + DeviceIndex, + pEffectChain + ); + + LOG_API_EXIT(audio) + return retval; +} + +void FAudio_SetEngineProcedureEXT( + FAudio *audio, + FAudioEngineProcedureEXT clientEngineProc, + void *user +) { + LOG_API_ENTER(audio) + audio->pClientEngineProc = clientEngineProc; + audio->clientEngineUser = user; + LOG_API_EXIT(audio) +} + +uint32_t FAudio_StartEngine(FAudio *audio) +{ + LOG_API_ENTER(audio) + audio->active = 1; + LOG_API_EXIT(audio) + return 0; +} + +void FAudio_StopEngine(FAudio *audio) +{ + LOG_API_ENTER(audio) + audio->active = 0; + FAudio_OPERATIONSET_CommitAll(audio); + FAudio_OPERATIONSET_Execute(audio); + LOG_API_EXIT(audio) +} + +uint32_t FAudio_CommitOperationSet(FAudio *audio, uint32_t OperationSet) +{ + LOG_API_ENTER(audio) + if (OperationSet == FAUDIO_COMMIT_ALL) + { + FAudio_OPERATIONSET_CommitAll(audio); + } + else + { + FAudio_OPERATIONSET_Commit(audio, OperationSet); + } + LOG_API_EXIT(audio) + return 0; +} + +uint32_t FAudio_CommitChanges(FAudio *audio) +{ + FAudio_Log( + "IF YOU CAN READ THIS, YOUR PROGRAM IS ABOUT TO BREAK!" + "\n\nEither you or somebody else is using FAudio_CommitChanges," + "\nwhen they should be using FAudio_CommitOperationSet instead." + "\n\nIf your program calls this, move to CommitOperationSet." + "\n\nIf somebody else is calling this, find out who it is and" + "\nfile a bug report with them ASAP." + ); + + /* Seriously, this is like the worst possible thing short of no-oping. + * For the love-a Pete, just migrate, do it, what is wrong with you + */ + return FAudio_CommitOperationSet(audio, FAUDIO_COMMIT_ALL); +} + +void FAudio_GetPerformanceData( + FAudio *audio, + FAudioPerformanceData *pPerfData +) { + LinkedList *list; + FAudioSourceVoice *source; + + LOG_API_ENTER(audio) + + FAudio_zero(pPerfData, sizeof(FAudioPerformanceData)); + + FAudio_PlatformLockMutex(audio->sourceLock); + LOG_MUTEX_LOCK(audio, audio->sourceLock) + list = audio->sources; + while (list != NULL) + { + source = (FAudioSourceVoice*) list->entry; + pPerfData->TotalSourceVoiceCount += 1; + if (source->src.active) + { + pPerfData->ActiveSourceVoiceCount += 1; + } + list = list->next; + } + FAudio_PlatformUnlockMutex(audio->sourceLock); + LOG_MUTEX_UNLOCK(audio, audio->sourceLock) + + FAudio_PlatformLockMutex(audio->submixLock); + LOG_MUTEX_LOCK(audio, audio->submixLock) + list = audio->submixes; + while (list != NULL) + { + pPerfData->ActiveSubmixVoiceCount += 1; + list = list->next; + } + FAudio_PlatformUnlockMutex(audio->submixLock); + LOG_MUTEX_UNLOCK(audio, audio->submixLock) + + if (audio->master != NULL) + { + /* estimate, should use real latency from platform */ + pPerfData->CurrentLatencyInSamples = 2 * audio->updateSize; + } + + LOG_API_EXIT(audio) +} + +void FAudio_SetDebugConfiguration( + FAudio *audio, + FAudioDebugConfiguration *pDebugConfiguration, + void* pReserved +) { +#ifndef FAUDIO_DISABLE_DEBUGCONFIGURATION + char *env; + + LOG_API_ENTER(audio) + + FAudio_memcpy( + &audio->debug, + pDebugConfiguration, + sizeof(FAudioDebugConfiguration) + ); + + env = FAudio_getenv("FAUDIO_LOG_EVERYTHING"); + if (env != NULL && *env == '1') + { + audio->debug.TraceMask = ( + FAUDIO_LOG_ERRORS | + FAUDIO_LOG_WARNINGS | + FAUDIO_LOG_INFO | + FAUDIO_LOG_DETAIL | + FAUDIO_LOG_API_CALLS | + FAUDIO_LOG_FUNC_CALLS | + FAUDIO_LOG_TIMING | + FAUDIO_LOG_LOCKS | + FAUDIO_LOG_MEMORY | + FAUDIO_LOG_STREAMING + ); + audio->debug.LogThreadID = 1; + audio->debug.LogFunctionName = 1; + audio->debug.LogTiming = 1; + } + + #define CHECK_ENV(type) \ + env = FAudio_getenv("FAUDIO_LOG_" #type); \ + if (env != NULL) \ + { \ + if (*env == '1') \ + { \ + audio->debug.TraceMask |= FAUDIO_LOG_##type; \ + } \ + else \ + { \ + audio->debug.TraceMask &= ~FAUDIO_LOG_##type; \ + } \ + } + CHECK_ENV(ERRORS) + CHECK_ENV(WARNINGS) + CHECK_ENV(INFO) + CHECK_ENV(DETAIL) + CHECK_ENV(API_CALLS) + CHECK_ENV(FUNC_CALLS) + CHECK_ENV(TIMING) + CHECK_ENV(LOCKS) + CHECK_ENV(MEMORY) + CHECK_ENV(STREAMING) + #undef CHECK_ENV + #define CHECK_ENV(envvar, boolvar) \ + env = FAudio_getenv("FAUDIO_LOG_LOG" #envvar); \ + if (env != NULL) \ + { \ + audio->debug.Log##boolvar = (*env == '1'); \ + } + CHECK_ENV(THREADID, ThreadID) + CHECK_ENV(FILELINE, Fileline) + CHECK_ENV(FUNCTIONNAME, FunctionName) + CHECK_ENV(TIMING, Timing) + #undef CHECK_ENV + + LOG_API_EXIT(audio) +#endif /* FAUDIO_DISABLE_DEBUGCONFIGURATION */ +} + +void FAudio_GetProcessingQuantum( + FAudio *audio, + uint32_t *quantumNumerator, + uint32_t *quantumDenominator +) { + FAudio_assert(audio->master != NULL); + if (quantumNumerator != NULL) + { + *quantumNumerator = audio->updateSize; + } + if (quantumDenominator != NULL) + { + *quantumDenominator = audio->master->master.inputSampleRate; + } +} + +/* FAudioVoice Interface */ + +static void FAudio_RecalcMixMatrix(FAudioVoice *voice, uint32_t sendIndex) +{ + uint32_t oChan, s, d; + FAudioVoice *out = voice->sends.pSends[sendIndex].pOutputVoice; + float volume, *matrix = voice->mixCoefficients[sendIndex]; + + if (voice->type == FAUDIO_VOICE_SUBMIX) + { + volume = 1.f; + } + else + { + volume = voice->volume; + } + + if (out->type == FAUDIO_VOICE_MASTER) + { + oChan = out->master.inputChannels; + } + else + { + oChan = out->mix.inputChannels; + } + + for (d = 0; d < oChan; d += 1) + { + for (s = 0; s < voice->outputChannels; s += 1) + { + matrix[d * voice->outputChannels + s] = volume * + voice->channelVolume[s] * + voice->sendCoefficients[sendIndex][d * voice->outputChannels + s]; + } + } +} + +void FAudioVoice_GetVoiceDetails( + FAudioVoice *voice, + FAudioVoiceDetails *pVoiceDetails +) { + LOG_API_ENTER(voice->audio) + + pVoiceDetails->CreationFlags = voice->flags; + pVoiceDetails->ActiveFlags = voice->flags; + if (voice->type == FAUDIO_VOICE_SOURCE) + { + pVoiceDetails->InputChannels = voice->src.format->nChannels; + pVoiceDetails->InputSampleRate = voice->src.format->nSamplesPerSec; + } + else if (voice->type == FAUDIO_VOICE_SUBMIX) + { + pVoiceDetails->InputChannels = voice->mix.inputChannels; + pVoiceDetails->InputSampleRate = voice->mix.inputSampleRate; + } + else if (voice->type == FAUDIO_VOICE_MASTER) + { + pVoiceDetails->InputChannels = voice->master.inputChannels; + pVoiceDetails->InputSampleRate = voice->master.inputSampleRate; + } + else + { + FAudio_assert(0 && "Unknown voice type!"); + } + + LOG_API_EXIT(voice->audio) +} + +uint32_t FAudioVoice_SetOutputVoices( + FAudioVoice *voice, + const FAudioVoiceSends *pSendList +) { + uint32_t i; + uint32_t outChannels; + FAudioVoiceSends defaultSends; + FAudioSendDescriptor defaultSend; + + LOG_API_ENTER(voice->audio) + + if (voice->type == FAUDIO_VOICE_MASTER) + { + LOG_API_EXIT(voice->audio) + return FAUDIO_E_INVALID_CALL; + } + + FAudio_PlatformLockMutex(voice->sendLock); + LOG_MUTEX_LOCK(voice->audio, voice->sendLock) + + if (FAudio_INTERNAL_VoiceOutputFrequency(voice, pSendList) != 0) + { + LOG_ERROR( + voice->audio, + "%s", + "Changing the sample rate while an effect chain is attached is invalid!" + ) + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + LOG_API_EXIT(voice->audio) + return FAUDIO_E_INVALID_CALL; + } + + FAudio_PlatformLockMutex(voice->volumeLock); + LOG_MUTEX_LOCK(voice->audio, voice->volumeLock) + + /* FIXME: This is lazy... */ + for (i = 0; i < voice->sends.SendCount; i += 1) + { + voice->audio->pFree(voice->sendCoefficients[i]); + } + if (voice->sendCoefficients != NULL) + { + voice->audio->pFree(voice->sendCoefficients); + } + for (i = 0; i < voice->sends.SendCount; i += 1) + { + voice->audio->pFree(voice->mixCoefficients[i]); + } + if (voice->mixCoefficients != NULL) + { + voice->audio->pFree(voice->mixCoefficients); + } + if (voice->sendMix != NULL) + { + voice->audio->pFree(voice->sendMix); + } + if (voice->sendFilter != NULL) + { + voice->audio->pFree(voice->sendFilter); + voice->sendFilter = NULL; + } + if (voice->sendFilterState != NULL) + { + for (i = 0; i < voice->sends.SendCount; i += 1) + { + if (voice->sendFilterState[i] != NULL) + { + voice->audio->pFree(voice->sendFilterState[i]); + } + } + voice->audio->pFree(voice->sendFilterState); + voice->sendFilterState = NULL; + } + if (voice->sends.pSends != NULL) + { + voice->audio->pFree(voice->sends.pSends); + } + + if (pSendList == NULL) + { + /* Default to the mastering voice as output */ + defaultSend.Flags = 0; + defaultSend.pOutputVoice = voice->audio->master; + defaultSends.SendCount = 1; + defaultSends.pSends = &defaultSend; + pSendList = &defaultSends; + } + else if (pSendList->SendCount == 0) + { + /* No sends? Nothing to do... */ + voice->sendCoefficients = NULL; + voice->mixCoefficients = NULL; + voice->sendMix = NULL; + FAudio_zero(&voice->sends, sizeof(FAudioVoiceSends)); + + FAudio_PlatformUnlockMutex(voice->volumeLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->volumeLock) + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + LOG_API_EXIT(voice->audio) + return 0; + } + + /* Copy send list */ + voice->sends.SendCount = pSendList->SendCount; + voice->sends.pSends = (FAudioSendDescriptor*) voice->audio->pMalloc( + pSendList->SendCount * sizeof(FAudioSendDescriptor) + ); + FAudio_memcpy( + voice->sends.pSends, + pSendList->pSends, + pSendList->SendCount * sizeof(FAudioSendDescriptor) + ); + + /* Allocate/Reset default output matrix, mixer function, filters */ + voice->sendCoefficients = (float**) voice->audio->pMalloc( + sizeof(float*) * pSendList->SendCount + ); + voice->mixCoefficients = (float**) voice->audio->pMalloc( + sizeof(float*) * pSendList->SendCount + ); + voice->sendMix = (FAudioMixCallback*) voice->audio->pMalloc( + sizeof(FAudioMixCallback) * pSendList->SendCount + ); + + for (i = 0; i < pSendList->SendCount; i += 1) + { + if (pSendList->pSends[i].pOutputVoice->type == FAUDIO_VOICE_MASTER) + { + outChannels = pSendList->pSends[i].pOutputVoice->master.inputChannels; + } + else + { + outChannels = pSendList->pSends[i].pOutputVoice->mix.inputChannels; + } + voice->sendCoefficients[i] = (float*) voice->audio->pMalloc( + sizeof(float) * voice->outputChannels * outChannels + ); + voice->mixCoefficients[i] = (float*) voice->audio->pMalloc( + sizeof(float) * voice->outputChannels * outChannels + ); + + FAudio_assert(voice->outputChannels > 0 && voice->outputChannels < 9); + FAudio_assert(outChannels > 0 && outChannels < 9); + FAudio_memcpy( + voice->sendCoefficients[i], + FAUDIO_INTERNAL_MATRIX_DEFAULTS[voice->outputChannels - 1][outChannels - 1], + voice->outputChannels * outChannels * sizeof(float) + ); + FAudio_RecalcMixMatrix(voice, i); + + if (voice->outputChannels == 1) + { + if (outChannels == 1) + { + voice->sendMix[i] = FAudio_INTERNAL_Mix_1in_1out_Scalar; + } + else if (outChannels == 2) + { + voice->sendMix[i] = FAudio_INTERNAL_Mix_1in_2out_Scalar; + } + else if (outChannels == 6) + { + voice->sendMix[i] = FAudio_INTERNAL_Mix_1in_6out_Scalar; + } + else if (outChannels == 8) + { + voice->sendMix[i] = FAudio_INTERNAL_Mix_1in_8out_Scalar; + } + else + { + voice->sendMix[i] = FAudio_INTERNAL_Mix_Generic; + } + } + else if (voice->outputChannels == 2) + { + if (outChannels == 1) + { + voice->sendMix[i] = FAudio_INTERNAL_Mix_2in_1out_Scalar; + } + else if (outChannels == 2) + { + voice->sendMix[i] = FAudio_INTERNAL_Mix_2in_2out_Scalar; + } + else if (outChannels == 6) + { + voice->sendMix[i] = FAudio_INTERNAL_Mix_2in_6out_Scalar; + } + else if (outChannels == 8) + { + voice->sendMix[i] = FAudio_INTERNAL_Mix_2in_8out_Scalar; + } + else + { + voice->sendMix[i] = FAudio_INTERNAL_Mix_Generic; + } + } + else + { + voice->sendMix[i] = FAudio_INTERNAL_Mix_Generic; + } + + if (pSendList->pSends[i].Flags & FAUDIO_SEND_USEFILTER) + { + /* Allocate the whole send filter array if needed... */ + if (voice->sendFilter == NULL) + { + voice->sendFilter = (FAudioFilterParameters*) voice->audio->pMalloc( + sizeof(FAudioFilterParameters) * pSendList->SendCount + ); + } + if (voice->sendFilterState == NULL) + { + voice->sendFilterState = (FAudioFilterState**) voice->audio->pMalloc( + sizeof(FAudioFilterState*) * pSendList->SendCount + ); + FAudio_zero( + voice->sendFilterState, + sizeof(FAudioFilterState*) * pSendList->SendCount + ); + } + + /* ... then fill in this send's filter data */ + voice->sendFilter[i].Type = FAUDIO_DEFAULT_FILTER_TYPE; + voice->sendFilter[i].Frequency = FAUDIO_DEFAULT_FILTER_FREQUENCY; + voice->sendFilter[i].OneOverQ = FAUDIO_DEFAULT_FILTER_ONEOVERQ; + voice->sendFilterState[i] = (FAudioFilterState*) voice->audio->pMalloc( + sizeof(FAudioFilterState) * outChannels + ); + FAudio_zero( + voice->sendFilterState[i], + sizeof(FAudioFilterState) * outChannels + ); + } + } + + FAudio_PlatformUnlockMutex(voice->volumeLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->volumeLock) + + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + LOG_API_EXIT(voice->audio) + return 0; +} + +uint32_t FAudioVoice_SetEffectChain( + FAudioVoice *voice, + const FAudioEffectChain *pEffectChain +) { + uint32_t i; + FAPO *fapo; + uint32_t channelCount; + FAudioVoiceDetails voiceDetails; + FAPORegistrationProperties *pProps; + FAudioWaveFormatExtensible srcFmt, dstFmt; + FAPOLockForProcessBufferParameters srcLockParams, dstLockParams; + + LOG_API_ENTER(voice->audio) + + FAudioVoice_GetVoiceDetails(voice, &voiceDetails); + + /* SetEffectChain must not change the number of output channels once the voice has been created */ + if (pEffectChain == NULL && voice->outputChannels != 0) + { + /* cannot remove an effect chain that changes the number of channels */ + if (voice->outputChannels != voiceDetails.InputChannels) + { + LOG_ERROR( + voice->audio, + "%s", + "Cannot remove effect chain that changes the number of channels" + ) + FAudio_assert(0 && "Cannot remove effect chain that changes the number of channels"); + LOG_API_EXIT(voice->audio) + return FAUDIO_E_INVALID_CALL; + } + } + + if (pEffectChain != NULL && voice->outputChannels != 0) + { + uint32_t lst = pEffectChain->EffectCount - 1; + + /* new effect chain must have same number of output channels */ + if (voice->outputChannels != pEffectChain->pEffectDescriptors[lst].OutputChannels) + { + LOG_ERROR( + voice->audio, + "%s", + "New effect chain must have same number of output channels as the old chain" + ) + FAudio_assert(0 && "New effect chain must have same number of output channels as the old chain"); + LOG_API_EXIT(voice->audio) + return FAUDIO_E_INVALID_CALL; + } + } + + FAudio_PlatformLockMutex(voice->effectLock); + LOG_MUTEX_LOCK(voice->audio, voice->effectLock) + + if (pEffectChain == NULL) + { + FAudio_INTERNAL_FreeEffectChain(voice); + FAudio_zero(&voice->effects, sizeof(voice->effects)); + voice->outputChannels = voiceDetails.InputChannels; + } + else + { + /* Validate incoming chain before changing the current chain */ + + /* These are always the same, so just write them now. */ + srcLockParams.pFormat = &srcFmt.Format; + dstLockParams.pFormat = &dstFmt.Format; + if (voice->type == FAUDIO_VOICE_SOURCE) + { + srcLockParams.MaxFrameCount = voice->src.resampleSamples; + dstLockParams.MaxFrameCount = voice->src.resampleSamples; + } + else if (voice->type == FAUDIO_VOICE_SUBMIX) + { + srcLockParams.MaxFrameCount = voice->mix.outputSamples; + dstLockParams.MaxFrameCount = voice->mix.outputSamples; + } + else if (voice->type == FAUDIO_VOICE_MASTER) + { + srcLockParams.MaxFrameCount = voice->audio->updateSize; + dstLockParams.MaxFrameCount = voice->audio->updateSize; + } + + /* The first source is the voice input data... */ + srcFmt.Format.wBitsPerSample = 32; + srcFmt.Format.wFormatTag = FAUDIO_FORMAT_EXTENSIBLE; + srcFmt.Format.nChannels = voiceDetails.InputChannels; + srcFmt.Format.nSamplesPerSec = voiceDetails.InputSampleRate; + srcFmt.Format.nBlockAlign = srcFmt.Format.nChannels * (srcFmt.Format.wBitsPerSample / 8); + srcFmt.Format.nAvgBytesPerSec = srcFmt.Format.nSamplesPerSec * srcFmt.Format.nBlockAlign; + srcFmt.Format.cbSize = sizeof(FAudioWaveFormatExtensible) - sizeof(FAudioWaveFormatEx); + srcFmt.Samples.wValidBitsPerSample = srcFmt.Format.wBitsPerSample; + srcFmt.dwChannelMask = 0; + FAudio_memcpy(&srcFmt.SubFormat, &DATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(FAudioGUID)); + FAudio_memcpy(&dstFmt, &srcFmt, sizeof(srcFmt)); + + for (i = 0; i < pEffectChain->EffectCount; i += 1) + { + fapo = pEffectChain->pEffectDescriptors[i].pEffect; + + /* ... then we get this effect's format... */ + dstFmt.Format.nChannels = pEffectChain->pEffectDescriptors[i].OutputChannels; + dstFmt.Format.nBlockAlign = dstFmt.Format.nChannels * (dstFmt.Format.wBitsPerSample / 8); + dstFmt.Format.nAvgBytesPerSec = dstFmt.Format.nSamplesPerSec * dstFmt.Format.nBlockAlign; + + /* FIXME: This error needs to be found _before_ we start + * shredding the voice's state. This function is highly + * destructive so any errors need to be found at the + * beginning, not in the middle! We can't undo this! + * -flibit + */ + if (fapo->LockForProcess(fapo, 1, &srcLockParams, 1, &dstLockParams)) + { + LOG_ERROR( + voice->audio, + "%s", + "Effect output format not supported" + ) + FAudio_assert(0 && "Effect output format not supported"); + FAudio_PlatformUnlockMutex(voice->effectLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock) + LOG_API_EXIT(voice->audio) + return FAUDIO_E_UNSUPPORTED_FORMAT; + } + + /* Okay, now this effect is the source and the next + * effect will be the destination. Repeat until no + * effects left. + */ + FAudio_memcpy(&srcFmt, &dstFmt, sizeof(srcFmt)); + } + + FAudio_INTERNAL_FreeEffectChain(voice); + FAudio_INTERNAL_AllocEffectChain( + voice, + pEffectChain + ); + + /* check if in-place processing is supported */ + channelCount = voiceDetails.InputChannels; + for (i = 0; i < voice->effects.count; i += 1) + { + fapo = voice->effects.desc[i].pEffect; + if (fapo->GetRegistrationProperties(fapo, &pProps) == 0) + { + voice->effects.inPlaceProcessing[i] = (pProps->Flags & FAPO_FLAG_INPLACE_SUPPORTED) == FAPO_FLAG_INPLACE_SUPPORTED; + voice->effects.inPlaceProcessing[i] &= (channelCount == voice->effects.desc[i].OutputChannels); + channelCount = voice->effects.desc[i].OutputChannels; + + /* Fails if in-place processing is mandatory and + * the chain forces us to do otherwise... + */ + FAudio_assert( + !(pProps->Flags & FAPO_FLAG_INPLACE_REQUIRED) || + voice->effects.inPlaceProcessing[i] + ); + + voice->audio->pFree(pProps); + } + } + voice->outputChannels = channelCount; + } + + FAudio_PlatformUnlockMutex(voice->effectLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock) + LOG_API_EXIT(voice->audio) + return 0; +} + +uint32_t FAudioVoice_EnableEffect( + FAudioVoice *voice, + uint32_t EffectIndex, + uint32_t OperationSet +) { + LOG_API_ENTER(voice->audio) + + if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active) + { + FAudio_OPERATIONSET_QueueEnableEffect( + voice, + EffectIndex, + OperationSet + ); + LOG_API_EXIT(voice->audio) + return 0; + } + + FAudio_PlatformLockMutex(voice->effectLock); + LOG_MUTEX_LOCK(voice->audio, voice->effectLock) + voice->effects.desc[EffectIndex].InitialState = 1; + FAudio_PlatformUnlockMutex(voice->effectLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock) + LOG_API_EXIT(voice->audio) + return 0; +} + +uint32_t FAudioVoice_DisableEffect( + FAudioVoice *voice, + uint32_t EffectIndex, + uint32_t OperationSet +) { + LOG_API_ENTER(voice->audio) + + if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active) + { + FAudio_OPERATIONSET_QueueDisableEffect( + voice, + EffectIndex, + OperationSet + ); + LOG_API_EXIT(voice->audio) + return 0; + } + + FAudio_PlatformLockMutex(voice->effectLock); + LOG_MUTEX_LOCK(voice->audio, voice->effectLock) + voice->effects.desc[EffectIndex].InitialState = 0; + FAudio_PlatformUnlockMutex(voice->effectLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock) + LOG_API_EXIT(voice->audio) + return 0; +} + +void FAudioVoice_GetEffectState( + FAudioVoice *voice, + uint32_t EffectIndex, + int32_t *pEnabled +) { + LOG_API_ENTER(voice->audio) + FAudio_PlatformLockMutex(voice->effectLock); + LOG_MUTEX_LOCK(voice->audio, voice->effectLock) + *pEnabled = voice->effects.desc[EffectIndex].InitialState; + FAudio_PlatformUnlockMutex(voice->effectLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock) + LOG_API_EXIT(voice->audio) +} + +uint32_t FAudioVoice_SetEffectParameters( + FAudioVoice *voice, + uint32_t EffectIndex, + const void *pParameters, + uint32_t ParametersByteSize, + uint32_t OperationSet +) { + LOG_API_ENTER(voice->audio) + + if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active) + { + FAudio_OPERATIONSET_QueueSetEffectParameters( + voice, + EffectIndex, + pParameters, + ParametersByteSize, + OperationSet + ); + LOG_API_EXIT(voice->audio) + return 0; + } + + if (voice->effects.parameters[EffectIndex] == NULL) + { + voice->effects.parameters[EffectIndex] = voice->audio->pMalloc( + ParametersByteSize + ); + voice->effects.parameterSizes[EffectIndex] = ParametersByteSize; + } + FAudio_PlatformLockMutex(voice->effectLock); + LOG_MUTEX_LOCK(voice->audio, voice->effectLock) + if (voice->effects.parameterSizes[EffectIndex] < ParametersByteSize) + { + voice->effects.parameters[EffectIndex] = voice->audio->pRealloc( + voice->effects.parameters[EffectIndex], + ParametersByteSize + ); + voice->effects.parameterSizes[EffectIndex] = ParametersByteSize; + } + FAudio_memcpy( + voice->effects.parameters[EffectIndex], + pParameters, + ParametersByteSize + ); + voice->effects.parameterUpdates[EffectIndex] = 1; + FAudio_PlatformUnlockMutex(voice->effectLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock) + LOG_API_EXIT(voice->audio) + return 0; +} + +uint32_t FAudioVoice_GetEffectParameters( + FAudioVoice *voice, + uint32_t EffectIndex, + void *pParameters, + uint32_t ParametersByteSize +) { + FAPO *fapo; + LOG_API_ENTER(voice->audio) + FAudio_PlatformLockMutex(voice->effectLock); + LOG_MUTEX_LOCK(voice->audio, voice->effectLock) + fapo = voice->effects.desc[EffectIndex].pEffect; + fapo->GetParameters(fapo, pParameters, ParametersByteSize); + FAudio_PlatformUnlockMutex(voice->effectLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock) + LOG_API_EXIT(voice->audio) + return 0; +} + +uint32_t FAudioVoice_SetFilterParameters( + FAudioVoice *voice, + const FAudioFilterParameters *pParameters, + uint32_t OperationSet +) { + LOG_API_ENTER(voice->audio) + + if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active) + { + FAudio_OPERATIONSET_QueueSetFilterParameters( + voice, + pParameters, + OperationSet + ); + LOG_API_EXIT(voice->audio) + return 0; + } + + /* MSDN: "This method is usable only on source and submix voices and + * has no effect on mastering voices." + */ + if (voice->type == FAUDIO_VOICE_MASTER) + { + LOG_API_EXIT(voice->audio) + return 0; + } + + if (!(voice->flags & FAUDIO_VOICE_USEFILTER)) + { + LOG_API_EXIT(voice->audio) + return 0; + } + + FAudio_PlatformLockMutex(voice->filterLock); + LOG_MUTEX_LOCK(voice->audio, voice->filterLock) + FAudio_memcpy( + &voice->filter, + pParameters, + sizeof(FAudioFilterParameters) + ); + FAudio_PlatformUnlockMutex(voice->filterLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->filterLock) + + LOG_API_EXIT(voice->audio) + return 0; +} + +void FAudioVoice_GetFilterParameters( + FAudioVoice *voice, + FAudioFilterParameters *pParameters +) { + LOG_API_ENTER(voice->audio) + + /* MSDN: "This method is usable only on source and submix voices and + * has no effect on mastering voices." + */ + if (voice->type == FAUDIO_VOICE_MASTER) + { + LOG_API_EXIT(voice->audio) + return; + } + + if (!(voice->flags & FAUDIO_VOICE_USEFILTER)) + { + LOG_API_EXIT(voice->audio) + return; + } + + FAudio_PlatformLockMutex(voice->filterLock); + LOG_MUTEX_LOCK(voice->audio, voice->filterLock) + FAudio_memcpy( + pParameters, + &voice->filter, + sizeof(FAudioFilterParameters) + ); + FAudio_PlatformUnlockMutex(voice->filterLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->filterLock) + LOG_API_EXIT(voice->audio) +} + +uint32_t FAudioVoice_SetOutputFilterParameters( + FAudioVoice *voice, + FAudioVoice *pDestinationVoice, + const FAudioFilterParameters *pParameters, + uint32_t OperationSet +) { + uint32_t i; + LOG_API_ENTER(voice->audio) + + if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active) + { + FAudio_OPERATIONSET_QueueSetOutputFilterParameters( + voice, + pDestinationVoice, + pParameters, + OperationSet + ); + LOG_API_EXIT(voice->audio) + return 0; + } + + /* MSDN: "This method is usable only on source and submix voices and + * has no effect on mastering voices." + */ + if (voice->type == FAUDIO_VOICE_MASTER) + { + LOG_API_EXIT(voice->audio) + return 0; + } + + FAudio_PlatformLockMutex(voice->sendLock); + LOG_MUTEX_LOCK(voice->audio, voice->sendLock) + + /* Find the send index */ + if (pDestinationVoice == NULL && voice->sends.SendCount == 1) + { + pDestinationVoice = voice->sends.pSends[0].pOutputVoice; + } + for (i = 0; i < voice->sends.SendCount; i += 1) + { + if (pDestinationVoice == voice->sends.pSends[i].pOutputVoice) + { + break; + } + } + if (i >= voice->sends.SendCount) + { + LOG_ERROR( + voice->audio, + "Destination not attached to source: %p %p", + (void*) voice, + (void*) pDestinationVoice + ) + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + LOG_API_EXIT(voice->audio) + return FAUDIO_E_INVALID_CALL; + } + + if (!(voice->sends.pSends[i].Flags & FAUDIO_SEND_USEFILTER)) + { + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + LOG_API_EXIT(voice->audio) + return 0; + } + + /* Set the filter parameters, finally. */ + FAudio_memcpy( + &voice->sendFilter[i], + pParameters, + sizeof(FAudioFilterParameters) + ); + + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + LOG_API_EXIT(voice->audio) + return 0; +} + +void FAudioVoice_GetOutputFilterParameters( + FAudioVoice *voice, + FAudioVoice *pDestinationVoice, + FAudioFilterParameters *pParameters +) { + uint32_t i; + + LOG_API_ENTER(voice->audio) + + /* MSDN: "This method is usable only on source and submix voices and + * has no effect on mastering voices." + */ + if (voice->type == FAUDIO_VOICE_MASTER) + { + LOG_API_EXIT(voice->audio) + return; + } + + FAudio_PlatformLockMutex(voice->sendLock); + LOG_MUTEX_LOCK(voice->audio, voice->sendLock) + + /* Find the send index */ + if (pDestinationVoice == NULL && voice->sends.SendCount == 1) + { + pDestinationVoice = voice->sends.pSends[0].pOutputVoice; + } + for (i = 0; i < voice->sends.SendCount; i += 1) + { + if (pDestinationVoice == voice->sends.pSends[i].pOutputVoice) + { + break; + } + } + if (i >= voice->sends.SendCount) + { + LOG_ERROR( + voice->audio, + "Destination not attached to source: %p %p", + (void*) voice, + (void*) pDestinationVoice + ) + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + LOG_API_EXIT(voice->audio) + return; + } + + if (!(voice->sends.pSends[i].Flags & FAUDIO_SEND_USEFILTER)) + { + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + LOG_API_EXIT(voice->audio) + return; + } + + /* Set the filter parameters, finally. */ + FAudio_memcpy( + pParameters, + &voice->sendFilter[i], + sizeof(FAudioFilterParameters) + ); + + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + LOG_API_EXIT(voice->audio) +} + +uint32_t FAudioVoice_SetVolume( + FAudioVoice *voice, + float Volume, + uint32_t OperationSet +) { + uint32_t i; + + LOG_API_ENTER(voice->audio) + + if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active) + { + FAudio_OPERATIONSET_QueueSetVolume( + voice, + Volume, + OperationSet + ); + LOG_API_EXIT(voice->audio) + return 0; + } + + FAudio_PlatformLockMutex(voice->sendLock); + LOG_MUTEX_LOCK(voice->audio, voice->sendLock) + + FAudio_PlatformLockMutex(voice->volumeLock); + LOG_MUTEX_LOCK(voice->audio, voice->volumeLock) + + voice->volume = FAudio_clamp( + Volume, + -FAUDIO_MAX_VOLUME_LEVEL, + FAUDIO_MAX_VOLUME_LEVEL + ); + + for (i = 0; i < voice->sends.SendCount; i += 1) + { + FAudio_RecalcMixMatrix(voice, i); + } + + FAudio_PlatformUnlockMutex(voice->volumeLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->volumeLock) + + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + + LOG_API_EXIT(voice->audio) + return 0; +} + +void FAudioVoice_GetVolume( + FAudioVoice *voice, + float *pVolume +) { + LOG_API_ENTER(voice->audio) + *pVolume = voice->volume; + LOG_API_EXIT(voice->audio) +} + +uint32_t FAudioVoice_SetChannelVolumes( + FAudioVoice *voice, + uint32_t Channels, + const float *pVolumes, + uint32_t OperationSet +) { + uint32_t i; + + LOG_API_ENTER(voice->audio) + + if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active) + { + FAudio_OPERATIONSET_QueueSetChannelVolumes( + voice, + Channels, + pVolumes, + OperationSet + ); + LOG_API_EXIT(voice->audio) + return 0; + } + + if (pVolumes == NULL) + { + LOG_API_EXIT(voice->audio) + return FAUDIO_E_INVALID_CALL; + } + + if (voice->type == FAUDIO_VOICE_MASTER) + { + LOG_API_EXIT(voice->audio) + return FAUDIO_E_INVALID_CALL; + } + + if (voice->audio->version > 7 && Channels != voice->outputChannels) + { + LOG_API_EXIT(voice->audio) + return FAUDIO_E_INVALID_CALL; + } + + FAudio_PlatformLockMutex(voice->sendLock); + LOG_MUTEX_LOCK(voice->audio, voice->sendLock) + + FAudio_PlatformLockMutex(voice->volumeLock); + LOG_MUTEX_LOCK(voice->audio, voice->volumeLock) + + FAudio_memcpy( + voice->channelVolume, + pVolumes, + sizeof(float) * Channels + ); + + for (i = 0; i < voice->sends.SendCount; i += 1) + { + FAudio_RecalcMixMatrix(voice, i); + } + + FAudio_PlatformUnlockMutex(voice->volumeLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->volumeLock) + + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + + LOG_API_EXIT(voice->audio) + return 0; +} + +void FAudioVoice_GetChannelVolumes( + FAudioVoice *voice, + uint32_t Channels, + float *pVolumes +) { + LOG_API_ENTER(voice->audio) + FAudio_PlatformLockMutex(voice->volumeLock); + LOG_MUTEX_LOCK(voice->audio, voice->volumeLock) + FAudio_memcpy( + pVolumes, + voice->channelVolume, + sizeof(float) * Channels + ); + FAudio_PlatformUnlockMutex(voice->volumeLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->volumeLock) + LOG_API_EXIT(voice->audio) +} + +uint32_t FAudioVoice_SetOutputMatrix( + FAudioVoice *voice, + FAudioVoice *pDestinationVoice, + uint32_t SourceChannels, + uint32_t DestinationChannels, + const float *pLevelMatrix, + uint32_t OperationSet +) { + uint32_t i, result = 0; + LOG_API_ENTER(voice->audio) + + if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active) + { + FAudio_OPERATIONSET_QueueSetOutputMatrix( + voice, + pDestinationVoice, + SourceChannels, + DestinationChannels, + pLevelMatrix, + OperationSet + ); + LOG_API_EXIT(voice->audio) + return 0; + } + + FAudio_PlatformLockMutex(voice->sendLock); + LOG_MUTEX_LOCK(voice->audio, voice->sendLock) + + /* Find the send index */ + if (pDestinationVoice == NULL && voice->sends.SendCount == 1) + { + pDestinationVoice = voice->sends.pSends[0].pOutputVoice; + } + FAudio_assert(pDestinationVoice != NULL); + for (i = 0; i < voice->sends.SendCount; i += 1) + { + if (pDestinationVoice == voice->sends.pSends[i].pOutputVoice) + { + break; + } + } + if (i >= voice->sends.SendCount) + { + LOG_ERROR( + voice->audio, + "Destination not attached to source: %p %p", + (void*) voice, + (void*) pDestinationVoice + ) + result = FAUDIO_E_INVALID_CALL; + goto end; + } + + /* Verify the Source/Destination channel count */ + if (SourceChannels != voice->outputChannels) + { + LOG_ERROR( + voice->audio, + "SourceChannels not equal to voice channel count: %p %d %d", + (void*) voice, + SourceChannels, + voice->outputChannels + ) + result = FAUDIO_E_INVALID_CALL; + goto end; + } + + if (pDestinationVoice->type == FAUDIO_VOICE_MASTER) + { + if (DestinationChannels != pDestinationVoice->master.inputChannels) + { + LOG_ERROR( + voice->audio, + "DestinationChannels not equal to master channel count: %p %d %d", + (void*) pDestinationVoice, + DestinationChannels, + pDestinationVoice->master.inputChannels + ) + result = FAUDIO_E_INVALID_CALL; + goto end; + } + } + else + { + if (DestinationChannels != pDestinationVoice->mix.inputChannels) + { + LOG_ERROR( + voice->audio, + "DestinationChannels not equal to submix channel count: %p %d %d", + (void*) pDestinationVoice, + DestinationChannels, + pDestinationVoice->mix.inputChannels + ) + result = FAUDIO_E_INVALID_CALL; + goto end; + } + } + + /* Set the matrix values, finally */ + FAudio_PlatformLockMutex(voice->volumeLock); + LOG_MUTEX_LOCK(voice->audio, voice->volumeLock) + + FAudio_memcpy( + voice->sendCoefficients[i], + pLevelMatrix, + sizeof(float) * SourceChannels * DestinationChannels + ); + + FAudio_RecalcMixMatrix(voice, i); + + FAudio_PlatformUnlockMutex(voice->volumeLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->volumeLock) + +end: + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + LOG_API_EXIT(voice->audio) + return result; +} + +void FAudioVoice_GetOutputMatrix( + FAudioVoice *voice, + FAudioVoice *pDestinationVoice, + uint32_t SourceChannels, + uint32_t DestinationChannels, + float *pLevelMatrix +) { + uint32_t i; + + LOG_API_ENTER(voice->audio) + FAudio_PlatformLockMutex(voice->sendLock); + LOG_MUTEX_LOCK(voice->audio, voice->sendLock) + + /* Find the send index */ + for (i = 0; i < voice->sends.SendCount; i += 1) + { + if (pDestinationVoice == voice->sends.pSends[i].pOutputVoice) + { + break; + } + } + if (i >= voice->sends.SendCount) + { + LOG_ERROR( + voice->audio, + "Destination not attached to source: %p %p", + (void*) voice, + (void*) pDestinationVoice + ) + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + LOG_API_EXIT(voice->audio) + return; + } + + /* Verify the Source/Destination channel count */ + if (voice->type == FAUDIO_VOICE_SOURCE) + { + FAudio_assert(SourceChannels == voice->src.format->nChannels); + } + else + { + FAudio_assert(SourceChannels == voice->mix.inputChannels); + } + if (pDestinationVoice->type == FAUDIO_VOICE_MASTER) + { + FAudio_assert(DestinationChannels == pDestinationVoice->master.inputChannels); + } + else + { + FAudio_assert(DestinationChannels == pDestinationVoice->mix.inputChannels); + } + + /* Get the matrix values, finally */ + FAudio_memcpy( + pLevelMatrix, + voice->sendCoefficients[i], + sizeof(float) * SourceChannels * DestinationChannels + ); + + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + LOG_API_EXIT(voice->audio) +} + +void FAudioVoice_DestroyVoice(FAudioVoice *voice) +{ + uint32_t i; + LOG_API_ENTER(voice->audio) + + /* TODO: Check for dependencies and remove from audio graph first! */ + FAudio_OPERATIONSET_ClearAllForVoice(voice); + + if (voice->type == FAUDIO_VOICE_SOURCE) + { + FAudioBufferEntry *entry, *next; + +#ifdef FAUDIO_DUMP_VOICES + FAudio_DUMPVOICE_Finalize((FAudioSourceVoice*) voice); +#endif /* FAUDIO_DUMP_VOICES */ + + FAudio_PlatformLockMutex(voice->audio->sourceLock); + LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock) + while (voice == voice->audio->processingSource) + { + FAudio_PlatformUnlockMutex(voice->audio->sourceLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock) + FAudio_PlatformLockMutex(voice->audio->sourceLock); + LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock) + } + LinkedList_RemoveEntry( + &voice->audio->sources, + voice, + voice->audio->sourceLock, + voice->audio->pFree + ); + FAudio_PlatformUnlockMutex(voice->audio->sourceLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock) + + entry = voice->src.bufferList; + while (entry != NULL) + { + next = entry->next; + voice->audio->pFree(entry); + entry = next; + } + + entry = voice->src.flushList; + while (entry != NULL) + { + next = entry->next; + voice->audio->pFree(entry); + entry = next; + } + + voice->audio->pFree(voice->src.format); + LOG_MUTEX_DESTROY(voice->audio, voice->src.bufferLock) + FAudio_PlatformDestroyMutex(voice->src.bufferLock); +#ifdef HAVE_WMADEC + if (voice->src.wmadec) + { + FAudio_WMADEC_free(voice); + } +#endif /* HAVE_WMADEC */ + } + else if (voice->type == FAUDIO_VOICE_SUBMIX) + { + /* Remove submix from list */ + LinkedList_RemoveEntry( + &voice->audio->submixes, + voice, + voice->audio->submixLock, + voice->audio->pFree + ); + + /* Delete submix data */ + voice->audio->pFree(voice->mix.inputCache); + } + else if (voice->type == FAUDIO_VOICE_MASTER) + { + if (voice->audio->platform != NULL) + { + FAudio_PlatformQuit(voice->audio->platform); + voice->audio->platform = NULL; + } + if (voice->master.effectCache != NULL) + { + voice->audio->pFree(voice->master.effectCache); + } + voice->audio->master = NULL; + } + + if (voice->sendLock != NULL) + { + FAudio_PlatformLockMutex(voice->sendLock); + LOG_MUTEX_LOCK(voice->audio, voice->sendLock) + for (i = 0; i < voice->sends.SendCount; i += 1) + { + voice->audio->pFree(voice->sendCoefficients[i]); + } + if (voice->sendCoefficients != NULL) + { + voice->audio->pFree(voice->sendCoefficients); + } + for (i = 0; i < voice->sends.SendCount; i += 1) + { + voice->audio->pFree(voice->mixCoefficients[i]); + } + if (voice->mixCoefficients != NULL) + { + voice->audio->pFree(voice->mixCoefficients); + } + if (voice->sendMix != NULL) + { + voice->audio->pFree(voice->sendMix); + } + if (voice->sendFilter != NULL) + { + voice->audio->pFree(voice->sendFilter); + } + if (voice->sendFilterState != NULL) + { + for (i = 0; i < voice->sends.SendCount; i += 1) + { + if (voice->sendFilterState[i] != NULL) + { + voice->audio->pFree(voice->sendFilterState[i]); + } + } + voice->audio->pFree(voice->sendFilterState); + } + if (voice->sends.pSends != NULL) + { + voice->audio->pFree(voice->sends.pSends); + } + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + LOG_MUTEX_DESTROY(voice->audio, voice->sendLock) + FAudio_PlatformDestroyMutex(voice->sendLock); + } + + if (voice->effectLock != NULL) + { + FAudio_PlatformLockMutex(voice->effectLock); + LOG_MUTEX_LOCK(voice->audio, voice->effectLock) + FAudio_INTERNAL_FreeEffectChain(voice); + FAudio_PlatformUnlockMutex(voice->effectLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock) + LOG_MUTEX_DESTROY(voice->audio, voice->effectLock) + FAudio_PlatformDestroyMutex(voice->effectLock); + } + + if (voice->filterLock != NULL) + { + FAudio_PlatformLockMutex(voice->filterLock); + LOG_MUTEX_LOCK(voice->audio, voice->filterLock) + if (voice->filterState != NULL) + { + voice->audio->pFree(voice->filterState); + } + FAudio_PlatformUnlockMutex(voice->filterLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->filterLock) + LOG_MUTEX_DESTROY(voice->audio, voice->filterLock) + FAudio_PlatformDestroyMutex(voice->filterLock); + } + + if (voice->volumeLock != NULL) + { + FAudio_PlatformLockMutex(voice->volumeLock); + LOG_MUTEX_LOCK(voice->audio, voice->volumeLock) + if (voice->channelVolume != NULL) + { + voice->audio->pFree(voice->channelVolume); + } + FAudio_PlatformUnlockMutex(voice->volumeLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->volumeLock) + LOG_MUTEX_DESTROY(voice->audio, voice->volumeLock) + FAudio_PlatformDestroyMutex(voice->volumeLock); + } + + LOG_API_EXIT(voice->audio) + FAudio_Release(voice->audio); + voice->audio->pFree(voice); +} + +/* FAudioSourceVoice Interface */ + +uint32_t FAudioSourceVoice_Start( + FAudioSourceVoice *voice, + uint32_t Flags, + uint32_t OperationSet +) { + LOG_API_ENTER(voice->audio) + + if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active) + { + FAudio_OPERATIONSET_QueueStart( + voice, + Flags, + OperationSet + ); + LOG_API_EXIT(voice->audio) + return 0; + } + + + FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE); + + FAudio_assert(Flags == 0); + voice->src.active = 1; + LOG_API_EXIT(voice->audio) + return 0; +} + +uint32_t FAudioSourceVoice_Stop( + FAudioSourceVoice *voice, + uint32_t Flags, + uint32_t OperationSet +) { + LOG_API_ENTER(voice->audio) + + if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active) + { + FAudio_OPERATIONSET_QueueStop( + voice, + Flags, + OperationSet + ); + LOG_API_EXIT(voice->audio) + return 0; + } + + FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE); + + if (Flags & FAUDIO_PLAY_TAILS) + { + voice->src.active = 2; + } + else + { + voice->src.active = 0; + } + LOG_API_EXIT(voice->audio) + return 0; +} + +uint32_t FAudioSourceVoice_SubmitSourceBuffer( + FAudioSourceVoice *voice, + const FAudioBuffer *pBuffer, + const FAudioBufferWMA *pBufferWMA +) { + uint32_t adpcmMask, *adpcmByteCount; + uint32_t playBegin, playLength, loopBegin, loopLength; + FAudioBufferEntry *entry, *list; + + LOG_API_ENTER(voice->audio) + LOG_INFO( + voice->audio, + "%p: {Flags: 0x%x, AudioBytes: %u, pAudioData: %p, Play: %u + %u, Loop: %u + %u x %u}", + (void*) voice, + pBuffer->Flags, + pBuffer->AudioBytes, + (const void*) pBuffer->pAudioData, + pBuffer->PlayBegin, + pBuffer->PlayLength, + pBuffer->LoopBegin, + pBuffer->LoopLength, + pBuffer->LoopCount + ) + + FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE); +#ifdef HAVE_WMADEC + FAudio_assert( (voice->src.wmadec != NULL && (pBufferWMA != NULL || voice->src.format->wFormatTag == FAUDIO_FORMAT_XMAUDIO2)) || + (voice->src.wmadec == NULL && (pBufferWMA == NULL && voice->src.format->wFormatTag != FAUDIO_FORMAT_XMAUDIO2)) ); +#endif /* HAVE_WMADEC */ + + /* Start off with whatever they just sent us... */ + playBegin = pBuffer->PlayBegin; + playLength = pBuffer->PlayLength; + loopBegin = pBuffer->LoopBegin; + loopLength = pBuffer->LoopLength; + + /* "LoopBegin/LoopLength must be zero if LoopCount is 0" */ + if (pBuffer->LoopCount == 0 && (loopBegin > 0 || loopLength > 0)) + { + LOG_API_EXIT(voice->audio) + return FAUDIO_E_INVALID_CALL; + } + + /* PlayLength Default */ + if (playLength == 0) + { + if (voice->src.format->wFormatTag == FAUDIO_FORMAT_MSADPCM) + { + FAudioADPCMWaveFormat *fmtex = (FAudioADPCMWaveFormat*) voice->src.format; + playLength = ( + pBuffer->AudioBytes / + fmtex->wfx.nBlockAlign * + fmtex->wSamplesPerBlock + ) - playBegin; + } + else if (voice->src.format->wFormatTag == FAUDIO_FORMAT_XMAUDIO2) + { + FAudioXMA2WaveFormat *fmtex = (FAudioXMA2WaveFormat*) voice->src.format; + playLength = fmtex->dwSamplesEncoded - playBegin; + } + else if (pBufferWMA != NULL) + { + playLength = ( + pBufferWMA->pDecodedPacketCumulativeBytes[pBufferWMA->PacketCount - 1] / + (voice->src.format->nChannels * voice->src.format->wBitsPerSample / 8) + ) - playBegin; + } + else + { + playLength = ( + pBuffer->AudioBytes / + voice->src.format->nBlockAlign + ) - playBegin; + } + } + + if (pBuffer->LoopCount > 0 && pBufferWMA == NULL && voice->src.format->wFormatTag != FAUDIO_FORMAT_XMAUDIO2) + { + /* "The value of LoopBegin must be less than PlayBegin + PlayLength" */ + if (loopBegin >= (playBegin + playLength)) + { + LOG_API_EXIT(voice->audio) + return FAUDIO_E_INVALID_CALL; + } + + /* LoopLength Default */ + if (loopLength == 0) + { + loopLength = playBegin + playLength - loopBegin; + } + + /* "The value of LoopBegin + LoopLength must be greater than PlayBegin + * and less than PlayBegin + PlayLength" + */ + if ( voice->audio->version > 7 && ( + (loopBegin + loopLength) <= playBegin || + (loopBegin + loopLength) > (playBegin + playLength)) ) + { + LOG_API_EXIT(voice->audio) + return FAUDIO_E_INVALID_CALL; + } + } + + /* For ADPCM, round down to the nearest sample block size */ + if (voice->src.format->wFormatTag == FAUDIO_FORMAT_MSADPCM) + { + adpcmMask = ((FAudioADPCMWaveFormat*) voice->src.format)->wSamplesPerBlock; + playBegin -= playBegin % adpcmMask; + playLength -= playLength % adpcmMask; + loopBegin -= loopBegin % adpcmMask; + loopLength -= loopLength % adpcmMask; + + /* This is basically a const_cast... */ + adpcmByteCount = (uint32_t*) &pBuffer->AudioBytes; + *adpcmByteCount = ( + pBuffer->AudioBytes / voice->src.format->nBlockAlign + ) * voice->src.format->nBlockAlign; + } + else if (pBufferWMA != NULL || voice->src.format->wFormatTag == FAUDIO_FORMAT_XMAUDIO2) + { + /* WMA only supports looping the whole buffer */ + loopBegin = 0; + loopLength = playBegin + playLength; + } + + /* Allocate, now that we have valid input */ + entry = (FAudioBufferEntry*) voice->audio->pMalloc(sizeof(FAudioBufferEntry)); + FAudio_memcpy(&entry->buffer, pBuffer, sizeof(FAudioBuffer)); + entry->buffer.PlayBegin = playBegin; + entry->buffer.PlayLength = playLength; + entry->buffer.LoopBegin = loopBegin; + entry->buffer.LoopLength = loopLength; + if (pBufferWMA != NULL) + { + FAudio_memcpy(&entry->bufferWMA, pBufferWMA, sizeof(FAudioBufferWMA)); + } + entry->next = NULL; + + if ( voice->audio->version <= 7 && ( + entry->buffer.LoopCount > 0 && + entry->buffer.LoopBegin + entry->buffer.LoopLength <= entry->buffer.PlayBegin)) + { + entry->buffer.LoopCount = 0; + } + +#ifdef FAUDIO_DUMP_VOICES + /* dumping current buffer, append into "data" section */ + if (pBuffer->pAudioData != NULL && playLength > 0) + { + FAudio_DUMPVOICE_WriteBuffer(voice, pBuffer, pBufferWMA, playBegin, playLength); + } +#endif /* FAUDIO_DUMP_VOICES */ + + /* Submit! */ + FAudio_PlatformLockMutex(voice->src.bufferLock); + LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock) + if (voice->src.bufferList == NULL) + { + voice->src.bufferList = entry; + voice->src.curBufferOffset = entry->buffer.PlayBegin; + voice->src.newBuffer = 1; + } + else + { + list = voice->src.bufferList; + while (list->next != NULL) + { + list = list->next; + } + list->next = entry; + + /* For some bizarre reason we get scenarios where a buffer is freed, only to + * have the allocator give us the exact same address and somehow get a single + * buffer referencing itself. I don't even know. + */ + FAudio_assert(list != entry); + } + LOG_INFO( + voice->audio, + "%p: appended buffer %p", + (void*) voice, + (void*) &entry->buffer + ) + FAudio_PlatformUnlockMutex(voice->src.bufferLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock) + LOG_API_EXIT(voice->audio) + return 0; +} + +uint32_t FAudioSourceVoice_FlushSourceBuffers( + FAudioSourceVoice *voice +) { + FAudioBufferEntry *entry, *latest; + + LOG_API_ENTER(voice->audio) + FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE); + + FAudio_PlatformLockMutex(voice->src.bufferLock); + LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock) + + /* If the source is playing, don't flush the active buffer */ + entry = voice->src.bufferList; + if ((voice->src.active == 1) && entry != NULL && !voice->src.newBuffer) + { + entry = entry->next; + voice->src.bufferList->next = NULL; + } + else + { + voice->src.curBufferOffset = 0; + voice->src.bufferList = NULL; + voice->src.newBuffer = 0; + } + + /* Move them to the pending flush list */ + if (entry != NULL) + { + if (voice->src.flushList == NULL) + { + voice->src.flushList = entry; + } + else + { + latest = voice->src.flushList; + while (latest->next != NULL) + { + latest = latest->next; + } + latest->next = entry; + } + } + + FAudio_PlatformUnlockMutex(voice->src.bufferLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock) + LOG_API_EXIT(voice->audio) + return 0; +} + +uint32_t FAudioSourceVoice_Discontinuity( + FAudioSourceVoice *voice +) { + FAudioBufferEntry *buf; + + LOG_API_ENTER(voice->audio) + FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE); + + FAudio_PlatformLockMutex(voice->src.bufferLock); + LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock) + + if (voice->src.bufferList != NULL) + { + for (buf = voice->src.bufferList; buf->next != NULL; buf = buf->next); + buf->buffer.Flags |= FAUDIO_END_OF_STREAM; + } + + FAudio_PlatformUnlockMutex(voice->src.bufferLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock) + LOG_API_EXIT(voice->audio) + return 0; +} + +uint32_t FAudioSourceVoice_ExitLoop( + FAudioSourceVoice *voice, + uint32_t OperationSet +) { + LOG_API_ENTER(voice->audio) + + if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active) + { + FAudio_OPERATIONSET_QueueExitLoop( + voice, + OperationSet + ); + LOG_API_EXIT(voice->audio) + return 0; + } + + FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE); + + FAudio_PlatformLockMutex(voice->src.bufferLock); + LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock) + + if (voice->src.bufferList != NULL) + { + voice->src.bufferList->buffer.LoopCount = 0; + } + + FAudio_PlatformUnlockMutex(voice->src.bufferLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock) + LOG_API_EXIT(voice->audio) + return 0; +} + +void FAudioSourceVoice_GetState( + FAudioSourceVoice *voice, + FAudioVoiceState *pVoiceState, + uint32_t Flags +) { + FAudioBufferEntry *entry; + + LOG_API_ENTER(voice->audio) + FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE); + + FAudio_PlatformLockMutex(voice->src.bufferLock); + LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock) + + if (!(Flags & FAUDIO_VOICE_NOSAMPLESPLAYED)) + { + pVoiceState->SamplesPlayed = voice->src.totalSamples; + } + + pVoiceState->BuffersQueued = 0; + pVoiceState->pCurrentBufferContext = NULL; + if (voice->src.bufferList != NULL) + { + entry = voice->src.bufferList; + if (!voice->src.newBuffer) + { + pVoiceState->pCurrentBufferContext = entry->buffer.pContext; + } + do + { + pVoiceState->BuffersQueued += 1; + entry = entry->next; + } while (entry != NULL); + } + + /* Pending flushed buffers also count */ + entry = voice->src.flushList; + while (entry != NULL) + { + pVoiceState->BuffersQueued += 1; + entry = entry->next; + } + + LOG_INFO( + voice->audio, + "-> {pCurrentBufferContext: %p, BuffersQueued: %u, SamplesPlayed: %"FAudio_PRIu64"}", + pVoiceState->pCurrentBufferContext, pVoiceState->BuffersQueued, + pVoiceState->SamplesPlayed + ) + + FAudio_PlatformUnlockMutex(voice->src.bufferLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock) + LOG_API_EXIT(voice->audio) +} + +uint32_t FAudioSourceVoice_SetFrequencyRatio( + FAudioSourceVoice *voice, + float Ratio, + uint32_t OperationSet +) { + LOG_API_ENTER(voice->audio) + + if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active) + { + FAudio_OPERATIONSET_QueueSetFrequencyRatio( + voice, + Ratio, + OperationSet + ); + LOG_API_EXIT(voice->audio) + return 0; + } + FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE); + + if (voice->flags & FAUDIO_VOICE_NOPITCH) + { + LOG_API_EXIT(voice->audio) + return 0; + } + + voice->src.freqRatio = FAudio_clamp( + Ratio, + FAUDIO_MIN_FREQ_RATIO, + voice->src.maxFreqRatio + ); + LOG_API_EXIT(voice->audio) + return 0; +} + +void FAudioSourceVoice_GetFrequencyRatio( + FAudioSourceVoice *voice, + float *pRatio +) { + LOG_API_ENTER(voice->audio) + FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE); + + *pRatio = voice->src.freqRatio; + LOG_API_EXIT(voice->audio) +} + +uint32_t FAudioSourceVoice_SetSourceSampleRate( + FAudioSourceVoice *voice, + uint32_t NewSourceSampleRate +) { + uint32_t outSampleRate; + uint32_t newDecodeSamples, newResampleSamples; + + LOG_API_ENTER(voice->audio) + FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE); + FAudio_assert( NewSourceSampleRate >= FAUDIO_MIN_SAMPLE_RATE && + NewSourceSampleRate <= FAUDIO_MAX_SAMPLE_RATE ); + + FAudio_PlatformLockMutex(voice->src.bufferLock); + LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock) + if ( voice->audio->version > 7 && + voice->src.bufferList != NULL ) + { + FAudio_PlatformUnlockMutex(voice->src.bufferLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock) + LOG_API_EXIT(voice->audio) + return FAUDIO_E_INVALID_CALL; + } + FAudio_PlatformUnlockMutex(voice->src.bufferLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock) + + voice->src.format->nSamplesPerSec = NewSourceSampleRate; + + /* Resize decode cache */ + newDecodeSamples = (uint32_t) FAudio_ceil( + voice->audio->updateSize * + (double) voice->src.maxFreqRatio * + (double) NewSourceSampleRate / + (double) voice->audio->master->master.inputSampleRate + ) + EXTRA_DECODE_PADDING * voice->src.format->nChannels; + FAudio_INTERNAL_ResizeDecodeCache( + voice->audio, + (newDecodeSamples + EXTRA_DECODE_PADDING) * voice->src.format->nChannels + ); + voice->src.decodeSamples = newDecodeSamples; + + FAudio_PlatformLockMutex(voice->sendLock); + LOG_MUTEX_LOCK(voice->audio, voice->sendLock) + + if (voice->sends.SendCount == 0) + { + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + LOG_API_EXIT(voice->audio) + return 0; + } + outSampleRate = voice->sends.pSends[0].pOutputVoice->type == FAUDIO_VOICE_MASTER ? + voice->sends.pSends[0].pOutputVoice->master.inputSampleRate : + voice->sends.pSends[0].pOutputVoice->mix.inputSampleRate; + + newResampleSamples = (uint32_t) (FAudio_ceil( + (double) voice->audio->updateSize * + (double) outSampleRate / + (double) voice->audio->master->master.inputSampleRate + )); + voice->src.resampleSamples = newResampleSamples; + + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + + LOG_API_EXIT(voice->audio) + return 0; +} + +/* FAudioMasteringVoice Interface */ + +FAUDIOAPI uint32_t FAudioMasteringVoice_GetChannelMask( + FAudioMasteringVoice *voice, + uint32_t *pChannelMask +) { + LOG_API_ENTER(voice->audio) + FAudio_assert(voice->type == FAUDIO_VOICE_MASTER); + FAudio_assert(pChannelMask != NULL); + + *pChannelMask = voice->audio->mixFormat.dwChannelMask; + LOG_API_EXIT(voice->audio) + return 0; +} + +#ifdef FAUDIO_DUMP_VOICES + +static inline FAudioIOStreamOut *DumpVoices_fopen( + const FAudioSourceVoice *voice, + const FAudioWaveFormatEx *format, + const char *mode, + const char *ext +) { + char loc[64]; + uint16_t format_tag = format->wFormatTag; + uint16_t format_ex_tag = 0; + if (format->wFormatTag == FAUDIO_FORMAT_EXTENSIBLE) + { + /* get the GUID of the extended subformat */ + const FAudioWaveFormatExtensible *format_ex = + (const FAudioWaveFormatExtensible*) format; + format_ex_tag = (uint16_t) (format_ex->SubFormat.Data1); + } + FAudio_snprintf( + loc, + sizeof(loc), + "FA_fmt_0x%04X_0x%04X_0x%016lX%s.wav", + format_tag, + format_ex_tag, + (uint64_t) voice, + ext + ); + FAudioIOStreamOut *fileOut = FAudio_fopen_out(loc, mode); + return fileOut; +} + +static inline void DumpVoices_finalize_section( + const FAudioSourceVoice *voice, + const FAudioWaveFormatEx *format, + const char *section /* one of "data" or "dpds" */ +) { + /* data file only contains the real data bytes */ + FAudioIOStreamOut *io_data = DumpVoices_fopen(voice, format, "rb", section); + if (!io_data) + { + return; + } + FAudio_PlatformLockMutex((FAudioMutex) io_data->lock); + size_t file_size_data = io_data->size(io_data->data); + if (file_size_data == 0) + { + /* nothing to do */ + /* close data file */ + FAudio_PlatformUnlockMutex((FAudioMutex) io_data->lock); + FAudio_close_out(io_data); + return; + } + + /* we got some data: append data section to main file */ + FAudioIOStreamOut *io = DumpVoices_fopen(voice, format, "ab", ""); + if (!io) + { + /* close data file */ + FAudio_PlatformUnlockMutex((FAudioMutex) io_data->lock); + FAudio_close_out(io_data); + return; + } + + /* data sub-chunk - 8 bytes + data */ + /* SubChunk2ID - 4 --> "data" or "dpds" */ + io->write(io->data, section, 4, 1); + /* Subchunk2Size - 4 */ + uint32_t chunk_size = (uint32_t)file_size_data; + io->write(io->data, &chunk_size, 4, 1); + /* data */ + /* fill in data bytes */ + uint8_t buffer[1024*1024]; + size_t count; + while((count = io_data->read(io_data->data, (void*) buffer, 1, 1024*1024)) > 0) + { + io->write(io->data, (void*) buffer, 1, count); + } + + /* close data file */ + FAudio_PlatformUnlockMutex((FAudioMutex) io_data->lock); + FAudio_close_out(io_data); + /* close main file */ + FAudio_PlatformUnlockMutex((FAudioMutex) io->lock); + FAudio_close_out(io); +} + +static void FAudio_DUMPVOICE_Init(const FAudioSourceVoice *voice) +{ + const FAudioWaveFormatEx *format = voice->src.format; + + FAudioIOStreamOut *io = DumpVoices_fopen(voice, format, "wb", ""); + if (!io) + { + return; + } + FAudio_PlatformLockMutex((FAudioMutex) io->lock); + /* another GREAT ressource + * https://wiki.multimedia.cx/index.php/Microsoft_xWMA + */ + + + /* wave file format taken from + * http://soundfile.sapp.org/doc/WaveFormat + * https://sites.google.com/site/musicgapi/technical-documents/wav-file-format + * |52 49|46 46|52 4A|02 00| + * |c1 sz|af|nc|sp rt|bt rt| + * |ba|bs|da ta|c2 sz| + + * | R I F F |chunk size |W A V E |f m t | + * 19026 + * | 52 49 46 46 52 4A 02 00 57 41 56 45 66 6D 74 20 | RIFFRJ..WAVEfmt + + * | subchnk size|fmt |nChan |samplerate |byte rate | + * | 50 | 2 |2 |11025 |11289 | + * | 32 00 00 00 02 00 02 00 11 2B 00 00 19 2C 00 00 | 2........+...,.. + + * |blkaln|bps |efmt |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| + * | 512 |4 |32 |500 |7 |256 |0 |512 | + * | 512 |4 |32 |459252 |256 | + * | 00 02|04 00 20 00 F4 01 07 00 00 01 00 00 00 02 | .... .ô......... + + * | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | + * | + * | 00 FF 00 00 00 00 C0 00 40 00 F0 00 00 00 CC 01 | .ÿ....À.@.ð...ÃŒ. + + * | XXXXXXXXXXXXXXXXXX|d a t a |chunk size |XXXXX | + * | | |18944 | | + * | 30 FF 88 01 18 FF 64 61 74 61 00 4A 02 00 00 00 | 0ÿ...ÿdata.J.... + */ + + uint16_t cbSize = format->cbSize; + const char *formatFourcc = "WAVE"; + uint16_t wFormatTag = format->wFormatTag; + /* special handling for WMAUDIO2 */ + if (wFormatTag == FAUDIO_FORMAT_EXTENSIBLE && cbSize >= 22) + { + const FAudioWaveFormatExtensible *format_ex = + (const FAudioWaveFormatExtensible*) format; + uint16_t format_ex_tag = (uint16_t) (format_ex->SubFormat.Data1); + if (format_ex_tag == FAUDIO_FORMAT_WMAUDIO2) + { + cbSize = 0; + formatFourcc = "XWMA"; + wFormatTag = FAUDIO_FORMAT_WMAUDIO2; + } + } + + { /* RIFF chunk descriptor - 12 byte */ + /* ChunkID - 4 */ + io->write(io->data, "RIFF", 4, 1); + /* ChunkSize - 4 */ + uint32_t filesize = 0; /* the real file size is written in finalize step */ + io->write(io->data, &filesize, 4, 1); + /* Format - 4 */ + io->write(io->data, formatFourcc, 4, 1); + } + { /* fmt sub-chunk 24 */ + /* Subchunk1ID - 4 */ + io->write(io->data, "fmt ", 4, 1); + /* Subchunk1Size - 4 */ + /* 18 byte for WAVEFORMATEX and cbSize for WAVEFORMATEXTENDED */ + uint32_t chunk_data_size = 18 + (uint32_t) cbSize; + io->write(io->data, &chunk_data_size, 4, 1); + /* AudioFormat - 2 */ + io->write(io->data, &wFormatTag, 2, 1); + /* NumChannels - 2 */ + io->write(io->data, &format->nChannels, 2, 1); + /* SampleRate - 4 */ + io->write(io->data, &format->nSamplesPerSec, 4, 1); + /* ByteRate - 4 */ + /* SampleRate * NumChannels * BitsPerSample/8 */ + io->write(io->data, &format->nAvgBytesPerSec, 4, 1); + /* BlockAlign - 2 */ + /* NumChannels * BitsPerSample/8 */ + io->write(io->data, &format->nBlockAlign, 2, 1); + /* BitsPerSample - 2 */ + io->write(io->data, &format->wBitsPerSample, 2, 1); + } + /* in case of extensible audio format write the additional data to the file */ + { + /* always write the cbSize */ + io->write(io->data, &cbSize, 2, 1); + + if (cbSize >= 22) + { + /* we have a WAVEFORMATEXTENSIBLE struct to write */ + const FAudioWaveFormatExtensible *format_ex = + (const FAudioWaveFormatExtensible*) format; + io->write(io->data, &format_ex->Samples.wValidBitsPerSample, 2, 1); + io->write(io->data, &format_ex->dwChannelMask, 4, 1); + /* write FAudioGUID */ + io->write(io->data, &format_ex->SubFormat.Data1, 4, 1); + io->write(io->data, &format_ex->SubFormat.Data2, 2, 1); + io->write(io->data, &format_ex->SubFormat.Data3, 2, 1); + io->write(io->data, &format_ex->SubFormat.Data4, 1, 8); + } + if (format->cbSize > 22) + { + /* fill up the remaining cbSize bytes with zeros */ + uint8_t zero = 0; + for (uint16_t i=23; i<=format->cbSize; i++) + { + io->write(io->data, &zero, 1, 1); + } + } + } + { /* dpds sub-chunk - optional - 8 bytes + bufferWMA uint32_t samples */ + /* create file to hold the bufferWMA samples */ + FAudioIOStreamOut *io_dpds = DumpVoices_fopen(voice, format, "wb", "dpds"); + FAudio_close_out(io_dpds); + /* io_dpds file will be filled by SubmitBuffer */ + } + { /* data sub-chunk - 8 bytes + data */ + /* create file to hold the data samples */ + FAudioIOStreamOut *io_data = DumpVoices_fopen(voice, format, "wb", "data"); + FAudio_close_out(io_data); + /* io_data file will be filled by SubmitBuffer */ + } + FAudio_PlatformUnlockMutex((FAudioMutex) io->lock); + FAudio_close_out(io); +} + +static void FAudio_DUMPVOICE_Finalize(const FAudioSourceVoice *voice) +{ + const FAudioWaveFormatEx *format = voice->src.format; + + /* add dpds subchunk - optional */ + DumpVoices_finalize_section(voice, format, "dpds"); + /* add data subchunk */ + DumpVoices_finalize_section(voice, format, "data"); + + /* open main file to update filesize */ + FAudioIOStreamOut *io = DumpVoices_fopen(voice, format, "r+b", ""); + if (!io) + { + return; + } + FAudio_PlatformLockMutex((FAudioMutex) io->lock); + size_t file_size = io->size(io->data); + if (file_size >= 44) + { + /* update filesize */ + uint32_t chunk_size = (uint32_t)(file_size - 8); + io->seek(io->data, 4, FAUDIO_SEEK_SET); + io->write(io->data, &chunk_size, 4, 1); + } + FAudio_PlatformUnlockMutex((FAudioMutex) io->lock); + FAudio_close_out(io); +} + +static void FAudio_DUMPVOICE_WriteBuffer( + const FAudioSourceVoice *voice, + const FAudioBuffer *pBuffer, + const FAudioBufferWMA *pBufferWMA, + const uint32_t playBegin, + const uint32_t playLength +) { + FAudioIOStreamOut *io_data = DumpVoices_fopen(voice, voice->src.format, "ab", "data"); + if (io_data == NULL) + { + return; + } + + FAudio_PlatformLockMutex((FAudioMutex) io_data->lock); + if (pBufferWMA != NULL) + { + /* dump encoded buffer contents */ + if (pBufferWMA->PacketCount > 0) + { + FAudioIOStreamOut *io_dpds = DumpVoices_fopen(voice, voice->src.format, "ab", "dpds"); + if (io_dpds) + { + FAudio_PlatformLockMutex((FAudioMutex) io_dpds->lock); + /* write to dpds file */ + io_dpds->write(io_dpds->data, pBufferWMA->pDecodedPacketCumulativeBytes, sizeof(uint32_t), pBufferWMA->PacketCount); + FAudio_PlatformUnlockMutex((FAudioMutex) io_dpds->lock); + FAudio_close_out(io_dpds); + } + /* write buffer contents to data file */ + io_data->write(io_data->data, pBuffer->pAudioData, sizeof(uint8_t), pBuffer->AudioBytes); + } + } + else + { + /* dump unencoded buffer contents */ + uint16_t bytesPerFrame = (voice->src.format->nChannels * voice->src.format->wBitsPerSample / 8); + FAudio_assert(bytesPerFrame > 0); + const void *pAudioDataBegin = pBuffer->pAudioData + playBegin*bytesPerFrame; + io_data->write(io_data->data, pAudioDataBegin, bytesPerFrame, playLength); + } + FAudio_PlatformUnlockMutex((FAudioMutex) io_data->lock); + FAudio_close_out(io_data); +} + +#endif /* FAUDIO_DUMP_VOICES */ + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/src/FAudioFX_reverb.c b/libs/faudio/src/FAudioFX_reverb.c new file mode 100644 index 00000000000..4d43fa4e76c --- /dev/null +++ b/libs/faudio/src/FAudioFX_reverb.c @@ -0,0 +1,1969 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * Ethan "flibitijibibo" Lee + * + */ + +#include "FAudioFX.h" +#include "FAudio_internal.h" + +/* #define DISABLE_SUBNORMALS */ +#ifdef DISABLE_SUBNORMALS +#include /* ONLY USE THIS FOR fpclassify/_fpclass! */ + +/* VS2010 doesn't define fpclassify (which is C99), so here it is. */ +#if defined(_MSC_VER) && !defined(fpclassify) +#define IS_SUBNORMAL(a) (_fpclass(a) & (_FPCLASS_ND | _FPCLASS_PD)) +#else +#define IS_SUBNORMAL(a) (fpclassify(a) == FP_SUBNORMAL) +#endif +#endif /* DISABLE_SUBNORMALS */ + +/* Utility Functions */ + +static inline float DbGainToFactor(float gain) +{ + return (float) FAudio_pow(10, gain / 20.0f); +} + +static inline uint32_t MsToSamples(float msec, int32_t sampleRate) +{ + return (uint32_t) ((sampleRate * msec) / 1000.0f); +} + +#ifndef DISABLE_SUBNORMALS +#define Undenormalize(a) ((a)) +#else /* DISABLE_SUBNORMALS */ +static inline float Undenormalize(float sample_in) +{ + if (IS_SUBNORMAL(sample_in)) + { + return 0.0f; + } + return sample_in; +} +#endif /* DISABLE_SUBNORMALS */ + +/* Component - Delay */ + +#define DSP_DELAY_MAX_DELAY_MS 300 + +typedef struct DspDelay +{ + int32_t sampleRate; + uint32_t capacity; /* In samples */ + uint32_t delay; /* In samples */ + uint32_t read_idx; + uint32_t write_idx; + float *buffer; +} DspDelay; + +static inline void DspDelay_Initialize( + DspDelay *filter, + int32_t sampleRate, + float delay_ms, + FAudioMallocFunc pMalloc +) { + FAudio_assert(delay_ms >= 0 && delay_ms <= DSP_DELAY_MAX_DELAY_MS); + + filter->sampleRate = sampleRate; + filter->capacity = MsToSamples(DSP_DELAY_MAX_DELAY_MS, sampleRate); + filter->delay = MsToSamples(delay_ms, sampleRate); + filter->read_idx = 0; + filter->write_idx = filter->delay; + filter->buffer = (float*) pMalloc(filter->capacity * sizeof(float)); + FAudio_zero(filter->buffer, filter->capacity * sizeof(float)); +} + +static inline void DspDelay_Change(DspDelay *filter, float delay_ms) +{ + FAudio_assert(delay_ms >= 0 && delay_ms <= DSP_DELAY_MAX_DELAY_MS); + + /* Length */ + filter->delay = MsToSamples(delay_ms, filter->sampleRate); + filter->read_idx = (filter->write_idx - filter->delay + filter->capacity) % filter->capacity; +} + +static inline float DspDelay_Read(DspDelay *filter) +{ + float delay_out; + + FAudio_assert(filter->read_idx < filter->capacity); + + delay_out = filter->buffer[filter->read_idx]; + filter->read_idx = (filter->read_idx + 1) % filter->capacity; + return delay_out; +} + +static inline void DspDelay_Write(DspDelay *filter, float sample) +{ + FAudio_assert(filter->write_idx < filter->capacity); + + filter->buffer[filter->write_idx] = sample; + filter->write_idx = (filter->write_idx + 1) % filter->capacity; +} + +static inline float DspDelay_Process(DspDelay *filter, float sample_in) +{ + float delay_out = DspDelay_Read(filter); + DspDelay_Write(filter, sample_in); + return delay_out; +} + +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) +{ + filter->read_idx = 0; + filter->write_idx = filter->delay; + FAudio_zero(filter->buffer, filter->capacity * sizeof(float)); +} + +static inline void DspDelay_Destroy(DspDelay *filter, FAudioFreeFunc pFree) +{ + pFree(filter->buffer); +} + +static inline float DspComb_FeedbackFromRT60(DspDelay *delay, float rt60_ms) +{ + float exponent = ( + (-3.0f * delay->delay * 1000.0f) / + (delay->sampleRate * rt60_ms) + ); + return (float) FAudio_pow(10.0f, exponent); +} + +/* Component - Bi-Quad Filter */ + +typedef enum DspBiQuadType +{ + DSP_BIQUAD_LOWSHELVING, + DSP_BIQUAD_HIGHSHELVING +} DspBiQuadType; + +typedef struct DspBiQuad +{ + int32_t sampleRate; + float a0, a1, a2; + float b1, b2; + float c0, d0; + float delay0, delay1; +} DspBiQuad; + +static inline void DspBiQuad_Change( + DspBiQuad *filter, + DspBiQuadType type, + float frequency, + float q, + float gain +) { + const float TWOPI = 6.283185307179586476925286766559005; + float theta_c = (TWOPI * frequency) / (float) filter->sampleRate; + float mu = DbGainToFactor(gain); + float beta = (type == DSP_BIQUAD_LOWSHELVING) ? + 4.0f / (1 + mu) : + (1 + mu) / 4.0f; + float delta = beta * (float) FAudio_tan(theta_c * 0.5f); + float gamma = (1 - delta) / (1 + delta); + + if (type == DSP_BIQUAD_LOWSHELVING) + { + filter->a0 = (1 - gamma) * 0.5f; + filter->a1 = filter->a0; + } + else + { + filter->a0 = (1 + gamma) * 0.5f; + filter->a1 = -filter->a0; + } + + filter->a2 = 0.0f; + filter->b1 = -gamma; + filter->b2 = 0.0f; + filter->c0 = mu - 1.0f; + filter->d0 = 1.0f; +} + +static inline void DspBiQuad_Initialize( + DspBiQuad *filter, + int32_t sampleRate, + DspBiQuadType type, + float frequency, /* Corner frequency */ + float q, /* Only used by low/high-pass filters */ + float gain /* Only used by low/high-shelving filters */ +) { + filter->sampleRate = sampleRate; + filter->delay0 = 0.0f; + filter->delay1 = 0.0f; + DspBiQuad_Change(filter, type, frequency, q, gain); +} + +static inline float DspBiQuad_Process(DspBiQuad *filter, float sample_in) +{ + /* Direct Form II Transposed: + * - Less delay registers than Direct Form I + * - More numerically stable than Direct Form II + */ + float result = (filter->a0 * sample_in) + filter->delay0; + filter->delay0 = (filter->a1 * sample_in) - (filter->b1 * result) + filter->delay1; + filter->delay1 = (filter->a2 * sample_in) - (filter->b2 * result); + + return Undenormalize( + (result * filter->c0) + + (sample_in * filter->d0) + ); +} + +static inline void DspBiQuad_Reset(DspBiQuad *filter) +{ + filter->delay0 = 0.0f; + filter->delay1 = 0.0f; +} + +static inline void DspBiQuad_Destroy(DspBiQuad *filter) +{ +} + +/* Component - Comb Filter with Integrated Low/High Shelving Filters */ + +typedef struct DspCombShelving +{ + DspDelay comb_delay; + float comb_feedback_gain; + + DspBiQuad low_shelving; + DspBiQuad high_shelving; +} DspCombShelving; + +static inline void DspCombShelving_Initialize( + DspCombShelving *filter, + int32_t sampleRate, + float delay_ms, + float rt60_ms, + float low_frequency, + float low_gain, + float high_frequency, + float high_gain, + FAudioMallocFunc pMalloc +) { + DspDelay_Initialize(&filter->comb_delay, sampleRate, delay_ms, pMalloc); + filter->comb_feedback_gain = DspComb_FeedbackFromRT60( + &filter->comb_delay, + rt60_ms + ); + + DspBiQuad_Initialize( + &filter->low_shelving, + sampleRate, + DSP_BIQUAD_LOWSHELVING, + low_frequency, + 0.0f, + low_gain + ); + DspBiQuad_Initialize( + &filter->high_shelving, + sampleRate, + DSP_BIQUAD_HIGHSHELVING, + high_frequency, + 0.0f, + high_gain + ); +} + +static inline float DspCombShelving_Process( + DspCombShelving *filter, + float sample_in +) { + float delay_out, feedback, to_buf; + + delay_out = DspDelay_Read(&filter->comb_delay); + + /* Apply shelving filters */ + feedback = DspBiQuad_Process(&filter->high_shelving, delay_out); + feedback = DspBiQuad_Process(&filter->low_shelving, feedback); + + /* Apply comb filter */ + to_buf = Undenormalize(sample_in + (filter->comb_feedback_gain * feedback)); + DspDelay_Write(&filter->comb_delay, to_buf); + + return delay_out; +} + +static inline void DspCombShelving_Reset(DspCombShelving *filter) +{ + DspDelay_Reset(&filter->comb_delay); + DspBiQuad_Reset(&filter->low_shelving); + DspBiQuad_Reset(&filter->high_shelving); +} + +static inline void DspCombShelving_Destroy( + DspCombShelving *filter, + FAudioFreeFunc pFree +) { + DspDelay_Destroy(&filter->comb_delay, pFree); + DspBiQuad_Destroy(&filter->low_shelving); + DspBiQuad_Destroy(&filter->high_shelving); +} + +/* Component - Delaying All-Pass Filter */ + +typedef struct DspAllPass +{ + DspDelay delay; + float feedback_gain; +} DspAllPass; + +static inline void DspAllPass_Initialize( + DspAllPass *filter, + int32_t sampleRate, + float delay_ms, + float gain, + FAudioMallocFunc pMalloc +) { + DspDelay_Initialize(&filter->delay, sampleRate, delay_ms, pMalloc); + filter->feedback_gain = gain; +} + +static inline void DspAllPass_Change(DspAllPass *filter, float delay_ms, float gain) +{ + DspDelay_Change(&filter->delay, delay_ms); + filter->feedback_gain = gain; +} + +static inline float DspAllPass_Process(DspAllPass *filter, float sample_in) +{ + float delay_out, to_buf; + + delay_out = DspDelay_Read(&filter->delay); + + to_buf = Undenormalize(sample_in + (filter->feedback_gain * delay_out)); + DspDelay_Write(&filter->delay, to_buf); + + return Undenormalize(delay_out - (filter->feedback_gain * to_buf)); +} + +static inline void DspAllPass_Reset(DspAllPass *filter) +{ + DspDelay_Reset(&filter->delay); +} + +static inline void DspAllPass_Destroy(DspAllPass *filter, FAudioFreeFunc pFree) +{ + DspDelay_Destroy(&filter->delay, pFree); +} + +/* +Reverb network - loosely based on the reverberator from +"Designing Audio Effect Plug-Ins in C++" by Will Pirkle and +the classic classic Schroeder-Moorer reverberator with modifications +to fit the XAudio2FX parameters. + + + In +--------+ +----+ +------------+ +-----+ + ----|--->PreDelay---->APF1---+--->Sub LeftCh |----->| | Left Out + | +--------+ +----+ | +------------+ | Wet |--------> + | | +------------+ | | + | |---|Sub RightCh |----->| Dry | + | +------------+ | | Right Out + | | Mix |--------> + +----------------------------------------------->| | + +-----+ + Sub routine per channel : + + In +-----+ +-----+ * cg + ---+->|Delay|--+---|Comb1|------+ + | +-----+ | +-----+ | + | | | + | | +-----+ * cg | + | +--->Comb2|------+ + | | +-----+ | +-----+ + | | +---->| SUM |--------+ + | | +-----+ * cg | +-----+ | + | +--->... |------+ | + | * g0 | +-----+ | | + | | | | + | +--->-----+ * cg | | + | |Comb8|------+ | + | +-----+ | + v | + +-----+ g1 +----+ +----+ +----+ +----+ | + | SUM |<------|APF4|<--|APF3|<--|APF2|<--|APF1|<-----+ + +-----+ +----+ +----+ +----+ +----+ + | + | + | +-------------+ Out + +----------->|RoomFilter |------------------------> + +-------------+ + + +Parameters: + +float WetDryMix; 0 - 100 (0 = fully dry, 100 = fully wet) +uint32_t ReflectionsDelay; 0 - 300 ms +uint8_t ReverbDelay; 0 - 85 ms +uint8_t RearDelay; 0 - 5 ms +uint8_t PositionLeft; 0 - 30 +uint8_t PositionRight; 0 - 30 +uint8_t PositionMatrixLeft; 0 - 30 +uint8_t PositionMatrixRight; 0 - 30 +uint8_t EarlyDiffusion; 0 - 15 +uint8_t LateDiffusion; 0 - 15 +uint8_t LowEQGain; 0 - 12 (formula dB = LowEQGain - 8) +uint8_t LowEQCutoff; 0 - 9 (formula Hz = 50 + (LowEQCutoff * 50)) +uint8_t HighEQGain; 0 - 8 (formula dB = HighEQGain - 8) +uint8_t HighEQCutoff; 0 - 14 (formula Hz = 1000 + (HighEqCutoff * 500)) +float RoomFilterFreq; 20 - 20000Hz +float RoomFilterMain; -100 - 0dB +float RoomFilterHF; -100 - 0dB +float ReflectionsGain; -100 - 20dB +float ReverbGain; -100 - 20dB +float DecayTime; 0.1 - .... ms +float Density; 0 - 100 % +float RoomSize; 1 - 100 feet (NOT USED YET) + +*/ + +#define REVERB_COUNT_COMB 8 +#define REVERB_COUNT_APF_IN 1 +#define REVERB_COUNT_APF_OUT 4 + +static float COMB_DELAYS[REVERB_COUNT_COMB] = +{ + 25.31f, + 26.94f, + 28.96f, + 30.75f, + 32.24f, + 33.80f, + 35.31f, + 36.67f +}; + +static float APF_IN_DELAYS[REVERB_COUNT_APF_IN] = +{ + 13.28f, +/* 28.13f */ +}; + +static float APF_OUT_DELAYS[REVERB_COUNT_APF_OUT] = +{ + 5.10f, + 12.61f, + 10.0f, + 7.73f +}; + +typedef enum FAudio_ChannelPositionFlags +{ + Position_Left = 0x1, + Position_Right = 0x2, + Position_Center = 0x4, + Position_Rear = 0x8, +} FAudio_ChannelPositionFlags; + +static FAudio_ChannelPositionFlags FAudio_GetChannelPositionFlags(int32_t total_channels, int32_t channel) +{ + switch (total_channels) + { + case 1: + return Position_Center; + + case 2: + return (channel == 0) ? Position_Left : Position_Right; + + case 4: + switch (channel) + { + case 0: + return Position_Left; + case 1: + return Position_Right; + case 2: + return Position_Left | Position_Rear; + case 3: + return Position_Right | Position_Rear; + } + + case 5: + switch (channel) + { + case 0: + return Position_Left; + case 1: + return Position_Right; + case 2: + return Position_Center; + case 3: + return Position_Left | Position_Rear; + case 4: + return Position_Right | Position_Rear; + } + + default: + FAudio_assert(0 && "Unsupported channel count"); + break; + } + + /* shouldn't happen, but default to left speaker */ + return Position_Left; +} + +float FAudio_GetStereoSpreadDelayMS(int32_t total_channels, int32_t channel) +{ + FAudio_ChannelPositionFlags flags = FAudio_GetChannelPositionFlags(total_channels, channel); + return (flags & Position_Right) ? 0.5216f : 0.0f; +} + +typedef struct DspReverbChannel +{ + DspDelay reverb_delay; + DspCombShelving lpf_comb[REVERB_COUNT_COMB]; + DspAllPass apf_out[REVERB_COUNT_APF_OUT]; + DspBiQuad room_high_shelf; + float early_gain; + float gain; +} DspReverbChannel; + +typedef struct DspReverb +{ + DspDelay early_delay; + DspAllPass apf_in[REVERB_COUNT_APF_IN]; + + int32_t in_channels; + int32_t out_channels; + int32_t reverb_channels; + DspReverbChannel channel[5]; + + float early_gain; + float reverb_gain; + float room_gain; + float wet_ratio; + float dry_ratio; +} DspReverb; + +static inline void DspReverb_Create( + DspReverb *reverb, + int32_t sampleRate, + int32_t in_channels, + int32_t out_channels, + FAudioMallocFunc pMalloc +) { + int32_t i, c; + + FAudio_assert(in_channels == 1 || in_channels == 2 || in_channels == 6); + FAudio_assert(out_channels == 1 || out_channels == 2 || out_channels == 6); + + FAudio_zero(reverb, sizeof(DspReverb)); + DspDelay_Initialize(&reverb->early_delay, sampleRate, 10, pMalloc); + + for (i = 0; i < REVERB_COUNT_APF_IN; i += 1) + { + DspAllPass_Initialize( + &reverb->apf_in[i], + sampleRate, + APF_IN_DELAYS[i], + 0.5f, + pMalloc + ); + } + + if (out_channels == 6) + { + reverb->reverb_channels = (in_channels == 6) ? 5 : 4; + } + else + { + reverb->reverb_channels = out_channels; + } + + for (c = 0; c < reverb->reverb_channels; c += 1) + { + DspDelay_Initialize( + &reverb->channel[c].reverb_delay, + sampleRate, + 10, + pMalloc + ); + + for (i = 0; i < REVERB_COUNT_COMB; i += 1) + { + DspCombShelving_Initialize( + &reverb->channel[c].lpf_comb[i], + sampleRate, + COMB_DELAYS[i] + FAudio_GetStereoSpreadDelayMS(reverb->reverb_channels, c), + 500, + 500, + -6, + 5000, + -6, + pMalloc + ); + } + + for (i = 0; i < REVERB_COUNT_APF_OUT; i += 1) + { + DspAllPass_Initialize( + &reverb->channel[c].apf_out[i], + sampleRate, + APF_OUT_DELAYS[i] + FAudio_GetStereoSpreadDelayMS(reverb->reverb_channels, c), + 0.5f, + pMalloc + ); + } + + DspBiQuad_Initialize( + &reverb->channel[c].room_high_shelf, + sampleRate, + DSP_BIQUAD_HIGHSHELVING, + 5000, + 0, + -10 + ); + reverb->channel[c].gain = 1.0f; + } + + reverb->early_gain = 1.0f; + reverb->reverb_gain = 1.0f; + reverb->dry_ratio = 0.0f; + reverb->wet_ratio = 1.0f; + reverb->in_channels = in_channels; + reverb->out_channels = out_channels; +} + +static inline void DspReverb_Destroy(DspReverb *reverb, FAudioFreeFunc pFree) +{ + int32_t i, c; + + DspDelay_Destroy(&reverb->early_delay, pFree); + + for (i = 0; i < REVERB_COUNT_APF_IN; i += 1) + { + DspAllPass_Destroy(&reverb->apf_in[i], pFree); + } + + for (c = 0; c < reverb->reverb_channels; c += 1) + { + DspDelay_Destroy(&reverb->channel[c].reverb_delay, pFree); + + for (i = 0; i < REVERB_COUNT_COMB; i += 1) + { + DspCombShelving_Destroy( + &reverb->channel[c].lpf_comb[i], + pFree + ); + } + + DspBiQuad_Destroy(&reverb->channel[c].room_high_shelf); + + for (i = 0; i < REVERB_COUNT_APF_OUT; i += 1) + { + DspAllPass_Destroy( + &reverb->channel[c].apf_out[i], + pFree + ); + } + } +} + +static inline void DspReverb_SetParameters( + DspReverb *reverb, + FAudioFXReverbParameters *params +) { + float early_diffusion, late_diffusion; + DspCombShelving *comb; + int32_t i, c; + + /* Pre-Delay */ + DspDelay_Change(&reverb->early_delay, (float) params->ReflectionsDelay); + + /* Early Reflections - Diffusion */ + early_diffusion = 0.6f - ((params->EarlyDiffusion / 15.0f) * 0.2f); + + for (i = 0; i < REVERB_COUNT_APF_IN; i += 1) + { + DspAllPass_Change( + &reverb->apf_in[i], + APF_IN_DELAYS[i], + early_diffusion + ); + } + + /* Reverberation */ + for (c = 0; c < reverb->reverb_channels; c += 1) + { + float channel_delay = + (FAudio_GetChannelPositionFlags(reverb->reverb_channels, c) & Position_Rear) ? + params->RearDelay : + 0.0f; + + DspDelay_Change( + &reverb->channel[c].reverb_delay, + (float) params->ReverbDelay + channel_delay + ); + + for (i = 0; i < REVERB_COUNT_COMB; i += 1) + { + comb = &reverb->channel[c].lpf_comb[i]; + + /* Set decay time of comb filter */ + DspDelay_Change( + &comb->comb_delay, + COMB_DELAYS[i] + FAudio_GetStereoSpreadDelayMS(reverb->reverb_channels, c) + ); + comb->comb_feedback_gain = DspComb_FeedbackFromRT60( + &comb->comb_delay, + FAudio_max(params->DecayTime, FAUDIOFX_REVERB_MIN_DECAY_TIME) * 1000.0f + ); + + /* High/Low shelving */ + DspBiQuad_Change( + &comb->low_shelving, + DSP_BIQUAD_LOWSHELVING, + 50.0f + params->LowEQCutoff * 50.0f, + 0.0f, + params->LowEQGain - 8.0f + ); + DspBiQuad_Change( + &comb->high_shelving, + DSP_BIQUAD_HIGHSHELVING, + 1000 + params->HighEQCutoff * 500.0f, + 0.0f, + params->HighEQGain - 8.0f + ); + } + } + + /* Gain */ + reverb->early_gain = DbGainToFactor(params->ReflectionsGain); + reverb->reverb_gain = DbGainToFactor(params->ReverbGain); + reverb->room_gain = DbGainToFactor(params->RoomFilterMain); + + /* Late Diffusion */ + late_diffusion = 0.6f - ((params->LateDiffusion / 15.0f) * 0.2f); + + for (c = 0; c < reverb->reverb_channels; c += 1) + { + FAudio_ChannelPositionFlags position = FAudio_GetChannelPositionFlags(reverb->reverb_channels, c); + float gain; + + for (i = 0; i < REVERB_COUNT_APF_OUT; i += 1) + { + DspAllPass_Change( + &reverb->channel[c].apf_out[i], + APF_OUT_DELAYS[i] + FAudio_GetStereoSpreadDelayMS(reverb->reverb_channels, c), + late_diffusion + ); + } + + DspBiQuad_Change( + &reverb->channel[c].room_high_shelf, + DSP_BIQUAD_HIGHSHELVING, + params->RoomFilterFreq, + 0.0f, + params->RoomFilterMain + params->RoomFilterHF + ); + + if (position & Position_Left) + { + gain = params->PositionMatrixLeft; + } + else if (position & Position_Right) + { + gain = params->PositionMatrixRight; + } + else /*if (position & Position_Center) */ + { + gain = (params->PositionMatrixLeft + params->PositionMatrixRight) / 2.0f; + } + reverb->channel[c].gain = 1.5f - (gain / 27.0f) * 0.5f; + + if (position & Position_Rear) + { + /* Rear-channel Attenuation */ + reverb->channel[c].gain *= 0.75f; + } + + if (position & Position_Left) + { + gain = params->PositionLeft; + } + else if (position & Position_Right) + { + gain = params->PositionRight; + } + else /*if (position & Position_Center) */ + { + gain = (params->PositionLeft + params->PositionRight) / 2.0f; + } + reverb->channel[c].early_gain = 1.2f - (gain / 6.0f) * 0.2f; + reverb->channel[c].early_gain = ( + reverb->channel[c].early_gain * + reverb->early_gain + ); + } + + /* Wet/Dry Mix (100 = fully wet, 0 = fully dry) */ + reverb->wet_ratio = params->WetDryMix / 100.0f; + reverb->dry_ratio = 1.0f - reverb->wet_ratio; +} + +static inline void DspReverb_SetParameters9( + DspReverb *reverb, + FAudioFXReverbParameters9 *params +) { + FAudioFXReverbParameters oldParams; + oldParams.WetDryMix = params->WetDryMix; + oldParams.ReflectionsDelay = params->ReflectionsDelay; + oldParams.ReverbDelay = params->ReverbDelay; + oldParams.RearDelay = params->RearDelay; + oldParams.PositionLeft = params->PositionLeft; + oldParams.PositionRight = params->PositionRight; + oldParams.PositionMatrixLeft = params->PositionMatrixLeft; + oldParams.PositionMatrixRight = params->PositionMatrixRight; + oldParams.EarlyDiffusion = params->EarlyDiffusion; + oldParams.LateDiffusion = params->LateDiffusion; + oldParams.LowEQGain = params->LowEQGain; + oldParams.LowEQCutoff = params->LowEQCutoff; + oldParams.HighEQGain = params->HighEQGain; + oldParams.HighEQCutoff = params->HighEQCutoff; + oldParams.RoomFilterFreq = params->RoomFilterFreq; + oldParams.RoomFilterMain = params->RoomFilterMain; + oldParams.RoomFilterHF = params->RoomFilterHF; + oldParams.ReflectionsGain = params->ReflectionsGain; + oldParams.ReverbGain = params->ReverbGain; + oldParams.DecayTime = params->DecayTime; + oldParams.Density = params->Density; + oldParams.RoomSize = params->RoomSize; + DspReverb_SetParameters(reverb, &oldParams); +} + +static inline float DspReverb_INTERNAL_ProcessEarly( + DspReverb *reverb, + float sample_in +) { + float early; + int32_t i; + + /* Pre-Delay */ + early = DspDelay_Process(&reverb->early_delay, sample_in); + + /* Early Reflections */ + for (i = 0; i < REVERB_COUNT_APF_IN; i += 1) + { + early = DspAllPass_Process(&reverb->apf_in[i], early); + } + + return early; +} + +static inline float DspReverb_INTERNAL_ProcessChannel( + DspReverb *reverb, + DspReverbChannel *channel, + float sample_in +) { + float revdelay, early_late, sample_out; + int32_t i; + + revdelay = DspDelay_Process(&channel->reverb_delay, sample_in); + + sample_out = 0.0f; + for (i = 0; i < REVERB_COUNT_COMB; i += 1) + { + sample_out += DspCombShelving_Process( + &channel->lpf_comb[i], + revdelay + ); + } + sample_out /= (float) REVERB_COUNT_COMB; + + /* Output Diffusion */ + for (i = 0; i < REVERB_COUNT_APF_OUT; i += 1) + { + sample_out = DspAllPass_Process( + &channel->apf_out[i], + sample_out + ); + } + + /* Combine early reflections and reverberation */ + early_late = ( + (sample_in * channel->early_gain) + + (sample_out * reverb->reverb_gain) + ); + + /* Room filter */ + sample_out = DspBiQuad_Process( + &channel->room_high_shelf, + early_late * reverb->room_gain + ); + + /* PositionMatrixLeft/Right */ + return sample_out * channel->gain; +} + +/* Reverb Process Functions */ + +static inline float DspReverb_INTERNAL_Process_1_to_1( + DspReverb *reverb, + float *restrict samples_in, + float *restrict samples_out, + size_t sample_count +) { + const float *in_end = samples_in + sample_count; + float in, early, late, out; + float squared_sum = 0.0f; + + while (samples_in < in_end) + { + /* Input */ + in = *samples_in++; + + /* Early Reflections */ + early = DspReverb_INTERNAL_ProcessEarly(reverb, in); + + /* Reverberation */ + late = DspReverb_INTERNAL_ProcessChannel( + reverb, + &reverb->channel[0], + early + ); + + /* Wet/Dry Mix */ + out = (late * reverb->wet_ratio) + (in * reverb->dry_ratio); + squared_sum += out * out; + + /* Output */ + *samples_out++ = out; + } + + return squared_sum; +} + +static inline float DspReverb_INTERNAL_Process_1_to_5p1( + DspReverb *reverb, + float *restrict samples_in, + float *restrict samples_out, + size_t sample_count +) { + const float *in_end = samples_in + sample_count; + float in, in_ratio, early, late[4]; + float squared_sum = 0.0f; + int32_t c; + + while (samples_in < in_end) + { + /* Input */ + in = *samples_in++; + in_ratio = in * reverb->dry_ratio; + + /* Early Reflections */ + early = DspReverb_INTERNAL_ProcessEarly(reverb, in); + + /* Reverberation with Wet/Dry Mix */ + for (c = 0; c < 4; c += 1) + { + late[c] = (DspReverb_INTERNAL_ProcessChannel( + reverb, + &reverb->channel[c], + early + ) * reverb->wet_ratio) + in_ratio; + squared_sum += late[c] * late[c]; + } + + /* Output */ + *samples_out++ = late[0]; /* Front Left */ + *samples_out++ = late[1]; /* Front Right */ + *samples_out++ = 0.0f; /* Center */ + *samples_out++ = 0.0f; /* LFE */ + *samples_out++ = late[2]; /* Rear Left */ + *samples_out++ = late[3]; /* Rear Right */ + } + + return squared_sum; +} + +static inline float DspReverb_INTERNAL_Process_2_to_2( + DspReverb *reverb, + float *restrict samples_in, + float *restrict samples_out, + size_t sample_count +) { + const float *in_end = samples_in + sample_count; + float in, in_ratio, 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( + reverb, + &reverb->channel[0], + early + ); + late[1] = (DspReverb_INTERNAL_ProcessChannel( + reverb, + &reverb->channel[1], + early + ) * reverb->wet_ratio) + in_ratio; + squared_sum += (late[0] * late[0]) + (late[1] * late[1]); + + /* Output */ + *samples_out++ = late[0]; + *samples_out++ = late[1]; + } + + return squared_sum; +} + +static inline float DspReverb_INTERNAL_Process_2_to_5p1( + DspReverb *reverb, + float *restrict samples_in, + float *restrict samples_out, + size_t sample_count +) { + const float *in_end = samples_in + sample_count; + float in, in_ratio, early, late[4]; + float squared_sum = 0; + int32_t c; + + 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 */ + for (c = 0; c < 4; c += 1) + { + late[c] = (DspReverb_INTERNAL_ProcessChannel( + reverb, + &reverb->channel[c], + early + ) * reverb->wet_ratio) + in_ratio; + squared_sum += late[c] * late[c]; + } + + /* Output */ + *samples_out++ = late[0]; /* Front Left */ + *samples_out++ = late[1]; /* Front Right */ + *samples_out++ = 0.0f; /* Center */ + *samples_out++ = 0.0f; /* LFE */ + *samples_out++ = late[2]; /* Rear Left */ + *samples_out++ = late[3]; /* Rear Right */ + } + + return squared_sum; +} + +static inline float DspReverb_INTERNAL_Process_5p1_to_5p1( + DspReverb *reverb, + float *restrict samples_in, + float *restrict samples_out, + size_t sample_count +) { + const float *in_end = samples_in + sample_count; + float in, in_ratio, early, late[5]; + float squared_sum = 0; + int32_t c; + + while (samples_in < in_end) + { + /* Input - Combine non-LFE channels into 1 */ + in = (samples_in[0] + samples_in[1] + samples_in[2] + + samples_in[4] + samples_in[5]) / 5.0f; + in_ratio = in * reverb->dry_ratio; + + /* Early Reflections */ + early = DspReverb_INTERNAL_ProcessEarly(reverb, in); + + /* Reverberation with Wet/Dry Mix */ + for (c = 0; c < 5; c += 1) + { + late[c] = (DspReverb_INTERNAL_ProcessChannel( + reverb, + &reverb->channel[c], + early + ) * reverb->wet_ratio) + in_ratio; + squared_sum += late[c] * late[c]; + } + + /* Output */ + *samples_out++ = late[0]; /* Front Left */ + *samples_out++ = late[1]; /* Front Right */ + *samples_out++ = late[2]; /* Center */ + *samples_out++ = samples_in[3]; /* LFE, pass through */ + *samples_out++ = late[3]; /* Rear Left */ + *samples_out++ = late[4]; /* Rear Right */ + + samples_in += 6; + } + + return squared_sum; +} + +#undef OUTPUT_SAMPLE + +/* Reverb FAPO Implementation */ + +const FAudioGUID FAudioFX_CLSID_AudioReverb = /* 2.7 */ +{ + 0x6A93130E, + 0xCB4E, + 0x4CE1, + { + 0xA9, + 0xCF, + 0xE7, + 0x58, + 0x80, + 0x0B, + 0xB1, + 0x79 + } +}; + +static FAPORegistrationProperties ReverbProperties = +{ + /* .clsid = */ {0}, + /*.FriendlyName = */ + { + 'R', 'e', 'v', 'e', 'r', 'b', '\0' + }, + /*.CopyrightInfo = */ { + 'C', 'o', 'p', 'y', 'r', 'i', 'g', 'h', 't', ' ', '(', 'c', ')', + 'E', 't', 'h', 'a', 'n', ' ', 'L', 'e', 'e', '\0' + }, + /*.MajorVersion = */ 0, + /*.MinorVersion = */ 0, + /*.Flags = */ ( + FAPO_FLAG_FRAMERATE_MUST_MATCH | + FAPO_FLAG_BITSPERSAMPLE_MUST_MATCH | + FAPO_FLAG_BUFFERCOUNT_MUST_MATCH | + FAPO_FLAG_INPLACE_SUPPORTED + ), + /*.MinInputBufferCount = */ 1, + /*.MaxInputBufferCount = */ 1, + /*.MinOutputBufferCount = */ 1, + /*.MaxOutputBufferCount = */ 1 +}; + +typedef struct FAudioFXReverb +{ + FAPOBase base; + + uint16_t inChannels; + uint16_t outChannels; + uint32_t sampleRate; + uint16_t inBlockAlign; + uint16_t outBlockAlign; + + uint8_t apiVersion; + DspReverb reverb; +} FAudioFXReverb; + +static inline int8_t IsFloatFormat(const FAudioWaveFormatEx *format) +{ + if (format->wFormatTag == FAUDIO_FORMAT_IEEE_FLOAT) + { + /* Plain ol' WaveFormatEx */ + return 1; + } + + if (format->wFormatTag == FAUDIO_FORMAT_EXTENSIBLE) + { + /* WaveFormatExtensible, match GUID */ + #define MAKE_SUBFORMAT_GUID(guid, fmt) \ + static FAudioGUID KSDATAFORMAT_SUBTYPE_##guid = \ + { \ + (uint16_t) (fmt), 0x0000, 0x0010, \ + { \ + 0x80, 0x00, 0x00, 0xaa, \ + 0x00, 0x38, 0x9b, 0x71 \ + } \ + } + MAKE_SUBFORMAT_GUID(IEEE_FLOAT, 3); + #undef MAKE_SUBFORMAT_GUID + + if (FAudio_memcmp( + &((FAudioWaveFormatExtensible*) format)->SubFormat, + &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, + sizeof(FAudioGUID) + ) == 0) { + return 1; + } + } + + return 0; +} + +uint32_t FAudioFXReverb_IsInputFormatSupported( + FAPOBase *fapo, + const FAudioWaveFormatEx *pOutputFormat, + const FAudioWaveFormatEx *pRequestedInputFormat, + FAudioWaveFormatEx **ppSupportedInputFormat +) { + uint32_t result = 0; + +#define SET_SUPPORTED_FIELD(field, value) \ + result = 1; \ + if (ppSupportedInputFormat && *ppSupportedInputFormat) \ + { \ + (*ppSupportedInputFormat)->field = (value); \ + } + + /* Sample Rate */ + if (pOutputFormat->nSamplesPerSec != pRequestedInputFormat->nSamplesPerSec) + { + SET_SUPPORTED_FIELD(nSamplesPerSec, pOutputFormat->nSamplesPerSec); + } + + /* Data Type */ + if (!IsFloatFormat(pRequestedInputFormat)) + { + SET_SUPPORTED_FIELD(wFormatTag, FAUDIO_FORMAT_IEEE_FLOAT); + } + + /* Input/Output Channel Count */ + if (pOutputFormat->nChannels == 1 || pOutputFormat->nChannels == 2) + { + if (pRequestedInputFormat->nChannels != pOutputFormat->nChannels) + { + SET_SUPPORTED_FIELD(nChannels, pOutputFormat->nChannels); + } + } + else if (pOutputFormat->nChannels == 6) + { + if ( pRequestedInputFormat->nChannels != 1 && + pRequestedInputFormat->nChannels != 2 && + pRequestedInputFormat->nChannels != 6 ) + { + SET_SUPPORTED_FIELD(nChannels, 1); + } + } + else + { + SET_SUPPORTED_FIELD(nChannels, 1); + } + +#undef SET_SUPPORTED_FIELD + + return result; +} + + +uint32_t FAudioFXReverb_IsOutputFormatSupported( + FAPOBase *fapo, + const FAudioWaveFormatEx *pInputFormat, + const FAudioWaveFormatEx *pRequestedOutputFormat, + FAudioWaveFormatEx **ppSupportedOutputFormat +) { + uint32_t result = 0; + +#define SET_SUPPORTED_FIELD(field, value) \ + result = 1; \ + if (ppSupportedOutputFormat && *ppSupportedOutputFormat) \ + { \ + (*ppSupportedOutputFormat)->field = (value); \ + } + + /* Sample Rate */ + if (pInputFormat->nSamplesPerSec != pRequestedOutputFormat->nSamplesPerSec) + { + SET_SUPPORTED_FIELD(nSamplesPerSec, pInputFormat->nSamplesPerSec); + } + + /* Data Type */ + if (!IsFloatFormat(pRequestedOutputFormat)) + { + SET_SUPPORTED_FIELD(wFormatTag, FAUDIO_FORMAT_IEEE_FLOAT); + } + + /* Input/Output Channel Count */ + if (pInputFormat->nChannels == 1 || pInputFormat->nChannels == 2) + { + if ( pRequestedOutputFormat->nChannels != pInputFormat->nChannels && + pRequestedOutputFormat->nChannels != 6) + { + SET_SUPPORTED_FIELD(nChannels, pInputFormat->nChannels); + } + } + else if (pInputFormat->nChannels == 6) + { + if (pRequestedOutputFormat->nChannels != 6) + { + SET_SUPPORTED_FIELD(nChannels, pInputFormat->nChannels); + } + } + else + { + SET_SUPPORTED_FIELD(nChannels, 1); + } + +#undef SET_SUPPORTED_FIELD + + return result; +} + +uint32_t FAudioFXReverb_Initialize( + FAudioFXReverb *fapo, + const void* pData, + uint32_t DataByteSize +) { + #define INITPARAMS(offset) \ + FAudio_memcpy( \ + fapo->base.m_pParameterBlocks + DataByteSize * offset, \ + pData, \ + DataByteSize \ + ); + INITPARAMS(0) + INITPARAMS(1) + INITPARAMS(2) + #undef INITPARAMS + return 0; +} + +uint32_t FAudioFXReverb_LockForProcess( + FAudioFXReverb *fapo, + uint32_t InputLockedParameterCount, + const FAPOLockForProcessBufferParameters *pInputLockedParameters, + uint32_t OutputLockedParameterCount, + const FAPOLockForProcessBufferParameters *pOutputLockedParameters +) { + /* Reverb specific validation */ + if (!IsFloatFormat(pInputLockedParameters->pFormat)) + { + return FAPO_E_FORMAT_UNSUPPORTED; + } + + if ( pInputLockedParameters->pFormat->nSamplesPerSec < FAUDIOFX_REVERB_MIN_FRAMERATE || + pInputLockedParameters->pFormat->nSamplesPerSec > FAUDIOFX_REVERB_MAX_FRAMERATE ) + { + return FAPO_E_FORMAT_UNSUPPORTED; + } + + if (!( (pInputLockedParameters->pFormat->nChannels == 1 && + (pOutputLockedParameters->pFormat->nChannels == 1 || + pOutputLockedParameters->pFormat->nChannels == 6)) || + (pInputLockedParameters->pFormat->nChannels == 2 && + (pOutputLockedParameters->pFormat->nChannels == 2 || + pOutputLockedParameters->pFormat->nChannels == 6)) || + (pInputLockedParameters->pFormat->nChannels == 6 && + pOutputLockedParameters->pFormat->nChannels == 6))) + { + return FAPO_E_FORMAT_UNSUPPORTED; + } + + /* Save the things we care about */ + fapo->inChannels = pInputLockedParameters->pFormat->nChannels; + fapo->outChannels = pOutputLockedParameters->pFormat->nChannels; + fapo->sampleRate = pOutputLockedParameters->pFormat->nSamplesPerSec; + fapo->inBlockAlign = pInputLockedParameters->pFormat->nBlockAlign; + fapo->outBlockAlign = pOutputLockedParameters->pFormat->nBlockAlign; + + /* Create the network */ + DspReverb_Create( + &fapo->reverb, + fapo->sampleRate, + fapo->inChannels, + fapo->outChannels, + fapo->base.pMalloc + ); + + /* Call parent to do basic validation */ + return FAPOBase_LockForProcess( + &fapo->base, + InputLockedParameterCount, + pInputLockedParameters, + OutputLockedParameterCount, + pOutputLockedParameters + ); +} + +static inline void FAudioFXReverb_CopyBuffer( + FAudioFXReverb *fapo, + float *restrict buffer_in, + float *restrict buffer_out, + size_t frames_in +) { + /* In-place processing? */ + if (buffer_in == buffer_out) + { + return; + } + + /* equal channel count */ + if (fapo->inBlockAlign == fapo->outBlockAlign) + { + FAudio_memcpy( + buffer_out, + buffer_in, + fapo->inBlockAlign * frames_in + ); + return; + } + + /* 1 -> 5.1 */ + if (fapo->inChannels == 1 && fapo->outChannels == 6) + { + const float *in_end = buffer_in + frames_in; + while (buffer_in < in_end) + { + *buffer_out++ = *buffer_in; + *buffer_out++ = *buffer_in++; + *buffer_out++ = 0.0f; + *buffer_out++ = 0.0f; + *buffer_out++ = 0.0f; + *buffer_out++ = 0.0f; + } + return; + } + + /* 2 -> 5.1 */ + if (fapo->inChannels == 2 && fapo->outChannels == 6) + { + const float *in_end = buffer_in + (frames_in * 2); + while (buffer_in < in_end) + { + *buffer_out++ = *buffer_in++; + *buffer_out++ = *buffer_in++; + *buffer_out++ = 0.0f; + *buffer_out++ = 0.0f; + *buffer_out++ = 0.0f; + *buffer_out++ = 0.0f; + } + return; + } + + FAudio_assert(0 && "Unsupported channel combination"); + FAudio_zero(buffer_out, fapo->outBlockAlign * frames_in); +} + +void FAudioFXReverb_Process( + FAudioFXReverb *fapo, + uint32_t InputProcessParameterCount, + const FAPOProcessBufferParameters* pInputProcessParameters, + uint32_t OutputProcessParameterCount, + FAPOProcessBufferParameters* pOutputProcessParameters, + int32_t IsEnabled +) { + FAudioFXReverbParameters *params; + uint8_t update_params = FAPOBase_ParametersChanged(&fapo->base); + float total; + + /* Handle disabled filter */ + if (IsEnabled == 0) + { + pOutputProcessParameters->BufferFlags = pInputProcessParameters->BufferFlags; + + if (pOutputProcessParameters->BufferFlags != FAPO_BUFFER_SILENT) + { + FAudioFXReverb_CopyBuffer( + fapo, + (float*) pInputProcessParameters->pBuffer, + (float*) pOutputProcessParameters->pBuffer, + pInputProcessParameters->ValidFrameCount + ); + } + + return; + } + + /* XAudio2 passes a 'silent' buffer when no input buffer is available to play the effect tail */ + if (pInputProcessParameters->BufferFlags == FAPO_BUFFER_SILENT) + { + /* Make sure input data is usable. FIXME: Is this required? */ + FAudio_zero( + pInputProcessParameters->pBuffer, + pInputProcessParameters->ValidFrameCount * fapo->inBlockAlign + ); + } + + 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( \ + &fapo->reverb, \ + (float*) pInputProcessParameters->pBuffer, \ + (float*) pOutputProcessParameters->pBuffer, \ + pInputProcessParameters->ValidFrameCount * fapo->inChannels \ + ) + switch (fapo->reverb.out_channels) + { + case 1: + total = PROCESS(1, 1); + break; + case 2: + total = PROCESS(2, 2); + break; + default: /* 5.1 */ + if (fapo->reverb.in_channels == 1) + { + total = PROCESS(1, 5p1); + } + else if (fapo->reverb.in_channels == 2) + { + total = PROCESS(2, 5p1); + } + else /* 5.1 */ + { + total = PROCESS(5p1, 5p1); + } + break; + } + #undef PROCESS + + /* Set BufferFlags to silent so PLAY_TAILS knows when to stop */ + pOutputProcessParameters->BufferFlags = (total < 0.0000001f) ? + FAPO_BUFFER_SILENT : + FAPO_BUFFER_VALID; + + FAPOBase_EndProcess(&fapo->base); +} + +void FAudioFXReverb_Reset(FAudioFXReverb *fapo) +{ + int32_t i, c; + FAPOBase_Reset(&fapo->base); + + /* Reset the cached state of the reverb filter */ + DspDelay_Reset(&fapo->reverb.early_delay); + + for (i = 0; i < REVERB_COUNT_APF_IN; i += 1) + { + DspAllPass_Reset(&fapo->reverb.apf_in[i]); + } + + for (c = 0; c < fapo->reverb.reverb_channels; c += 1) + { + DspDelay_Reset(&fapo->reverb.channel[c].reverb_delay); + + for (i = 0; i < REVERB_COUNT_COMB; i += 1) + { + DspCombShelving_Reset(&fapo->reverb.channel[c].lpf_comb[i]); + } + + DspBiQuad_Reset(&fapo->reverb.channel[c].room_high_shelf); + + for (i = 0; i < REVERB_COUNT_APF_OUT; i += 1) + { + DspAllPass_Reset(&fapo->reverb.channel[c].apf_out[i]); + } + } +} + +void FAudioFXReverb_Free(void* fapo) +{ + FAudioFXReverb *reverb = (FAudioFXReverb*) fapo; + DspReverb_Destroy(&reverb->reverb, reverb->base.pFree); + reverb->base.pFree(reverb->base.m_pParameterBlocks); + reverb->base.pFree(fapo); +} + +/* Public API (Version 7) */ + +uint32_t FAudioCreateReverb(FAPO** ppApo, uint32_t Flags) +{ + return FAudioCreateReverbWithCustomAllocatorEXT( + ppApo, + Flags, + FAudio_malloc, + FAudio_free, + FAudio_realloc + ); +} + +uint32_t FAudioCreateReverbWithCustomAllocatorEXT( + FAPO** ppApo, + uint32_t Flags, + FAudioMallocFunc customMalloc, + FAudioFreeFunc customFree, + FAudioReallocFunc customRealloc +) { + const FAudioFXReverbParameters fxdefault = + { + FAUDIOFX_REVERB_DEFAULT_WET_DRY_MIX, + FAUDIOFX_REVERB_DEFAULT_REFLECTIONS_DELAY, + FAUDIOFX_REVERB_DEFAULT_REVERB_DELAY, + FAUDIOFX_REVERB_DEFAULT_REAR_DELAY, + FAUDIOFX_REVERB_DEFAULT_POSITION, + FAUDIOFX_REVERB_DEFAULT_POSITION, + FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX, + FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX, + FAUDIOFX_REVERB_DEFAULT_EARLY_DIFFUSION, + FAUDIOFX_REVERB_DEFAULT_LATE_DIFFUSION, + FAUDIOFX_REVERB_DEFAULT_LOW_EQ_GAIN, + FAUDIOFX_REVERB_DEFAULT_LOW_EQ_CUTOFF, + FAUDIOFX_REVERB_DEFAULT_HIGH_EQ_GAIN, + FAUDIOFX_REVERB_DEFAULT_HIGH_EQ_CUTOFF, + FAUDIOFX_REVERB_DEFAULT_ROOM_FILTER_FREQ, + FAUDIOFX_REVERB_DEFAULT_ROOM_FILTER_MAIN, + FAUDIOFX_REVERB_DEFAULT_ROOM_FILTER_HF, + FAUDIOFX_REVERB_DEFAULT_REFLECTIONS_GAIN, + FAUDIOFX_REVERB_DEFAULT_REVERB_GAIN, + FAUDIOFX_REVERB_DEFAULT_DECAY_TIME, + FAUDIOFX_REVERB_DEFAULT_DENSITY, + FAUDIOFX_REVERB_DEFAULT_ROOM_SIZE + }; + + /* Allocate... */ + FAudioFXReverb *result = (FAudioFXReverb*) customMalloc(sizeof(FAudioFXReverb)); + uint8_t *params = (uint8_t*) customMalloc( + 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( + &ReverbProperties.clsid, + &FAudioFX_CLSID_AudioReverb, + sizeof(FAudioGUID) + ); + CreateFAPOBaseWithCustomAllocatorEXT( + &result->base, + &ReverbProperties, + params, + sizeof(FAudioFXReverbParameters), + 0, + customMalloc, + customFree, + customRealloc + ); + + result->inChannels = 0; + result->outChannels = 0; + result->sampleRate = 0; + FAudio_zero(&result->reverb, sizeof(DspReverb)); + + /* Function table... */ + #define ASSIGN_VT(name) \ + result->base.base.name = (name##Func) FAudioFXReverb_##name; + ASSIGN_VT(LockForProcess); + ASSIGN_VT(IsInputFormatSupported); + ASSIGN_VT(IsOutputFormatSupported); + ASSIGN_VT(Initialize); + ASSIGN_VT(Reset); + ASSIGN_VT(Process); + result->base.Destructor = FAudioFXReverb_Free; + #undef ASSIGN_VT + + /* Finally. */ + *ppApo = &result->base.base; + return 0; +} + +void ReverbConvertI3DL2ToNative( + const FAudioFXReverbI3DL2Parameters *pI3DL2, + FAudioFXReverbParameters *pNative +) { + float reflectionsDelay; + float reverbDelay; + + pNative->RearDelay = FAUDIOFX_REVERB_DEFAULT_REAR_DELAY; + pNative->PositionLeft = FAUDIOFX_REVERB_DEFAULT_POSITION; + pNative->PositionRight = FAUDIOFX_REVERB_DEFAULT_POSITION; + pNative->PositionMatrixLeft = FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX; + pNative->PositionMatrixRight = FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX; + pNative->RoomSize = FAUDIOFX_REVERB_DEFAULT_ROOM_SIZE; + pNative->LowEQCutoff = 4; + pNative->HighEQCutoff = 6; + + pNative->RoomFilterMain = (float) pI3DL2->Room / 100.0f; + pNative->RoomFilterHF = (float) pI3DL2->RoomHF / 100.0f; + + if (pI3DL2->DecayHFRatio >= 1.0f) + { + int32_t index = (int32_t) (-4.0 * FAudio_log10(pI3DL2->DecayHFRatio)); + if (index < -8) + { + index = -8; + } + pNative->LowEQGain = (uint8_t) ((index < 0) ? index + 8 : 8); + pNative->HighEQGain = 8; + pNative->DecayTime = pI3DL2->DecayTime * pI3DL2->DecayHFRatio; + } + else + { + int32_t index = (int32_t) (4.0 * FAudio_log10(pI3DL2->DecayHFRatio)); + if (index < -8) + { + index = -8; + } + pNative->LowEQGain = 8; + pNative->HighEQGain = (uint8_t) ((index < 0) ? index + 8 : 8); + pNative->DecayTime = pI3DL2->DecayTime; + } + + reflectionsDelay = pI3DL2->ReflectionsDelay * 1000.0f; + if (reflectionsDelay >= FAUDIOFX_REVERB_MAX_REFLECTIONS_DELAY) + { + reflectionsDelay = (float) (FAUDIOFX_REVERB_MAX_REFLECTIONS_DELAY - 1); + } + else if (reflectionsDelay <= 1) + { + reflectionsDelay = 1; + } + pNative->ReflectionsDelay = (uint32_t) reflectionsDelay; + + reverbDelay = pI3DL2->ReverbDelay * 1000.0f; + if (reverbDelay >= FAUDIOFX_REVERB_MAX_REVERB_DELAY) + { + reverbDelay = (float) (FAUDIOFX_REVERB_MAX_REVERB_DELAY - 1); + } + pNative->ReverbDelay = (uint8_t) reverbDelay; + + pNative->ReflectionsGain = pI3DL2->Reflections / 100.0f; + pNative->ReverbGain = pI3DL2->Reverb / 100.0f; + pNative->EarlyDiffusion = (uint8_t) (15.0f * pI3DL2->Diffusion / 100.0f); + pNative->LateDiffusion = pNative->EarlyDiffusion; + pNative->Density = pI3DL2->Density; + pNative->RoomFilterFreq = pI3DL2->HFReference; + + pNative->WetDryMix = pI3DL2->WetDryMix; +} + +/* Public API (Version 9) */ + +uint32_t FAudioCreateReverb9(FAPO** ppApo, uint32_t Flags) +{ + return FAudioCreateReverb9WithCustomAllocatorEXT( + ppApo, + Flags, + FAudio_malloc, + FAudio_free, + FAudio_realloc + ); +} + +uint32_t FAudioCreateReverb9WithCustomAllocatorEXT( + FAPO** ppApo, + uint32_t Flags, + FAudioMallocFunc customMalloc, + FAudioFreeFunc customFree, + FAudioReallocFunc customRealloc +) { + const FAudioFXReverbParameters9 fxdefault = + { + FAUDIOFX_REVERB_DEFAULT_WET_DRY_MIX, + FAUDIOFX_REVERB_DEFAULT_REFLECTIONS_DELAY, + FAUDIOFX_REVERB_DEFAULT_REVERB_DELAY, + FAUDIOFX_REVERB_DEFAULT_REAR_DELAY, /* FIXME: 7POINT1? */ + FAUDIOFX_REVERB_DEFAULT_7POINT1_SIDE_DELAY, + FAUDIOFX_REVERB_DEFAULT_POSITION, + FAUDIOFX_REVERB_DEFAULT_POSITION, + FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX, + FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX, + FAUDIOFX_REVERB_DEFAULT_EARLY_DIFFUSION, + FAUDIOFX_REVERB_DEFAULT_LATE_DIFFUSION, + FAUDIOFX_REVERB_DEFAULT_LOW_EQ_GAIN, + FAUDIOFX_REVERB_DEFAULT_LOW_EQ_CUTOFF, + FAUDIOFX_REVERB_DEFAULT_HIGH_EQ_GAIN, + FAUDIOFX_REVERB_DEFAULT_HIGH_EQ_CUTOFF, + FAUDIOFX_REVERB_DEFAULT_ROOM_FILTER_FREQ, + FAUDIOFX_REVERB_DEFAULT_ROOM_FILTER_MAIN, + FAUDIOFX_REVERB_DEFAULT_ROOM_FILTER_HF, + FAUDIOFX_REVERB_DEFAULT_REFLECTIONS_GAIN, + FAUDIOFX_REVERB_DEFAULT_REVERB_GAIN, + FAUDIOFX_REVERB_DEFAULT_DECAY_TIME, + FAUDIOFX_REVERB_DEFAULT_DENSITY, + FAUDIOFX_REVERB_DEFAULT_ROOM_SIZE + }; + + /* Allocate... */ + FAudioFXReverb *result = (FAudioFXReverb*) customMalloc(sizeof(FAudioFXReverb)); + uint8_t *params = (uint8_t*) customMalloc( + 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( + &ReverbProperties.clsid, + &FAudioFX_CLSID_AudioReverb, + sizeof(FAudioGUID) + ); + CreateFAPOBaseWithCustomAllocatorEXT( + &result->base, + &ReverbProperties, + params, + sizeof(FAudioFXReverbParameters9), + 0, + customMalloc, + customFree, + customRealloc + ); + + result->inChannels = 0; + result->outChannels = 0; + result->sampleRate = 0; + FAudio_zero(&result->reverb, sizeof(DspReverb)); + + /* Function table... */ + #define ASSIGN_VT(name) \ + result->base.base.name = (name##Func) FAudioFXReverb_##name; + ASSIGN_VT(LockForProcess); + ASSIGN_VT(IsInputFormatSupported); + ASSIGN_VT(IsOutputFormatSupported); + ASSIGN_VT(Initialize); + ASSIGN_VT(Reset); + ASSIGN_VT(Process); + result->base.Destructor = FAudioFXReverb_Free; + #undef ASSIGN_VT + + /* Finally. */ + *ppApo = &result->base.base; + return 0; +} + +void ReverbConvertI3DL2ToNative9( + const FAudioFXReverbI3DL2Parameters *pI3DL2, + FAudioFXReverbParameters9 *pNative, + int32_t sevenDotOneReverb +) { + float reflectionsDelay; + float reverbDelay; + + if (sevenDotOneReverb) + { + pNative->RearDelay = FAUDIOFX_REVERB_DEFAULT_7POINT1_REAR_DELAY; + } + else + { + pNative->RearDelay = FAUDIOFX_REVERB_DEFAULT_REAR_DELAY; + } + pNative->SideDelay = FAUDIOFX_REVERB_DEFAULT_7POINT1_SIDE_DELAY; + pNative->PositionLeft = FAUDIOFX_REVERB_DEFAULT_POSITION; + pNative->PositionRight = FAUDIOFX_REVERB_DEFAULT_POSITION; + pNative->PositionMatrixLeft = FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX; + pNative->PositionMatrixRight = FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX; + pNative->RoomSize = FAUDIOFX_REVERB_DEFAULT_ROOM_SIZE; + pNative->LowEQCutoff = 4; + pNative->HighEQCutoff = 6; + + pNative->RoomFilterMain = (float) pI3DL2->Room / 100.0f; + pNative->RoomFilterHF = (float) pI3DL2->RoomHF / 100.0f; + + if (pI3DL2->DecayHFRatio >= 1.0f) + { + int32_t index = (int32_t) (-4.0 * FAudio_log10(pI3DL2->DecayHFRatio)); + if (index < -8) + { + index = -8; + } + pNative->LowEQGain = (uint8_t) ((index < 0) ? index + 8 : 8); + pNative->HighEQGain = 8; + pNative->DecayTime = pI3DL2->DecayTime * pI3DL2->DecayHFRatio; + } + else + { + int32_t index = (int32_t) (4.0 * FAudio_log10(pI3DL2->DecayHFRatio)); + if (index < -8) + { + index = -8; + } + pNative->LowEQGain = 8; + pNative->HighEQGain = (uint8_t) ((index < 0) ? index + 8 : 8); + pNative->DecayTime = pI3DL2->DecayTime; + } + + reflectionsDelay = pI3DL2->ReflectionsDelay * 1000.0f; + if (reflectionsDelay >= FAUDIOFX_REVERB_MAX_REFLECTIONS_DELAY) + { + reflectionsDelay = (float) (FAUDIOFX_REVERB_MAX_REFLECTIONS_DELAY - 1); + } + else if (reflectionsDelay <= 1) + { + reflectionsDelay = 1; + } + pNative->ReflectionsDelay = (uint32_t) reflectionsDelay; + + reverbDelay = pI3DL2->ReverbDelay * 1000.0f; + if (reverbDelay >= FAUDIOFX_REVERB_MAX_REVERB_DELAY) + { + reverbDelay = (float) (FAUDIOFX_REVERB_MAX_REVERB_DELAY - 1); + } + pNative->ReverbDelay = (uint8_t) reverbDelay; + + pNative->ReflectionsGain = pI3DL2->Reflections / 100.0f; + pNative->ReverbGain = pI3DL2->Reverb / 100.0f; + pNative->EarlyDiffusion = (uint8_t) (15.0f * pI3DL2->Diffusion / 100.0f); + pNative->LateDiffusion = pNative->EarlyDiffusion; + pNative->Density = pI3DL2->Density; + pNative->RoomFilterFreq = pI3DL2->HFReference; + + pNative->WetDryMix = pI3DL2->WetDryMix; +} + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/src/FAudioFX_volumemeter.c b/libs/faudio/src/FAudioFX_volumemeter.c new file mode 100644 index 00000000000..ded77b9908e --- /dev/null +++ b/libs/faudio/src/FAudioFX_volumemeter.c @@ -0,0 +1,281 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * Ethan "flibitijibibo" Lee + * + */ + +#include "FAudioFX.h" +#include "FAudio_internal.h" + +/* Volume Meter FAPO Implementation */ + +const FAudioGUID FAudioFX_CLSID_AudioVolumeMeter = /* 2.7 */ +{ + 0xCAC1105F, + 0x619B, + 0x4D04, + { + 0x83, + 0x1A, + 0x44, + 0xE1, + 0xCB, + 0xF1, + 0x2D, + 0x57 + } +}; + +static FAPORegistrationProperties VolumeMeterProperties = +{ + /* .clsid = */ {0}, + /* .FriendlyName = */ + { + 'V', 'o', 'l', 'u', 'm', 'e', 'M', 'e', 't', 'e', 'r', '\0' + }, + /*.CopyrightInfo = */ + { + 'C', 'o', 'p', 'y', 'r', 'i', 'g', 'h', 't', ' ', '(', 'c', ')', + 'E', 't', 'h', 'a', 'n', ' ', 'L', 'e', 'e', '\0' + }, + /*.MajorVersion = */ 0, + /*.MinorVersion = */ 0, + /*.Flags = */( + FAPO_FLAG_CHANNELS_MUST_MATCH | + FAPO_FLAG_FRAMERATE_MUST_MATCH | + FAPO_FLAG_BITSPERSAMPLE_MUST_MATCH | + FAPO_FLAG_BUFFERCOUNT_MUST_MATCH | + FAPO_FLAG_INPLACE_SUPPORTED | + FAPO_FLAG_INPLACE_REQUIRED + ), + /*.MinInputBufferCount = */ 1, + /*.MaxInputBufferCount = */ 1, + /*.MinOutputBufferCount = */ 1, + /*.MaxOutputBufferCount =*/ 1 +}; + +typedef struct FAudioFXVolumeMeter +{ + FAPOBase base; + uint16_t channels; +} FAudioFXVolumeMeter; + +uint32_t FAudioFXVolumeMeter_LockForProcess( + FAudioFXVolumeMeter *fapo, + uint32_t InputLockedParameterCount, + const FAPOLockForProcessBufferParameters *pInputLockedParameters, + uint32_t OutputLockedParameterCount, + const FAPOLockForProcessBufferParameters *pOutputLockedParameters +) { + FAudioFXVolumeMeterLevels *levels = (FAudioFXVolumeMeterLevels*) + fapo->base.m_pParameterBlocks; + + /* Verify parameter counts... */ + if ( InputLockedParameterCount < fapo->base.m_pRegistrationProperties->MinInputBufferCount || + InputLockedParameterCount > fapo->base.m_pRegistrationProperties->MaxInputBufferCount || + OutputLockedParameterCount < fapo->base.m_pRegistrationProperties->MinOutputBufferCount || + OutputLockedParameterCount > fapo->base.m_pRegistrationProperties->MaxOutputBufferCount ) + { + return FAUDIO_E_INVALID_ARG; + } + + + /* Validate input/output formats */ + #define VERIFY_FORMAT_FLAG(flag, prop) \ + if ( (fapo->base.m_pRegistrationProperties->Flags & flag) && \ + (pInputLockedParameters->pFormat->prop != pOutputLockedParameters->pFormat->prop) ) \ + { \ + return FAUDIO_E_INVALID_ARG; \ + } + VERIFY_FORMAT_FLAG(FAPO_FLAG_CHANNELS_MUST_MATCH, nChannels) + VERIFY_FORMAT_FLAG(FAPO_FLAG_FRAMERATE_MUST_MATCH, nSamplesPerSec) + VERIFY_FORMAT_FLAG(FAPO_FLAG_BITSPERSAMPLE_MUST_MATCH, wBitsPerSample) + #undef VERIFY_FORMAT_FLAG + if ( (fapo->base.m_pRegistrationProperties->Flags & FAPO_FLAG_BUFFERCOUNT_MUST_MATCH) && + (InputLockedParameterCount != OutputLockedParameterCount) ) + { + return FAUDIO_E_INVALID_ARG; + } + + /* Allocate volume meter arrays */ + fapo->channels = pInputLockedParameters->pFormat->nChannels; + levels[0].pPeakLevels = (float*) fapo->base.pMalloc( + fapo->channels * sizeof(float) * 6 + ); + FAudio_zero(levels[0].pPeakLevels, fapo->channels * sizeof(float) * 6); + levels[0].pRMSLevels = levels[0].pPeakLevels + fapo->channels; + levels[1].pPeakLevels = levels[0].pPeakLevels + (fapo->channels * 2); + levels[1].pRMSLevels = levels[0].pPeakLevels + (fapo->channels * 3); + levels[2].pPeakLevels = levels[0].pPeakLevels + (fapo->channels * 4); + levels[2].pRMSLevels = levels[0].pPeakLevels + (fapo->channels * 5); + + fapo->base.m_fIsLocked = 1; + return 0; +} + +void FAudioFXVolumeMeter_UnlockForProcess(FAudioFXVolumeMeter *fapo) +{ + FAudioFXVolumeMeterLevels *levels = (FAudioFXVolumeMeterLevels*) + fapo->base.m_pParameterBlocks; + fapo->base.pFree(levels[0].pPeakLevels); + fapo->base.m_fIsLocked = 0; +} + +void FAudioFXVolumeMeter_Process( + FAudioFXVolumeMeter *fapo, + uint32_t InputProcessParameterCount, + const FAPOProcessBufferParameters* pInputProcessParameters, + uint32_t OutputProcessParameterCount, + FAPOProcessBufferParameters* pOutputProcessParameters, + int32_t IsEnabled +) { + float peak; + float total; + float *buffer; + uint32_t i, j; + FAudioFXVolumeMeterLevels *levels = (FAudioFXVolumeMeterLevels*) + FAPOBase_BeginProcess(&fapo->base); + + /* TODO: This could probably be SIMD-ified... */ + for (i = 0; i < fapo->channels; i += 1) + { + peak = 0.0f; + total = 0.0f; + buffer = ((float*) pInputProcessParameters->pBuffer) + i; + for (j = 0; j < pInputProcessParameters->ValidFrameCount; j += 1, buffer += fapo->channels) + { + const float sampleAbs = FAudio_fabsf(*buffer); + if (sampleAbs > peak) + { + peak = sampleAbs; + } + total += (*buffer) * (*buffer); + } + levels->pPeakLevels[i] = peak; + levels->pRMSLevels[i] = FAudio_sqrtf( + total / pInputProcessParameters->ValidFrameCount + ); + } + + FAPOBase_EndProcess(&fapo->base); +} + +void FAudioFXVolumeMeter_GetParameters( + FAudioFXVolumeMeter *fapo, + FAudioFXVolumeMeterLevels *pParameters, + uint32_t ParameterByteSize +) { + FAudioFXVolumeMeterLevels *levels = (FAudioFXVolumeMeterLevels*) + fapo->base.m_pCurrentParameters; + FAudio_assert(ParameterByteSize == sizeof(FAudioFXVolumeMeterLevels)); + FAudio_assert(pParameters->ChannelCount == fapo->channels); + + /* Copy what's current as of the last Process */ + if (pParameters->pPeakLevels != NULL) + { + FAudio_memcpy( + pParameters->pPeakLevels, + levels->pPeakLevels, + fapo->channels * sizeof(float) + ); + } + if (pParameters->pRMSLevels != NULL) + { + FAudio_memcpy( + pParameters->pRMSLevels, + levels->pRMSLevels, + fapo->channels * sizeof(float) + ); + } +} + +void FAudioFXVolumeMeter_Free(void* fapo) +{ + FAudioFXVolumeMeter *volumemeter = (FAudioFXVolumeMeter*) fapo; + volumemeter->base.pFree(volumemeter->base.m_pParameterBlocks); + volumemeter->base.pFree(fapo); +} + +/* Public API */ + +uint32_t FAudioCreateVolumeMeter(FAPO** ppApo, uint32_t Flags) +{ + return FAudioCreateVolumeMeterWithCustomAllocatorEXT( + ppApo, + Flags, + FAudio_malloc, + FAudio_free, + FAudio_realloc + ); +} + +uint32_t FAudioCreateVolumeMeterWithCustomAllocatorEXT( + FAPO** ppApo, + uint32_t Flags, + FAudioMallocFunc customMalloc, + FAudioFreeFunc customFree, + FAudioReallocFunc customRealloc +) { + /* Allocate... */ + FAudioFXVolumeMeter *result = (FAudioFXVolumeMeter*) customMalloc( + sizeof(FAudioFXVolumeMeter) + ); + uint8_t *params = (uint8_t*) customMalloc( + sizeof(FAudioFXVolumeMeterLevels) * 3 + ); + FAudio_zero(params, sizeof(FAudioFXVolumeMeterLevels) * 3); + + /* Initialize... */ + FAudio_memcpy( + &VolumeMeterProperties.clsid, + &FAudioFX_CLSID_AudioVolumeMeter, + sizeof(FAudioGUID) + ); + CreateFAPOBaseWithCustomAllocatorEXT( + &result->base, + &VolumeMeterProperties, + params, + sizeof(FAudioFXVolumeMeterLevels), + 1, + customMalloc, + customFree, + customRealloc + ); + + /* Function table... */ + result->base.base.LockForProcess = (LockForProcessFunc) + FAudioFXVolumeMeter_LockForProcess; + result->base.base.UnlockForProcess = (UnlockForProcessFunc) + FAudioFXVolumeMeter_UnlockForProcess; + result->base.base.Process = (ProcessFunc) + FAudioFXVolumeMeter_Process; + result->base.base.GetParameters = (GetParametersFunc) + FAudioFXVolumeMeter_GetParameters; + result->base.Destructor = FAudioFXVolumeMeter_Free; + + /* Finally. */ + *ppApo = &result->base.base; + return 0; +} + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/src/FAudio_internal.c b/libs/faudio/src/FAudio_internal.c new file mode 100644 index 00000000000..6ab0ce98bc2 --- /dev/null +++ b/libs/faudio/src/FAudio_internal.c @@ -0,0 +1,1967 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * Ethan "flibitijibibo" Lee + * + */ + +#include "FAudio_internal.h" + +#ifndef FAUDIO_DISABLE_DEBUGCONFIGURATION +void WINAPIV FAudio_INTERNAL_debug( + FAudio *audio, + const char *file, + uint32_t line, + const char *func, + const char *fmt, + ... +) { + char output[1024]; + char *out = output; + va_list va; + out[0] = '\0'; + + /* Logging extras */ + if (audio->debug.LogThreadID) + { + out += FAudio_snprintf( + out, + sizeof(output) - (out - output), + "0x%" FAudio_PRIx64 " ", + FAudio_PlatformGetThreadID() + ); + } + if (audio->debug.LogFileline) + { + out += FAudio_snprintf( + out, + sizeof(output) - (out - output), + "%s:%u ", + file, + line + ); + } + if (audio->debug.LogFunctionName) + { + out += FAudio_snprintf( + out, + sizeof(output) - (out - output), + "%s ", + func + ); + } + if (audio->debug.LogTiming) + { + out += FAudio_snprintf( + out, + sizeof(output) - (out - output), + "%dms ", + FAudio_timems() + ); + } + + /* The actual message... */ + va_start(va, fmt); + FAudio_vsnprintf( + out, + sizeof(output) - (out - output), + fmt, + va + ); + va_end(va); + + /* Print, finally. */ + FAudio_Log(output); +} + +static const char *get_wformattag_string(const FAudioWaveFormatEx *fmt) +{ +#define FMT_STRING(suffix) \ + if (fmt->wFormatTag == FAUDIO_FORMAT_##suffix) \ + { \ + return #suffix; \ + } + FMT_STRING(PCM) + FMT_STRING(MSADPCM) + FMT_STRING(IEEE_FLOAT) + FMT_STRING(XMAUDIO2) + FMT_STRING(WMAUDIO2) + FMT_STRING(EXTENSIBLE) +#undef FMT_STRING + return "UNKNOWN!"; +} + +static const char *get_subformat_string(const FAudioWaveFormatEx *fmt) +{ + const FAudioWaveFormatExtensible *fmtex = (const FAudioWaveFormatExtensible*) fmt; + + if (fmt->wFormatTag != FAUDIO_FORMAT_EXTENSIBLE) + { + return "N/A"; + } + if (!FAudio_memcmp(&fmtex->SubFormat, &DATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(FAudioGUID))) + { + return "IEEE_FLOAT"; + } + if (!FAudio_memcmp(&fmtex->SubFormat, &DATAFORMAT_SUBTYPE_PCM, sizeof(FAudioGUID))) + { + return "PCM"; + } + return "UNKNOWN!"; +} + +void FAudio_INTERNAL_debug_fmt( + FAudio *audio, + const char *file, + uint32_t line, + const char *func, + const FAudioWaveFormatEx *fmt +) { + FAudio_INTERNAL_debug( + audio, + file, + line, + func, + ( + "{" + "wFormatTag: 0x%x %s, " + "nChannels: %u, " + "nSamplesPerSec: %u, " + "wBitsPerSample: %u, " + "nBlockAlign: %u, " + "SubFormat: %s" + "}" + ), + fmt->wFormatTag, + get_wformattag_string(fmt), + fmt->nChannels, + fmt->nSamplesPerSec, + fmt->wBitsPerSample, + fmt->nBlockAlign, + get_subformat_string(fmt) + ); +} +#endif /* FAUDIO_DISABLE_DEBUGCONFIGURATION */ + +void LinkedList_AddEntry( + LinkedList **start, + void* toAdd, + FAudioMutex lock, + FAudioMallocFunc pMalloc +) { + LinkedList *newEntry, *latest; + newEntry = (LinkedList*) pMalloc(sizeof(LinkedList)); + newEntry->entry = toAdd; + newEntry->next = NULL; + FAudio_PlatformLockMutex(lock); + if (*start == NULL) + { + *start = newEntry; + } + else + { + latest = *start; + while (latest->next != NULL) + { + latest = latest->next; + } + latest->next = newEntry; + } + FAudio_PlatformUnlockMutex(lock); +} + +void LinkedList_PrependEntry( + LinkedList **start, + void* toAdd, + FAudioMutex lock, + FAudioMallocFunc pMalloc +) { + LinkedList *newEntry; + newEntry = (LinkedList*) pMalloc(sizeof(LinkedList)); + newEntry->entry = toAdd; + FAudio_PlatformLockMutex(lock); + newEntry->next = *start; + *start = newEntry; + FAudio_PlatformUnlockMutex(lock); +} + +void LinkedList_RemoveEntry( + LinkedList **start, + void* toRemove, + FAudioMutex lock, + FAudioFreeFunc pFree +) { + LinkedList *latest, *prev; + latest = *start; + prev = latest; + FAudio_PlatformLockMutex(lock); + while (latest != NULL) + { + if (latest->entry == toRemove) + { + if (latest == prev) /* First in list */ + { + *start = latest->next; + } + else + { + prev->next = latest->next; + } + pFree(latest); + FAudio_PlatformUnlockMutex(lock); + return; + } + prev = latest; + latest = latest->next; + } + FAudio_PlatformUnlockMutex(lock); + FAudio_assert(0 && "LinkedList element not found!"); +} + +void FAudio_INTERNAL_InsertSubmixSorted( + LinkedList **start, + FAudioSubmixVoice *toAdd, + FAudioMutex lock, + FAudioMallocFunc pMalloc +) { + LinkedList *newEntry, *latest; + newEntry = (LinkedList*) pMalloc(sizeof(LinkedList)); + newEntry->entry = toAdd; + newEntry->next = NULL; + FAudio_PlatformLockMutex(lock); + if (*start == NULL) + { + *start = newEntry; + } + else + { + latest = *start; + + /* Special case if the new stage is lower than everyone else */ + if (toAdd->mix.processingStage < ((FAudioSubmixVoice*) latest->entry)->mix.processingStage) + { + newEntry->next = latest; + *start = newEntry; + } + else + { + /* If we got here, we know that the new stage is + * _at least_ as high as the first submix in the list. + * + * Each loop iteration checks to see if the new stage + * is smaller than `latest->next`, meaning it fits + * between `latest` and `latest->next`. + */ + while (latest->next != NULL) + { + if (toAdd->mix.processingStage < ((FAudioSubmixVoice *) latest->next->entry)->mix.processingStage) + { + newEntry->next = latest->next; + latest->next = newEntry; + break; + } + latest = latest->next; + } + /* If newEntry didn't get a `next` value, that means + * it didn't fall in between any stages and `latest` + * is the last entry in the list. Add it to the end! + */ + if (newEntry->next == NULL) + { + latest->next = newEntry; + } + } + } + FAudio_PlatformUnlockMutex(lock); +} + +static uint32_t FAudio_INTERNAL_GetBytesRequested( + FAudioSourceVoice *voice, + uint32_t decoding +) { + uint32_t end, result; + FAudioBuffer *buffer; + FAudioWaveFormatExtensible *fmt; + FAudioBufferEntry *list = voice->src.bufferList; + + LOG_FUNC_ENTER(voice->audio) + +#ifdef HAVE_WMADEC + if (voice->src.wmadec != NULL) + { + /* Always 0, per the spec */ + LOG_FUNC_EXIT(voice->audio) + return 0; + } +#endif /* HAVE_WMADEC */ + while (list != NULL && decoding > 0) + { + buffer = &list->buffer; + if (buffer->LoopCount > 0) + { + end = ( + /* Current loop... */ + ((buffer->LoopBegin + buffer->LoopLength) - voice->src.curBufferOffset) + + /* Remaining loops... */ + (buffer->LoopLength * buffer->LoopCount - 1) + + /* ... Final iteration */ + buffer->PlayLength + ); + } + else + { + end = (buffer->PlayBegin + buffer->PlayLength) - voice->src.curBufferOffset; + } + if (end > decoding) + { + decoding = 0; + break; + } + decoding -= end; + list = list->next; + } + + /* Convert samples to bytes, factoring block alignment */ + if (voice->src.format->wFormatTag == FAUDIO_FORMAT_MSADPCM) + { + fmt = (FAudioWaveFormatExtensible*) voice->src.format; + result = ( + (decoding / fmt->Samples.wSamplesPerBlock) + + ((decoding % fmt->Samples.wSamplesPerBlock) > 0) + ) * voice->src.format->nBlockAlign; + } + else + { + result = decoding * voice->src.format->nBlockAlign; + } + + LOG_FUNC_EXIT(voice->audio) + return result; +} + +static void FAudio_INTERNAL_DecodeBuffers( + FAudioSourceVoice *voice, + uint64_t *toDecode +) { + uint32_t end, endRead, decoding, decoded = 0; + FAudioBuffer *buffer = &voice->src.bufferList->buffer; + FAudioBufferEntry *toDelete; + + LOG_FUNC_ENTER(voice->audio) + + /* This should never go past the max ratio size */ + FAudio_assert(*toDecode <= voice->src.decodeSamples); + + while (decoded < *toDecode && buffer != NULL) + { + decoding = (uint32_t) *toDecode - decoded; + + /* Start-of-buffer behavior */ + if (voice->src.newBuffer) + { + voice->src.newBuffer = 0; + if ( voice->src.callback != NULL && + voice->src.callback->OnBufferStart != NULL ) + { + 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) + } + } + + /* Check for end-of-buffer */ + end = (buffer->LoopCount > 0) ? + (buffer->LoopBegin + buffer->LoopLength) : + buffer->PlayBegin + buffer->PlayLength; + endRead = FAudio_min( + end - voice->src.curBufferOffset, + decoding + ); + + /* Decode... */ + voice->src.decode( + voice, + buffer, + voice->audio->decodeCache + ( + decoded * voice->src.format->nChannels + ), + endRead + ); + + LOG_INFO( + voice->audio, + "Voice %p, buffer %p, decoded %u samples from [%u,%u)", + (void*) voice, + (void*) buffer, + endRead, + voice->src.curBufferOffset, + voice->src.curBufferOffset + endRead + ) + + decoded += endRead; + voice->src.curBufferOffset += endRead; + voice->src.totalSamples += endRead; + + /* End-of-buffer behavior */ + if (endRead < decoding) + { + if (buffer->LoopCount > 0) + { + voice->src.curBufferOffset = buffer->LoopBegin; + if (buffer->LoopCount < FAUDIO_LOOP_INFINITE) + { + buffer->LoopCount -= 1; + } + if ( voice->src.callback != NULL && + voice->src.callback->OnLoopEnd != NULL ) + { + FAudio_PlatformUnlockMutex(voice->audio->sourceLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock) + + voice->src.callback->OnLoopEnd( + voice->src.callback, + buffer->pContext + ); + + FAudio_PlatformLockMutex(voice->audio->sourceLock); + LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock) + } + } + else + { +#ifdef HAVE_WMADEC + if (voice->src.wmadec != NULL) + { + FAudio_WMADEC_end_buffer(voice); + } +#endif /* HAVE_WMADEC */ + /* For EOS we can stop storing fraction offsets */ + if (buffer->Flags & FAUDIO_END_OF_STREAM) + { + voice->src.curBufferOffsetDec = 0; + voice->src.totalSamples = 0; + } + + LOG_INFO( + voice->audio, + "Voice %p, finished with buffer %p", + (void*) voice, + (void*) buffer + ) + + /* Change active buffer, delete finished buffer */ + toDelete = voice->src.bufferList; + voice->src.bufferList = voice->src.bufferList->next; + if (voice->src.bufferList != NULL) + { + buffer = &voice->src.bufferList->buffer; + voice->src.curBufferOffset = buffer->PlayBegin; + } + else + { + buffer = NULL; + + /* FIXME: I keep going past the buffer so fuck it */ + FAudio_zero( + voice->audio->decodeCache + ( + decoded * + voice->src.format->nChannels + ), + sizeof(float) * ( + (*toDecode - decoded) * + voice->src.format->nChannels + ) + ); + } + + /* Callbacks */ + if (voice->src.callback != NULL) + { + FAudio_PlatformUnlockMutex(voice->audio->sourceLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock) + + if (voice->src.callback->OnBufferEnd != NULL) + { + voice->src.callback->OnBufferEnd( + voice->src.callback, + toDelete->buffer.pContext + ); + } + if ( toDelete->buffer.Flags & FAUDIO_END_OF_STREAM && + voice->src.callback->OnStreamEnd != NULL ) + { + voice->src.callback->OnStreamEnd( + voice->src.callback + ); + } + + /* One last chance at redemption */ + if (buffer == NULL && voice->src.bufferList != NULL) + { + buffer = &voice->src.bufferList->buffer; + voice->src.curBufferOffset = buffer->PlayBegin; + } + + if (buffer != NULL && voice->src.callback->OnBufferStart != NULL) + { + voice->src.callback->OnBufferStart( + voice->src.callback, + buffer->pContext + ); + } + + FAudio_PlatformLockMutex(voice->audio->sourceLock); + LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock) + } + + voice->audio->pFree(toDelete); + } + } + } + + /* ... FIXME: I keep going past the buffer so fuck it */ + if (buffer) + { + end = (buffer->LoopCount > 0) ? + (buffer->LoopBegin + buffer->LoopLength) : + buffer->PlayBegin + buffer->PlayLength; + endRead = FAudio_min( + end - voice->src.curBufferOffset, + EXTRA_DECODE_PADDING + ); + + voice->src.decode( + voice, + buffer, + voice->audio->decodeCache + ( + decoded * voice->src.format->nChannels + ), + endRead + ); + /* Do NOT increment curBufferOffset! */ + + if (endRead < EXTRA_DECODE_PADDING) + { + FAudio_zero( + voice->audio->decodeCache + ( + decoded * voice->src.format->nChannels + ), + sizeof(float) * ( + (EXTRA_DECODE_PADDING - endRead) * + voice->src.format->nChannels + ) + ); + } + } + else + { + FAudio_zero( + voice->audio->decodeCache + ( + decoded * voice->src.format->nChannels + ), + sizeof(float) * ( + EXTRA_DECODE_PADDING * + voice->src.format->nChannels + ) + ); + } + + *toDecode = decoded; + LOG_FUNC_EXIT(voice->audio) +} + +static inline void FAudio_INTERNAL_FilterVoice( + FAudio *audio, + const FAudioFilterParameters *filter, + FAudioFilterState *filterState, + float *samples, + uint32_t numSamples, + uint16_t numChannels +) { + uint32_t j, ci; + + LOG_FUNC_ENTER(audio) + + /* Apply a digital state-variable filter to the voice. + * The difference equations of the filter are: + * + * Yl(n) = F Yb(n - 1) + Yl(n - 1) + * Yh(n) = x(n) - Yl(n) - OneOverQ Yb(n - 1) + * Yb(n) = F Yh(n) + Yb(n - 1) + * Yn(n) = Yl(n) + Yh(n) + * + * Please note that FAudioFilterParameters.Frequency is defined as: + * + * (2 * sin(pi * (desired filter cutoff frequency) / sampleRate)) + * + * - @JohanSmet + */ + + for (j = 0; j < numSamples; j += 1) + for (ci = 0; ci < numChannels; ci += 1) + { + filterState[ci][FAudioLowPassFilter] = filterState[ci][FAudioLowPassFilter] + (filter->Frequency * filterState[ci][FAudioBandPassFilter]); + 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]; + } + + LOG_FUNC_EXIT(audio) +} + +static void FAudio_INTERNAL_ResizeEffectChainCache(FAudio *audio, uint32_t samples) +{ + LOG_FUNC_ENTER(audio) + if (samples > audio->effectChainSamples) + { + audio->effectChainSamples = samples; + audio->effectChainCache = (float*) audio->pRealloc( + audio->effectChainCache, + sizeof(float) * audio->effectChainSamples + ); + } + LOG_FUNC_EXIT(audio) +} + +static inline float *FAudio_INTERNAL_ProcessEffectChain( + FAudioVoice *voice, + float *buffer, + uint32_t *samples +) { + uint32_t i; + FAPO *fapo; + FAPOProcessBufferParameters srcParams, dstParams; + + LOG_FUNC_ENTER(voice->audio) + + /* Set up the buffer to be written into */ + srcParams.pBuffer = buffer; + srcParams.BufferFlags = FAPO_BUFFER_SILENT; + srcParams.ValidFrameCount = *samples; + for (i = 0; i < srcParams.ValidFrameCount; i += 1) + { + if (buffer[i] != 0.0f) /* Arbitrary! */ + { + srcParams.BufferFlags = FAPO_BUFFER_VALID; + break; + } + } + + /* Initialize output parameters to something sane */ + dstParams.pBuffer = srcParams.pBuffer; + dstParams.BufferFlags = FAPO_BUFFER_VALID; + dstParams.ValidFrameCount = srcParams.ValidFrameCount; + + /* Update parameters, process! */ + for (i = 0; i < voice->effects.count; i += 1) + { + fapo = voice->effects.desc[i].pEffect; + + if (!voice->effects.inPlaceProcessing[i]) + { + if (dstParams.pBuffer == buffer) + { + FAudio_INTERNAL_ResizeEffectChainCache( + voice->audio, + voice->effects.desc[i].OutputChannels * srcParams.ValidFrameCount + ); + dstParams.pBuffer = voice->audio->effectChainCache; + } + else + { + /* FIXME: What if this is smaller because + * inputChannels < desc[i].OutputChannels? + */ + dstParams.pBuffer = buffer; + } + + FAudio_zero( + dstParams.pBuffer, + voice->effects.desc[i].OutputChannels * srcParams.ValidFrameCount * sizeof(float) + ); + } + + if (voice->effects.parameterUpdates[i]) + { + fapo->SetParameters( + fapo, + voice->effects.parameters[i], + voice->effects.parameterSizes[i] + ); + voice->effects.parameterUpdates[i] = 0; + } + + fapo->Process( + fapo, + 1, + &srcParams, + 1, + &dstParams, + voice->effects.desc[i].InitialState + ); + + FAudio_memcpy(&srcParams, &dstParams, sizeof(dstParams)); + } + + *samples = dstParams.ValidFrameCount; + + /* Save the output buffer-flags so the mixer-function can determine when it's save to stop processing the effect chain */ + voice->effects.state = dstParams.BufferFlags; + + LOG_FUNC_EXIT(voice->audio) + return (float*) dstParams.pBuffer; +} + +static void FAudio_INTERNAL_ResizeResampleCache(FAudio *audio, uint32_t samples) +{ + LOG_FUNC_ENTER(audio) + if (samples > audio->resampleSamples) + { + audio->resampleSamples = samples; + audio->resampleCache = (float*) audio->pRealloc( + audio->resampleCache, + sizeof(float) * audio->resampleSamples + ); + } + LOG_FUNC_EXIT(audio) +} + +static void FAudio_INTERNAL_MixSource(FAudioSourceVoice *voice) +{ + /* Iterators */ + uint32_t i; + /* Decode/Resample variables */ + uint64_t toDecode; + uint64_t toResample; + /* Output mix variables */ + float *stream; + uint32_t mixed; + uint32_t oChan; + FAudioVoice *out; + uint32_t outputRate; + double stepd; + float *finalSamples; + + LOG_FUNC_ENTER(voice->audio) + + FAudio_PlatformLockMutex(voice->sendLock); + LOG_MUTEX_LOCK(voice->audio, voice->sendLock) + + /* Calculate the resample stepping value */ + if (voice->src.resampleFreq != voice->src.freqRatio * voice->src.format->nSamplesPerSec) + { + out = (voice->sends.SendCount == 0) ? + voice->audio->master : /* Barf */ + voice->sends.pSends->pOutputVoice; + outputRate = (out->type == FAUDIO_VOICE_MASTER) ? + out->master.inputSampleRate : + out->mix.inputSampleRate; + stepd = ( + voice->src.freqRatio * + (double) voice->src.format->nSamplesPerSec / + (double) outputRate + ); + voice->src.resampleStep = DOUBLE_TO_FIXED(stepd); + voice->src.resampleFreq = voice->src.freqRatio * voice->src.format->nSamplesPerSec; + } + + if (voice->src.active == 2) + { + /* We're just playing tails, skip all buffer stuff */ + FAudio_INTERNAL_ResizeResampleCache( + voice->audio, + voice->src.resampleSamples * voice->src.format->nChannels + ); + mixed = voice->src.resampleSamples; + FAudio_zero( + voice->audio->resampleCache, + mixed * voice->src.format->nChannels * sizeof(float) + ); + finalSamples = voice->audio->resampleCache; + goto sendwork; + } + + /* Base decode size, int to fixed... */ + toDecode = voice->src.resampleSamples * voice->src.resampleStep; + /* ... rounded up based on current offset... */ + toDecode += voice->src.curBufferOffsetDec + FIXED_FRACTION_MASK; + /* ... fixed to int, truncating extra fraction from rounding. */ + toDecode >>= FIXED_PRECISION; + + /* First voice callback */ + if ( voice->src.callback != NULL && + voice->src.callback->OnVoiceProcessingPassStart != NULL ) + { + 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->OnVoiceProcessingPassStart( + voice->src.callback, + FAudio_INTERNAL_GetBytesRequested(voice, (uint32_t) toDecode) + ); + + 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) + + /* Nothing to do? */ + if (voice->src.bufferList == NULL) + { + FAudio_PlatformUnlockMutex(voice->src.bufferLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock) + + if (voice->effects.count > 0 && voice->effects.state != FAPO_BUFFER_SILENT) + { + /* do not stop while the effect chain generates a non-silent buffer */ + FAudio_INTERNAL_ResizeResampleCache( + voice->audio, + voice->src.resampleSamples * voice->src.format->nChannels + ); + mixed = voice->src.resampleSamples; + FAudio_zero( + voice->audio->resampleCache, + mixed * voice->src.format->nChannels * sizeof(float) + ); + finalSamples = voice->audio->resampleCache; + goto sendwork; + } + + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + + FAudio_PlatformUnlockMutex(voice->audio->sourceLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock) + + if ( voice->src.callback != NULL && + voice->src.callback->OnVoiceProcessingPassEnd != NULL) + { + voice->src.callback->OnVoiceProcessingPassEnd( + voice->src.callback + ); + } + + FAudio_PlatformLockMutex(voice->audio->sourceLock); + LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock) + + LOG_FUNC_EXIT(voice->audio) + return; + } + + /* Decode... */ + FAudio_INTERNAL_DecodeBuffers(voice, &toDecode); + + /* Subtract any padding samples from the total, if applicable */ + if ( voice->src.curBufferOffsetDec > 0 && + voice->src.totalSamples > 0 ) + { + voice->src.totalSamples -= 1; + } + + /* Okay, we're done messing with client data */ + if ( voice->src.callback != NULL && + voice->src.callback->OnVoiceProcessingPassEnd != 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->OnVoiceProcessingPassEnd( + voice->src.callback + ); + + 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) + } + + /* Nothing to resample? */ + if (toDecode == 0) + { + FAudio_PlatformUnlockMutex(voice->src.bufferLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock) + + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + + LOG_FUNC_EXIT(voice->audio) + return; + } + + /* int to fixed... */ + toResample = toDecode << FIXED_PRECISION; + /* ... round back down based on current offset... */ + toResample -= voice->src.curBufferOffsetDec; + /* ... but also ceil for any fraction value... */ + toResample += FIXED_FRACTION_MASK; + /* ... undo step size, fixed to int. */ + toResample /= voice->src.resampleStep; + /* Add the padding, for some reason this helps? */ + toResample += EXTRA_DECODE_PADDING; + /* FIXME: I feel like this should be an assert but I suck */ + toResample = FAudio_min(toResample, voice->src.resampleSamples); + + /* Resample... */ + if (voice->src.resampleStep == FIXED_ONE) + { + /* Actually, just use the existing buffer... */ + finalSamples = voice->audio->decodeCache; + } + else + { + FAudio_INTERNAL_ResizeResampleCache( + voice->audio, + voice->src.resampleSamples * voice->src.format->nChannels + ); + voice->src.resample( + voice->audio->decodeCache, + voice->audio->resampleCache, + &voice->src.resampleOffset, + voice->src.resampleStep, + toResample, + (uint8_t) voice->src.format->nChannels + ); + finalSamples = voice->audio->resampleCache; + } + + /* Update buffer offsets */ + if (voice->src.bufferList != NULL) + { + /* Increment fixed offset by resample size, int to fixed... */ + voice->src.curBufferOffsetDec += toResample * voice->src.resampleStep; + /* ... chop off any ints we got from the above increment */ + voice->src.curBufferOffsetDec &= FIXED_FRACTION_MASK; + + /* Dec >0? We need one frame from the past... + * FIXME: We can't go back to a prev buffer though? + */ + if ( voice->src.curBufferOffsetDec > 0 && + voice->src.curBufferOffset > 0 ) + { + voice->src.curBufferOffset -= 1; + } + } + else + { + voice->src.curBufferOffsetDec = 0; + voice->src.curBufferOffset = 0; + } + + /* Done with buffers, finally. */ + FAudio_PlatformUnlockMutex(voice->src.bufferLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock) + mixed = (uint32_t) toResample; + +sendwork: + + /* Filters */ + if (voice->flags & FAUDIO_VOICE_USEFILTER) + { + FAudio_PlatformLockMutex(voice->filterLock); + LOG_MUTEX_LOCK(voice->audio, voice->filterLock) + FAudio_INTERNAL_FilterVoice( + voice->audio, + &voice->filter, + voice->filterState, + finalSamples, + mixed, + voice->src.format->nChannels + ); + FAudio_PlatformUnlockMutex(voice->filterLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->filterLock) + } + + /* Process effect chain */ + FAudio_PlatformLockMutex(voice->effectLock); + LOG_MUTEX_LOCK(voice->audio, voice->effectLock) + if (voice->effects.count > 0) + { + /* If we didn't get the full size of the update, we have to fill + * it with silence so the effect can process a whole update + */ + if (mixed < voice->src.resampleSamples) + { + FAudio_zero( + finalSamples + (mixed * voice->src.format->nChannels), + (voice->src.resampleSamples - mixed) * voice->src.format->nChannels * sizeof(float) + ); + mixed = voice->src.resampleSamples; + } + finalSamples = FAudio_INTERNAL_ProcessEffectChain( + voice, + finalSamples, + &mixed + ); + } + FAudio_PlatformUnlockMutex(voice->effectLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock) + + /* Nowhere to send it? Just skip the rest...*/ + if (voice->sends.SendCount == 0) + { + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + LOG_FUNC_EXIT(voice->audio) + return; + } + + /* Send float cache to sends */ + FAudio_PlatformLockMutex(voice->volumeLock); + LOG_MUTEX_LOCK(voice->audio, voice->volumeLock) + for (i = 0; i < voice->sends.SendCount; i += 1) + { + out = voice->sends.pSends[i].pOutputVoice; + if (out->type == FAUDIO_VOICE_MASTER) + { + stream = out->master.output; + oChan = out->master.inputChannels; + } + else + { + stream = out->mix.inputCache; + oChan = out->mix.inputChannels; + } + + voice->sendMix[i]( + mixed, + voice->outputChannels, + oChan, + finalSamples, + stream, + voice->mixCoefficients[i] + ); + + if (voice->sends.pSends[i].Flags & FAUDIO_SEND_USEFILTER) + { + FAudio_INTERNAL_FilterVoice( + voice->audio, + &voice->sendFilter[i], + voice->sendFilterState[i], + stream, + mixed, + oChan + ); + } + } + FAudio_PlatformUnlockMutex(voice->volumeLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->volumeLock) + + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + LOG_FUNC_EXIT(voice->audio) +} + +static void FAudio_INTERNAL_MixSubmix(FAudioSubmixVoice *voice) +{ + uint32_t i; + float *stream; + uint32_t oChan; + FAudioVoice *out; + uint32_t resampled; + uint64_t resampleOffset = 0; + float *finalSamples; + + LOG_FUNC_ENTER(voice->audio) + FAudio_PlatformLockMutex(voice->sendLock); + LOG_MUTEX_LOCK(voice->audio, voice->sendLock) + + /* Resample */ + if (voice->mix.resampleStep == FIXED_ONE) + { + /* Actually, just use the existing buffer... */ + finalSamples = voice->mix.inputCache; + } + else + { + FAudio_INTERNAL_ResizeResampleCache( + voice->audio, + voice->mix.outputSamples * voice->mix.inputChannels + ); + voice->mix.resample( + voice->mix.inputCache, + voice->audio->resampleCache, + &resampleOffset, + voice->mix.resampleStep, + voice->mix.outputSamples, + (uint8_t) voice->mix.inputChannels + ); + finalSamples = voice->audio->resampleCache; + } + resampled = voice->mix.outputSamples * voice->mix.inputChannels; + + /* Submix overall volume is applied _before_ effects/filters, blech! */ + if (voice->volume != 1.0f) + { + FAudio_INTERNAL_Amplify( + finalSamples, + resampled, + voice->volume + ); + } + resampled /= voice->mix.inputChannels; + + /* Filters */ + if (voice->flags & FAUDIO_VOICE_USEFILTER) + { + FAudio_PlatformLockMutex(voice->filterLock); + LOG_MUTEX_LOCK(voice->audio, voice->filterLock) + FAudio_INTERNAL_FilterVoice( + voice->audio, + &voice->filter, + voice->filterState, + finalSamples, + resampled, + voice->mix.inputChannels + ); + FAudio_PlatformUnlockMutex(voice->filterLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->filterLock) + } + + /* Process effect chain */ + FAudio_PlatformLockMutex(voice->effectLock); + LOG_MUTEX_LOCK(voice->audio, voice->effectLock) + if (voice->effects.count > 0) + { + finalSamples = FAudio_INTERNAL_ProcessEffectChain( + voice, + finalSamples, + &resampled + ); + } + FAudio_PlatformUnlockMutex(voice->effectLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock) + + /* Nothing more to do? */ + if (voice->sends.SendCount == 0) + { + goto end; + } + + /* Send float cache to sends */ + FAudio_PlatformLockMutex(voice->volumeLock); + LOG_MUTEX_LOCK(voice->audio, voice->volumeLock) + for (i = 0; i < voice->sends.SendCount; i += 1) + { + out = voice->sends.pSends[i].pOutputVoice; + if (out->type == FAUDIO_VOICE_MASTER) + { + stream = out->master.output; + oChan = out->master.inputChannels; + } + else + { + stream = out->mix.inputCache; + oChan = out->mix.inputChannels; + } + + voice->sendMix[i]( + resampled, + voice->outputChannels, + oChan, + finalSamples, + stream, + voice->mixCoefficients[i] + ); + + if (voice->sends.pSends[i].Flags & FAUDIO_SEND_USEFILTER) + { + FAudio_INTERNAL_FilterVoice( + voice->audio, + &voice->sendFilter[i], + voice->sendFilterState[i], + stream, + resampled, + oChan + ); + } + } + FAudio_PlatformUnlockMutex(voice->volumeLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->volumeLock) + + /* Zero this at the end, for the next update */ +end: + FAudio_PlatformUnlockMutex(voice->sendLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) + FAudio_zero( + voice->mix.inputCache, + sizeof(float) * voice->mix.inputSamples + ); + LOG_FUNC_EXIT(voice->audio) +} + +static void FAudio_INTERNAL_FlushPendingBuffers(FAudioSourceVoice *voice) +{ + FAudioBufferEntry *entry; + + FAudio_PlatformLockMutex(voice->src.bufferLock); + LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock) + + /* Remove pending flushed buffers and send an event for each one */ + while (voice->src.flushList != NULL) + { + entry = voice->src.flushList; + voice->src.flushList = voice->src.flushList->next; + + if (voice->src.callback != NULL && voice->src.callback->OnBufferEnd != NULL) + { + FAudio_PlatformUnlockMutex(voice->audio->sourceLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock) + + voice->src.callback->OnBufferEnd( + voice->src.callback, + entry->buffer.pContext + ); + + FAudio_PlatformLockMutex(voice->audio->sourceLock); + LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock) + } + voice->audio->pFree(entry); + } + + FAudio_PlatformUnlockMutex(voice->src.bufferLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock) +} + +static void FAUDIOCALL FAudio_INTERNAL_GenerateOutput(FAudio *audio, float *output) +{ + uint32_t totalSamples; + LinkedList *list; + float *effectOut; + FAudioEngineCallback *callback; + + LOG_FUNC_ENTER(audio) + if (!audio->active) + { + LOG_FUNC_EXIT(audio) + return; + } + + /* Apply any committed changes */ + FAudio_OPERATIONSET_Execute(audio); + + /* ProcessingPassStart callbacks */ + FAudio_PlatformLockMutex(audio->callbackLock); + LOG_MUTEX_LOCK(audio, audio->callbackLock) + list = audio->callbacks; + while (list != NULL) + { + callback = (FAudioEngineCallback*) list->entry; + if (callback->OnProcessingPassStart != NULL) + { + callback->OnProcessingPassStart( + callback + ); + } + list = list->next; + } + FAudio_PlatformUnlockMutex(audio->callbackLock); + LOG_MUTEX_UNLOCK(audio, audio->callbackLock) + + /* Writes to master will directly write to output, but ONLY if there + * isn't any channel-changing effect processing to do first. + */ + if (audio->master->master.effectCache != NULL) + { + audio->master->master.output = audio->master->master.effectCache; + FAudio_zero( + audio->master->master.effectCache, + ( + sizeof(float) * + audio->updateSize * + audio->master->master.inputChannels + ) + ); + } + else + { + audio->master->master.output = output; + } + + /* Mix sources */ + FAudio_PlatformLockMutex(audio->sourceLock); + LOG_MUTEX_LOCK(audio, audio->sourceLock) + list = audio->sources; + while (list != NULL) + { + audio->processingSource = (FAudioSourceVoice*) list->entry; + + FAudio_INTERNAL_FlushPendingBuffers(audio->processingSource); + if (audio->processingSource->src.active) + { + FAudio_INTERNAL_MixSource(audio->processingSource); + FAudio_INTERNAL_FlushPendingBuffers(audio->processingSource); + } + + list = list->next; + } + audio->processingSource = NULL; + FAudio_PlatformUnlockMutex(audio->sourceLock); + LOG_MUTEX_UNLOCK(audio, audio->sourceLock) + + /* Mix submixes, ordered by processing stage */ + FAudio_PlatformLockMutex(audio->submixLock); + LOG_MUTEX_LOCK(audio, audio->submixLock) + list = audio->submixes; + while (list != NULL) + { + FAudio_INTERNAL_MixSubmix((FAudioSubmixVoice*) list->entry); + list = list->next; + } + FAudio_PlatformUnlockMutex(audio->submixLock); + LOG_MUTEX_UNLOCK(audio, audio->submixLock) + + /* Apply master volume */ + if (audio->master->volume != 1.0f) + { + FAudio_INTERNAL_Amplify( + audio->master->master.output, + audio->updateSize * audio->master->master.inputChannels, + audio->master->volume + ); + } + + /* Process master effect chain */ + FAudio_PlatformLockMutex(audio->master->effectLock); + LOG_MUTEX_LOCK(audio, audio->master->effectLock) + if (audio->master->effects.count > 0) + { + totalSamples = audio->updateSize; + effectOut = FAudio_INTERNAL_ProcessEffectChain( + audio->master, + audio->master->master.output, + &totalSamples + ); + + if (effectOut != output) + { + FAudio_memcpy( + output, + effectOut, + totalSamples * audio->master->outputChannels * sizeof(float) + ); + } + if (totalSamples < audio->updateSize) + { + FAudio_zero( + output + (totalSamples * audio->master->outputChannels), + (audio->updateSize - totalSamples) * sizeof(float) + ); + } + } + FAudio_PlatformUnlockMutex(audio->master->effectLock); + LOG_MUTEX_UNLOCK(audio, audio->master->effectLock) + + /* OnProcessingPassEnd callbacks */ + FAudio_PlatformLockMutex(audio->callbackLock); + LOG_MUTEX_LOCK(audio, audio->callbackLock) + list = audio->callbacks; + while (list != NULL) + { + callback = (FAudioEngineCallback*) list->entry; + if (callback->OnProcessingPassEnd != NULL) + { + callback->OnProcessingPassEnd( + callback + ); + } + list = list->next; + } + FAudio_PlatformUnlockMutex(audio->callbackLock); + LOG_MUTEX_UNLOCK(audio, audio->callbackLock) + + LOG_FUNC_EXIT(audio) +} + +void FAudio_INTERNAL_UpdateEngine(FAudio *audio, float *output) +{ + LOG_FUNC_ENTER(audio) + if (audio->pClientEngineProc) + { + audio->pClientEngineProc( + &FAudio_INTERNAL_GenerateOutput, + audio, + output, + audio->clientEngineUser + ); + } + else + { + FAudio_INTERNAL_GenerateOutput(audio, output); + } + LOG_FUNC_EXIT(audio) +} + +void FAudio_INTERNAL_ResizeDecodeCache(FAudio *audio, uint32_t samples) +{ + LOG_FUNC_ENTER(audio) + FAudio_PlatformLockMutex(audio->sourceLock); + LOG_MUTEX_LOCK(audio, audio->sourceLock) + if (samples > audio->decodeSamples) + { + audio->decodeSamples = samples; + audio->decodeCache = (float*) audio->pRealloc( + audio->decodeCache, + sizeof(float) * audio->decodeSamples + ); + } + FAudio_PlatformUnlockMutex(audio->sourceLock); + LOG_MUTEX_UNLOCK(audio, audio->sourceLock) + LOG_FUNC_EXIT(audio) +} + +void FAudio_INTERNAL_AllocEffectChain( + FAudioVoice *voice, + const FAudioEffectChain *pEffectChain +) { + uint32_t i; + + LOG_FUNC_ENTER(voice->audio) + voice->effects.state = FAPO_BUFFER_VALID; + voice->effects.count = pEffectChain->EffectCount; + if (voice->effects.count == 0) + { + LOG_FUNC_EXIT(voice->audio) + return; + } + + for (i = 0; i < pEffectChain->EffectCount; i += 1) + { + pEffectChain->pEffectDescriptors[i].pEffect->AddRef(pEffectChain->pEffectDescriptors[i].pEffect); + } + + voice->effects.desc = (FAudioEffectDescriptor*) voice->audio->pMalloc( + voice->effects.count * sizeof(FAudioEffectDescriptor) + ); + FAudio_memcpy( + voice->effects.desc, + pEffectChain->pEffectDescriptors, + voice->effects.count * sizeof(FAudioEffectDescriptor) + ); + #define ALLOC_EFFECT_PROPERTY(prop, type) \ + voice->effects.prop = (type*) voice->audio->pMalloc( \ + voice->effects.count * sizeof(type) \ + ); \ + FAudio_zero( \ + voice->effects.prop, \ + voice->effects.count * sizeof(type) \ + ); + ALLOC_EFFECT_PROPERTY(parameters, void*) + ALLOC_EFFECT_PROPERTY(parameterSizes, uint32_t) + ALLOC_EFFECT_PROPERTY(parameterUpdates, uint8_t) + ALLOC_EFFECT_PROPERTY(inPlaceProcessing, uint8_t) + #undef ALLOC_EFFECT_PROPERTY + LOG_FUNC_EXIT(voice->audio) +} + +void FAudio_INTERNAL_FreeEffectChain(FAudioVoice *voice) +{ + uint32_t i; + + LOG_FUNC_ENTER(voice->audio) + if (voice->effects.count == 0) + { + LOG_FUNC_EXIT(voice->audio) + return; + } + + for (i = 0; i < voice->effects.count; i += 1) + { + voice->effects.desc[i].pEffect->UnlockForProcess(voice->effects.desc[i].pEffect); + voice->effects.desc[i].pEffect->Release(voice->effects.desc[i].pEffect); + } + + voice->audio->pFree(voice->effects.desc); + voice->audio->pFree(voice->effects.parameters); + voice->audio->pFree(voice->effects.parameterSizes); + voice->audio->pFree(voice->effects.parameterUpdates); + voice->audio->pFree(voice->effects.inPlaceProcessing); + LOG_FUNC_EXIT(voice->audio) +} + +uint32_t FAudio_INTERNAL_VoiceOutputFrequency( + FAudioVoice *voice, + const FAudioVoiceSends *pSendList +) { + uint32_t outSampleRate; + uint32_t newResampleSamples; + uint64_t resampleSanityCheck; + + LOG_FUNC_ENTER(voice->audio) + + if ((pSendList == NULL) || (pSendList->SendCount == 0)) + { + /* When we're deliberately given no sends, use master rate! */ + outSampleRate = voice->audio->master->master.inputSampleRate; + } + else + { + outSampleRate = pSendList->pSends[0].pOutputVoice->type == FAUDIO_VOICE_MASTER ? + pSendList->pSends[0].pOutputVoice->master.inputSampleRate : + pSendList->pSends[0].pOutputVoice->mix.inputSampleRate; + } + newResampleSamples = (uint32_t) FAudio_ceil( + voice->audio->updateSize * + (double) outSampleRate / + (double) voice->audio->master->master.inputSampleRate + ); + if (voice->type == FAUDIO_VOICE_SOURCE) + { + if ( (voice->src.resampleSamples != 0) && + (newResampleSamples != voice->src.resampleSamples) && + (voice->effects.count > 0) ) + { + LOG_FUNC_EXIT(voice->audio) + return FAUDIO_E_INVALID_CALL; + } + voice->src.resampleSamples = newResampleSamples; + } + else /* (voice->type == FAUDIO_VOICE_SUBMIX) */ + { + if ( (voice->mix.outputSamples != 0) && + (newResampleSamples != voice->mix.outputSamples) && + (voice->effects.count > 0) ) + { + LOG_FUNC_EXIT(voice->audio) + return FAUDIO_E_INVALID_CALL; + } + voice->mix.outputSamples = newResampleSamples; + + voice->mix.resampleStep = DOUBLE_TO_FIXED(( + (double) voice->mix.inputSampleRate / + (double) outSampleRate + )); + + /* Because we used ceil earlier, there's a chance that + * downsampling submixes will go past the number of samples + * available. Sources can do this thanks to padding, but we + * don't have that luxury for submixes, so unfortunately we + * just have to undo the ceil and turn it into a floor. + * -flibit + */ + resampleSanityCheck = ( + voice->mix.resampleStep * voice->mix.outputSamples + ) >> FIXED_PRECISION; + if (resampleSanityCheck > (voice->mix.inputSamples / voice->mix.inputChannels)) + { + voice->mix.outputSamples -= 1; + } + } + + LOG_FUNC_EXIT(voice->audio) + return 0; +} + +const float FAUDIO_INTERNAL_MATRIX_DEFAULTS[8][8][64] = +{ + #include "matrix_defaults.inl" +}; + +/* PCM Decoding */ + +void FAudio_INTERNAL_DecodePCM8( + FAudioVoice *voice, + FAudioBuffer *buffer, + float *decodeCache, + uint32_t samples +) { + LOG_FUNC_ENTER(voice->audio) + FAudio_INTERNAL_Convert_U8_To_F32( + ((uint8_t*) buffer->pAudioData) + ( + voice->src.curBufferOffset * voice->src.format->nChannels + ), + decodeCache, + samples * voice->src.format->nChannels + ); + LOG_FUNC_EXIT(voice->audio) +} + +void FAudio_INTERNAL_DecodePCM16( + FAudioVoice *voice, + FAudioBuffer *buffer, + float *decodeCache, + uint32_t samples +) { + LOG_FUNC_ENTER(voice->audio) + FAudio_INTERNAL_Convert_S16_To_F32( + ((int16_t*) buffer->pAudioData) + ( + voice->src.curBufferOffset * voice->src.format->nChannels + ), + decodeCache, + samples * voice->src.format->nChannels + ); + LOG_FUNC_EXIT(voice->audio) +} + +void FAudio_INTERNAL_DecodePCM24( + FAudioVoice *voice, + FAudioBuffer *buffer, + float *decodeCache, + uint32_t samples +) { + uint32_t i, j; + const uint8_t *buf; + LOG_FUNC_ENTER(voice->audio) + + /* FIXME: Uh... is this something that can be SIMD-ified? */ + buf = buffer->pAudioData + ( + voice->src.curBufferOffset * voice->src.format->nBlockAlign + ); + for (i = 0; i < samples; i += 1, buf += voice->src.format->nBlockAlign) + for (j = 0; j < voice->src.format->nChannels; j += 1) + { + *decodeCache++ = ((int32_t) ( + ((uint32_t) buf[(j * 3) + 2] << 24) | + ((uint32_t) buf[(j * 3) + 1] << 16) | + ((uint32_t) buf[(j * 3) + 0] << 8) + ) >> 8) / 8388607.0f; + } + + LOG_FUNC_EXIT(voice->audio) +} + +void FAudio_INTERNAL_DecodePCM32( + FAudioVoice *voice, + FAudioBuffer *buffer, + float *decodeCache, + uint32_t samples +) { + LOG_FUNC_ENTER(voice->audio) + FAudio_INTERNAL_Convert_S32_To_F32( + ((int32_t*) buffer->pAudioData) + ( + voice->src.curBufferOffset * voice->src.format->nChannels + ), + decodeCache, + samples * voice->src.format->nChannels + ); + LOG_FUNC_EXIT(voice->audio) +} + +void FAudio_INTERNAL_DecodePCM32F( + FAudioVoice *voice, + FAudioBuffer *buffer, + float *decodeCache, + uint32_t samples +) { + LOG_FUNC_ENTER(voice->audio) + FAudio_memcpy( + decodeCache, + ((float*) buffer->pAudioData) + ( + voice->src.curBufferOffset * voice->src.format->nChannels + ), + sizeof(float) * samples * voice->src.format->nChannels + ); + LOG_FUNC_EXIT(voice->audio) +} + +/* MSADPCM Decoding */ + +static inline int16_t FAudio_INTERNAL_ParseNibble( + uint8_t nibble, + uint8_t predictor, + int16_t *delta, + int16_t *sample1, + int16_t *sample2 +) { + static const int32_t AdaptionTable[16] = + { + 230, 230, 230, 230, 307, 409, 512, 614, + 768, 614, 512, 409, 307, 230, 230, 230 + }; + static const int32_t AdaptCoeff_1[7] = + { + 256, 512, 0, 192, 240, 460, 392 + }; + static const int32_t AdaptCoeff_2[7] = + { + 0, -256, 0, 64, 0, -208, -232 + }; + + int8_t signedNibble; + int32_t sampleInt; + int16_t sample; + + signedNibble = (int8_t) nibble; + if (signedNibble & 0x08) + { + signedNibble -= 0x10; + } + + sampleInt = ( + (*sample1 * AdaptCoeff_1[predictor]) + + (*sample2 * AdaptCoeff_2[predictor]) + ) / 256; + sampleInt += signedNibble * (*delta); + sample = FAudio_clamp(sampleInt, -32768, 32767); + + *sample2 = *sample1; + *sample1 = sample; + *delta = (int16_t) (AdaptionTable[nibble] * (int32_t) (*delta) / 256); + if (*delta < 16) + { + *delta = 16; + } + return sample; +} + +#define READ(item, type) \ + item = *((type*) *buf); \ + *buf += sizeof(type); + +static inline void FAudio_INTERNAL_DecodeMonoMSADPCMBlock( + uint8_t **buf, + int16_t *blockCache, + uint32_t align +) { + uint32_t i; + + /* Temp storage for ADPCM blocks */ + uint8_t predictor; + int16_t delta; + int16_t sample1; + int16_t sample2; + + /* Preamble */ + READ(predictor, uint8_t) + READ(delta, int16_t) + READ(sample1, int16_t) + READ(sample2, int16_t) + align -= 7; + + /* Samples */ + *blockCache++ = sample2; + *blockCache++ = sample1; + for (i = 0; i < align; i += 1, *buf += 1) + { + *blockCache++ = FAudio_INTERNAL_ParseNibble( + *(*buf) >> 4, + predictor, + &delta, + &sample1, + &sample2 + ); + *blockCache++ = FAudio_INTERNAL_ParseNibble( + *(*buf) & 0x0F, + predictor, + &delta, + &sample1, + &sample2 + ); + } +} + +static inline void FAudio_INTERNAL_DecodeStereoMSADPCMBlock( + uint8_t **buf, + int16_t *blockCache, + uint32_t align +) { + uint32_t i; + + /* Temp storage for ADPCM blocks */ + uint8_t l_predictor; + uint8_t r_predictor; + int16_t l_delta; + int16_t r_delta; + int16_t l_sample1; + int16_t r_sample1; + int16_t l_sample2; + int16_t r_sample2; + + /* Preamble */ + READ(l_predictor, uint8_t) + READ(r_predictor, uint8_t) + READ(l_delta, int16_t) + READ(r_delta, int16_t) + READ(l_sample1, int16_t) + READ(r_sample1, int16_t) + READ(l_sample2, int16_t) + READ(r_sample2, int16_t) + align -= 14; + + /* Samples */ + *blockCache++ = l_sample2; + *blockCache++ = r_sample2; + *blockCache++ = l_sample1; + *blockCache++ = r_sample1; + for (i = 0; i < align; i += 1, *buf += 1) + { + *blockCache++ = FAudio_INTERNAL_ParseNibble( + *(*buf) >> 4, + l_predictor, + &l_delta, + &l_sample1, + &l_sample2 + ); + *blockCache++ = FAudio_INTERNAL_ParseNibble( + *(*buf) & 0x0F, + r_predictor, + &r_delta, + &r_sample1, + &r_sample2 + ); + } +} + +#undef READ + +void FAudio_INTERNAL_DecodeMonoMSADPCM( + FAudioVoice *voice, + FAudioBuffer *buffer, + float *decodeCache, + uint32_t samples +) { + /* Loop variables */ + uint32_t copy, done = 0; + + /* Read pointers */ + uint8_t *buf; + int32_t midOffset; + + /* PCM block cache */ + int16_t blockCache[1012]; /* Max block size */ + + /* Block size */ + uint32_t bsize = ((FAudioADPCMWaveFormat*) voice->src.format)->wSamplesPerBlock; + + LOG_FUNC_ENTER(voice->audio) + + /* Where are we starting? */ + buf = (uint8_t*) buffer->pAudioData + ( + (voice->src.curBufferOffset / bsize) * + voice->src.format->nBlockAlign + ); + + /* Are we starting in the middle? */ + midOffset = (voice->src.curBufferOffset % bsize); + + /* Read in each block directly to the decode cache */ + while (done < samples) + { + copy = FAudio_min(samples - done, bsize - midOffset); + FAudio_INTERNAL_DecodeMonoMSADPCMBlock( + &buf, + blockCache, + voice->src.format->nBlockAlign + ); + FAudio_INTERNAL_Convert_S16_To_F32( + blockCache + midOffset, + decodeCache, + copy + ); + decodeCache += copy; + done += copy; + midOffset = 0; + } + LOG_FUNC_EXIT(voice->audio) +} + +void FAudio_INTERNAL_DecodeStereoMSADPCM( + FAudioVoice *voice, + FAudioBuffer *buffer, + float *decodeCache, + uint32_t samples +) { + /* Loop variables */ + uint32_t copy, done = 0; + + /* Read pointers */ + uint8_t *buf; + int32_t midOffset; + + /* PCM block cache */ + int16_t blockCache[2024]; /* Max block size */ + + /* Align, block size */ + uint32_t bsize = ((FAudioADPCMWaveFormat*) voice->src.format)->wSamplesPerBlock; + + LOG_FUNC_ENTER(voice->audio) + + /* Where are we starting? */ + buf = (uint8_t*) buffer->pAudioData + ( + (voice->src.curBufferOffset / bsize) * + voice->src.format->nBlockAlign + ); + + /* Are we starting in the middle? */ + midOffset = (voice->src.curBufferOffset % bsize); + + /* Read in each block directly to the decode cache */ + while (done < samples) + { + copy = FAudio_min(samples - done, bsize - midOffset); + FAudio_INTERNAL_DecodeStereoMSADPCMBlock( + &buf, + blockCache, + voice->src.format->nBlockAlign + ); + FAudio_INTERNAL_Convert_S16_To_F32( + blockCache + (midOffset * 2), + decodeCache, + copy * 2 + ); + decodeCache += copy * 2; + done += copy; + midOffset = 0; + } + LOG_FUNC_EXIT(voice->audio) +} + +/* Fallback WMA decoder, get ready for spam! */ + +void FAudio_INTERNAL_DecodeWMAERROR( + FAudioVoice *voice, + FAudioBuffer *buffer, + float *decodeCache, + uint32_t samples +) { + LOG_FUNC_ENTER(voice->audio) + LOG_ERROR(voice->audio, "%s", "WMA IS NOT SUPPORTED IN THIS BUILD!") + FAudio_zero(decodeCache, samples * voice->src.format->nChannels * sizeof(float)); + LOG_FUNC_EXIT(voice->audio) +} + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/src/FAudio_internal.h b/libs/faudio/src/FAudio_internal.h new file mode 100644 index 00000000000..11cee11de24 --- /dev/null +++ b/libs/faudio/src/FAudio_internal.h @@ -0,0 +1,913 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * Ethan "flibitijibibo" Lee + * + */ + +#include "FAudio.h" +#include "FAPOBase.h" +#include + +#ifdef FAUDIO_WIN32_PLATFORM +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define FAudio_malloc malloc +#define FAudio_realloc realloc +#define FAudio_free free +#define FAudio_alloca(x) alloca(x) +#define FAudio_dealloca(x) (void)(x) +#define FAudio_zero(ptr, size) memset(ptr, '\0', size) +#define FAudio_memset(ptr, val, size) memset(ptr, val, size) +#define FAudio_memcpy(dst, src, size) memcpy(dst, src, size) +#define FAudio_memmove(dst, src, size) memmove(dst, src, size) +#define FAudio_memcmp(ptr1, ptr2, size) memcmp(ptr1, ptr2, size) + +#define FAudio_strlen(ptr) strlen(ptr) +#define FAudio_strcmp(str1, str2) strcmp(str1, str2) +#define FAudio_strncmp(str1, str2, size) strncmp(str1, str2, size) +#define FAudio_strlcpy(ptr1, ptr2, size) lstrcpynA(ptr1, ptr2, size) + +#define FAudio_pow(x, y) pow(x, y) +#define FAudio_log(x) log(x) +#define FAudio_log10(x) log10(x) +#define FAudio_sin(x) sin(x) +#define FAudio_cos(x) cos(x) +#define FAudio_tan(x) tan(x) +#define FAudio_acos(x) acos(x) +#define FAudio_ceil(x) ceil(x) +#define FAudio_floor(x) floor(x) +#define FAudio_abs(x) abs(x) +#define FAudio_ldexp(v, e) ldexp(v, e) +#define FAudio_exp(x) exp(x) + +#define FAudio_cosf(x) cosf(x) +#define FAudio_sinf(x) sinf(x) +#define FAudio_sqrtf(x) sqrtf(x) +#define FAudio_acosf(x) acosf(x) +#define FAudio_atan2f(y, x) atan2f(y, x) +#define FAudio_fabsf(x) fabsf(x) + +#define FAudio_qsort qsort + +#define FAudio_assert assert +#define FAudio_snprintf snprintf +#define FAudio_vsnprintf vsnprintf +#define FAudio_getenv getenv +#define FAudio_PRIu64 PRIu64 +#define FAudio_PRIx64 PRIx64 + +extern void FAudio_Log(char const *msg); + +/* FIXME: Assuming little-endian! */ +#define FAudio_swap16LE(x) (x) +#define FAudio_swap16BE(x) \ + ((x >> 8) & 0x00FF) | \ + ((x << 8) & 0xFF00) +#define FAudio_swap32LE(x) (x) +#define FAudio_swap32BE(x) \ + ((x >> 24) & 0x000000FF) | \ + ((x >> 8) & 0x0000FF00) | \ + ((x << 8) & 0x00FF0000) | \ + ((x << 24) & 0xFF000000) +#define FAudio_swap64LE(x) (x) +#define FAudio_swap64BE(x) \ + ((x >> 32) & 0x00000000000000FF) | \ + ((x >> 24) & 0x000000000000FF00) | \ + ((x >> 16) & 0x0000000000FF0000) | \ + ((x >> 8) & 0x00000000FF000000) | \ + ((x << 8) & 0x000000FF00000000) | \ + ((x << 16) & 0x0000FF0000000000) | \ + ((x << 24) & 0x00FF000000000000) | \ + ((x << 32) & 0xFF00000000000000) +#else +#include +#include +#include +#include + +#define FAudio_malloc SDL_malloc +#define FAudio_realloc SDL_realloc +#define FAudio_free SDL_free +#define FAudio_alloca(x) SDL_stack_alloc(uint8_t, x) +#define FAudio_dealloca(x) SDL_stack_free(x) +#define FAudio_zero(ptr, size) SDL_memset(ptr, '\0', size) +#define FAudio_memset(ptr, val, size) SDL_memset(ptr, val, size) +#define FAudio_memcpy(dst, src, size) SDL_memcpy(dst, src, size) +#define FAudio_memmove(dst, src, size) SDL_memmove(dst, src, size) +#define FAudio_memcmp(ptr1, ptr2, size) SDL_memcmp(ptr1, ptr2, size) + +#define FAudio_strlen(ptr) SDL_strlen(ptr) +#define FAudio_strcmp(str1, str2) SDL_strcmp(str1, str2) +#define FAudio_strncmp(str1, str2, size) SDL_strncmp(str1, str1, size) +#define FAudio_strlcpy(ptr1, ptr2, size) SDL_strlcpy(ptr1, ptr2, size) + +#define FAudio_pow(x, y) SDL_pow(x, y) +#define FAudio_log(x) SDL_log(x) +#define FAudio_log10(x) SDL_log10(x) +#define FAudio_sin(x) SDL_sin(x) +#define FAudio_cos(x) SDL_cos(x) +#define FAudio_tan(x) SDL_tan(x) +#define FAudio_acos(x) SDL_acos(x) +#define FAudio_ceil(x) SDL_ceil(x) +#define FAudio_floor(x) SDL_floor(x) +#define FAudio_abs(x) SDL_abs(x) +#define FAudio_ldexp(v, e) SDL_scalbn(v, e) +#define FAudio_exp(x) SDL_exp(x) + +#define FAudio_cosf(x) SDL_cosf(x) +#define FAudio_sinf(x) SDL_sinf(x) +#define FAudio_sqrtf(x) SDL_sqrtf(x) +#define FAudio_acosf(x) SDL_acosf(x) +#define FAudio_atan2f(y, x) SDL_atan2f(y, x) +#define FAudio_fabsf(x) SDL_fabsf(x) + +#define FAudio_qsort SDL_qsort + +#ifdef FAUDIO_LOG_ASSERTIONS +#define FAudio_assert(condition) \ + { \ + static uint8_t logged = 0; \ + if (!(condition) && !logged) \ + { \ + SDL_Log("Assertion failed: %s", #condition); \ + logged = 1; \ + } \ + } +#else +#define FAudio_assert SDL_assert +#endif +#define FAudio_snprintf SDL_snprintf +#define FAudio_vsnprintf SDL_vsnprintf +#define FAudio_Log(msg) SDL_Log("%s", 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 */ +#define FAudio_min(val1, val2) \ + (val1 < val2 ? val1 : val2) +#define FAudio_max(val1, val2) \ + (val1 > val2 ? val1 : val2) +#define FAudio_clamp(val, min, max) \ + (val > max ? max : (val < min ? min : val)) + +/* Windows/Visual Studio cruft */ +#ifdef _WIN32 + #ifdef __cplusplus + /* C++ should have `inline`, but not `restrict` */ + #define restrict + #else + #define inline __inline + #if defined(_MSC_VER) + #if (_MSC_VER >= 1700) /* VS2012+ */ + #define restrict __restrict + #else /* VS2010- */ + #define restrict + #endif + #else + #define restrict + #endif + #endif +#endif + +/* C++ does not have restrict (though VS2012+ does have __restrict) */ +#if defined(__cplusplus) && !defined(restrict) +#define restrict +#endif + +/* Threading Types */ + +typedef void* FAudioThread; +typedef void* FAudioMutex; +typedef int32_t (FAUDIOCALL * FAudioThreadFunc)(void* data); +typedef enum FAudioThreadPriority +{ + FAUDIO_THREAD_PRIORITY_LOW, + FAUDIO_THREAD_PRIORITY_NORMAL, + FAUDIO_THREAD_PRIORITY_HIGH, +} FAudioThreadPriority; + +/* Linked Lists */ + +typedef struct LinkedList LinkedList; +struct LinkedList +{ + void* entry; + LinkedList *next; +}; +void LinkedList_AddEntry( + LinkedList **start, + void* toAdd, + FAudioMutex lock, + FAudioMallocFunc pMalloc +); +void LinkedList_PrependEntry( + LinkedList **start, + void* toAdd, + FAudioMutex lock, + FAudioMallocFunc pMalloc +); +void LinkedList_RemoveEntry( + LinkedList **start, + void* toRemove, + FAudioMutex lock, + FAudioFreeFunc pFree +); + +/* Internal FAudio Types */ + +typedef enum FAudioVoiceType +{ + FAUDIO_VOICE_SOURCE, + FAUDIO_VOICE_SUBMIX, + FAUDIO_VOICE_MASTER +} FAudioVoiceType; + +typedef struct FAudioBufferEntry FAudioBufferEntry; +struct FAudioBufferEntry +{ + FAudioBuffer buffer; + FAudioBufferWMA bufferWMA; + FAudioBufferEntry *next; +}; + +typedef void (FAUDIOCALL * FAudioDecodeCallback)( + FAudioVoice *voice, + FAudioBuffer *buffer, /* Buffer to decode */ + float *decodeCache, /* Decode into here */ + uint32_t samples /* Samples to decode */ +); + +typedef void (FAUDIOCALL * FAudioResampleCallback)( + float *restrict dCache, + float *restrict resampleCache, + uint64_t *resampleOffset, + uint64_t resampleStep, + uint64_t toResample, + uint8_t channels +); + +typedef void (FAUDIOCALL * FAudioMixCallback)( + uint32_t toMix, + uint32_t srcChans, + uint32_t dstChans, + float *restrict srcData, + float *restrict dstData, + float *restrict coefficients +); + +typedef float FAudioFilterState[4]; + +/* Operation Sets, original implementation by Tyler Glaiel */ + +typedef struct FAudio_OPERATIONSET_Operation FAudio_OPERATIONSET_Operation; + +void FAudio_OPERATIONSET_Commit(FAudio *audio, uint32_t OperationSet); +void FAudio_OPERATIONSET_CommitAll(FAudio *audio); +void FAudio_OPERATIONSET_Execute(FAudio *audio); + +void FAudio_OPERATIONSET_ClearAll(FAudio *audio); +void FAudio_OPERATIONSET_ClearAllForVoice(FAudioVoice *voice); + +void FAudio_OPERATIONSET_QueueEnableEffect( + FAudioVoice *voice, + uint32_t EffectIndex, + uint32_t OperationSet +); +void FAudio_OPERATIONSET_QueueDisableEffect( + FAudioVoice *voice, + uint32_t EffectIndex, + uint32_t OperationSet +); +void FAudio_OPERATIONSET_QueueSetEffectParameters( + FAudioVoice *voice, + uint32_t EffectIndex, + const void *pParameters, + uint32_t ParametersByteSize, + uint32_t OperationSet +); +void FAudio_OPERATIONSET_QueueSetFilterParameters( + FAudioVoice *voice, + const FAudioFilterParameters *pParameters, + uint32_t OperationSet +); +void FAudio_OPERATIONSET_QueueSetOutputFilterParameters( + FAudioVoice *voice, + FAudioVoice *pDestinationVoice, + const FAudioFilterParameters *pParameters, + uint32_t OperationSet +); +void FAudio_OPERATIONSET_QueueSetVolume( + FAudioVoice *voice, + float Volume, + uint32_t OperationSet +); +void FAudio_OPERATIONSET_QueueSetChannelVolumes( + FAudioVoice *voice, + uint32_t Channels, + const float *pVolumes, + uint32_t OperationSet +); +void FAudio_OPERATIONSET_QueueSetOutputMatrix( + FAudioVoice *voice, + FAudioVoice *pDestinationVoice, + uint32_t SourceChannels, + uint32_t DestinationChannels, + const float *pLevelMatrix, + uint32_t OperationSet +); +void FAudio_OPERATIONSET_QueueStart( + FAudioSourceVoice *voice, + uint32_t Flags, + uint32_t OperationSet +); +void FAudio_OPERATIONSET_QueueStop( + FAudioSourceVoice *voice, + uint32_t Flags, + uint32_t OperationSet +); +void FAudio_OPERATIONSET_QueueExitLoop( + FAudioSourceVoice *voice, + uint32_t OperationSet +); +void FAudio_OPERATIONSET_QueueSetFrequencyRatio( + FAudioSourceVoice *voice, + float Ratio, + uint32_t OperationSet +); + +/* Public FAudio Types */ + +struct FAudio +{ + uint8_t version; + uint8_t active; + uint32_t refcount; + uint32_t initFlags; + uint32_t updateSize; + FAudioMasteringVoice *master; + LinkedList *sources; + LinkedList *submixes; + LinkedList *callbacks; + FAudioMutex sourceLock; + FAudioMutex submixLock; + FAudioMutex callbackLock; + FAudioMutex operationLock; + FAudioWaveFormatExtensible mixFormat; + + FAudio_OPERATIONSET_Operation *queuedOperations; + FAudio_OPERATIONSET_Operation *committedOperations; + + /* Used to prevent destroying an active voice */ + FAudioSourceVoice *processingSource; + + /* Temp storage for processing, interleaved PCM32F */ + #define EXTRA_DECODE_PADDING 2 + uint32_t decodeSamples; + uint32_t resampleSamples; + uint32_t effectChainSamples; + float *decodeCache; + float *resampleCache; + float *effectChainCache; + + /* Allocator callbacks */ + FAudioMallocFunc pMalloc; + FAudioFreeFunc pFree; + FAudioReallocFunc pRealloc; + + /* EngineProcedureEXT */ + void *clientEngineUser; + FAudioEngineProcedureEXT pClientEngineProc; + +#ifndef FAUDIO_DISABLE_DEBUGCONFIGURATION + /* Debug Information */ + FAudioDebugConfiguration debug; +#endif /* FAUDIO_DISABLE_DEBUGCONFIGURATION */ + + /* Platform opaque pointer */ + void *platform; +}; + +struct FAudioVoice +{ + FAudio *audio; + uint32_t flags; + FAudioVoiceType type; + + FAudioVoiceSends sends; + float **sendCoefficients; + float **mixCoefficients; + FAudioMixCallback *sendMix; + FAudioFilterParameters *sendFilter; + FAudioFilterState **sendFilterState; + struct + { + FAPOBufferFlags state; + uint32_t count; + FAudioEffectDescriptor *desc; + void **parameters; + uint32_t *parameterSizes; + uint8_t *parameterUpdates; + uint8_t *inPlaceProcessing; + } effects; + FAudioFilterParameters filter; + FAudioFilterState *filterState; + FAudioMutex sendLock; + FAudioMutex effectLock; + FAudioMutex filterLock; + + float volume; + float *channelVolume; + uint32_t outputChannels; + FAudioMutex volumeLock; + + FAUDIONAMELESS union + { + struct + { + /* Sample storage */ + uint32_t decodeSamples; + uint32_t resampleSamples; + + /* Resampler */ + float resampleFreq; + uint64_t resampleStep; + uint64_t resampleOffset; + uint64_t curBufferOffsetDec; + uint32_t curBufferOffset; + + /* WMA decoding */ +#ifdef HAVE_WMADEC + struct FAudioWMADEC *wmadec; +#endif /* HAVE_WMADEC*/ + + /* Read-only */ + float maxFreqRatio; + FAudioWaveFormatEx *format; + FAudioDecodeCallback decode; + FAudioResampleCallback resample; + FAudioVoiceCallback *callback; + + /* Dynamic */ + uint8_t active; + float freqRatio; + uint8_t newBuffer; + uint64_t totalSamples; + FAudioBufferEntry *bufferList; + FAudioBufferEntry *flushList; + FAudioMutex bufferLock; + } src; + struct + { + /* Sample storage */ + uint32_t inputSamples; + uint32_t outputSamples; + float *inputCache; + uint64_t resampleStep; + FAudioResampleCallback resample; + + /* Read-only */ + uint32_t inputChannels; + uint32_t inputSampleRate; + uint32_t processingStage; + } mix; + struct + { + /* Output stream, allocated by Platform */ + float *output; + + /* Needed when inputChannels != outputChannels */ + float *effectCache; + + /* Read-only */ + uint32_t inputChannels; + uint32_t inputSampleRate; + } master; + }; +}; + +/* Internal Functions */ +void FAudio_INTERNAL_InsertSubmixSorted( + LinkedList **start, + FAudioSubmixVoice *toAdd, + FAudioMutex lock, + FAudioMallocFunc pMalloc +); +void FAudio_INTERNAL_UpdateEngine(FAudio *audio, float *output); +void FAudio_INTERNAL_ResizeDecodeCache(FAudio *audio, uint32_t size); +void FAudio_INTERNAL_AllocEffectChain( + FAudioVoice *voice, + const FAudioEffectChain *pEffectChain +); +void FAudio_INTERNAL_FreeEffectChain(FAudioVoice *voice); +uint32_t FAudio_INTERNAL_VoiceOutputFrequency( + FAudioVoice *voice, + const FAudioVoiceSends *pSendList +); +extern const float FAUDIO_INTERNAL_MATRIX_DEFAULTS[8][8][64]; + +/* Debug */ + +#ifdef FAUDIO_DISABLE_DEBUGCONFIGURATION + +#define LOG_ERROR(engine, fmt, ...) +#define LOG_WARNING(engine, fmt, ...) +#define LOG_INFO(engine, fmt, ...) +#define LOG_DETAIL(engine, fmt, ...) +#define LOG_API_ENTER(engine) +#define LOG_API_EXIT(engine) +#define LOG_FUNC_ENTER(engine) +#define LOG_FUNC_EXIT(engine) +/* TODO: LOG_TIMING */ +#define LOG_MUTEX_CREATE(engine, mutex) +#define LOG_MUTEX_DESTROY(engine, mutex) +#define LOG_MUTEX_LOCK(engine, mutex) +#define LOG_MUTEX_UNLOCK(engine, mutex) +/* TODO: LOG_MEMORY */ +/* TODO: LOG_STREAMING */ + +#define LOG_FORMAT(engine, waveFormat) + +#else + +#if defined(_MSC_VER) +/* VC doesn't support __attribute__ at all, and there's no replacement for format. */ +void WINAPIV FAudio_INTERNAL_debug( + FAudio *audio, + const char *file, + uint32_t line, + const char *func, + const char *fmt, + ... +); +#if _MSC_VER <= 1700 /* <=2012 also doesn't support __func__ */ +#define __func__ __FUNCTION__ +#endif +#else +void WINAPIV FAudio_INTERNAL_debug( + FAudio *audio, + const char *file, + uint32_t line, + const char *func, + const char *fmt, + ... +) __attribute__((format(printf,5,6))); +#endif +void FAudio_INTERNAL_debug_fmt( + FAudio *audio, + const char *file, + uint32_t line, + const char *func, + const FAudioWaveFormatEx *fmt +); + +#define PRINT_DEBUG(engine, cond, type, fmt, ...) \ + if (engine->debug.TraceMask & FAUDIO_LOG_##cond) \ + { \ + FAudio_INTERNAL_debug( \ + engine, \ + __FILE__, \ + __LINE__, \ + __func__, \ + type ": " fmt, \ + __VA_ARGS__ \ + ); \ + } + +#define LOG_ERROR(engine, fmt, ...) PRINT_DEBUG(engine, ERRORS, "ERROR", fmt, __VA_ARGS__) +#define LOG_WARNING(engine, fmt, ...) PRINT_DEBUG(engine, WARNINGS, "WARNING", fmt, __VA_ARGS__) +#define LOG_INFO(engine, fmt, ...) PRINT_DEBUG(engine, INFO, "INFO", fmt, __VA_ARGS__) +#define LOG_DETAIL(engine, fmt, ...) PRINT_DEBUG(engine, DETAIL, "DETAIL", fmt, __VA_ARGS__) +#define LOG_API_ENTER(engine) PRINT_DEBUG(engine, API_CALLS, "API Enter", "%s", __func__) +#define LOG_API_EXIT(engine) PRINT_DEBUG(engine, API_CALLS, "API Exit", "%s", __func__) +#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) +/* TODO: LOG_MEMORY */ +/* TODO: LOG_STREAMING */ + +#define LOG_FORMAT(engine, waveFormat) \ + if (engine->debug.TraceMask & FAUDIO_LOG_INFO) \ + { \ + FAudio_INTERNAL_debug_fmt( \ + engine, \ + __FILE__, \ + __LINE__, \ + __func__, \ + waveFormat \ + ); \ + } + +#endif /* FAUDIO_DISABLE_DEBUGCONFIGURATION */ + +/* FAPOFX Creators */ + +#define CREATE_FAPOFX_FUNC(effect) \ + extern uint32_t FAPOFXCreate##effect( \ + FAPO **pEffect, \ + const void *pInitData, \ + uint32_t InitDataByteSize, \ + FAudioMallocFunc customMalloc, \ + FAudioFreeFunc customFree, \ + FAudioReallocFunc customRealloc, \ + uint8_t legacy \ + ); +CREATE_FAPOFX_FUNC(EQ) +CREATE_FAPOFX_FUNC(MasteringLimiter) +CREATE_FAPOFX_FUNC(Reverb) +CREATE_FAPOFX_FUNC(Echo) +#undef CREATE_FAPOFX_FUNC + +/* SIMD Stuff */ + +/* Callbacks declared as functions (rather than function pointers) are + * scalar-only, for now. SIMD versions should be possible for these. + */ + +extern void (*FAudio_INTERNAL_Convert_U8_To_F32)( + const uint8_t *restrict src, + float *restrict dst, + uint32_t len +); +extern void (*FAudio_INTERNAL_Convert_S16_To_F32)( + const int16_t *restrict src, + float *restrict dst, + uint32_t len +); +extern void (*FAudio_INTERNAL_Convert_S32_To_F32)( + const int32_t *restrict src, + float *restrict dst, + uint32_t len +); + +extern FAudioResampleCallback FAudio_INTERNAL_ResampleMono; +extern FAudioResampleCallback FAudio_INTERNAL_ResampleStereo; +extern void FAudio_INTERNAL_ResampleGeneric( + float *restrict dCache, + float *restrict resampleCache, + uint64_t *resampleOffset, + uint64_t resampleStep, + uint64_t toResample, + uint8_t channels +); + +extern void (*FAudio_INTERNAL_Amplify)( + float *output, + uint32_t totalSamples, + float volume +); + +extern FAudioMixCallback FAudio_INTERNAL_Mix_Generic; + +#define MIX_FUNC(type) \ + extern void FAudio_INTERNAL_Mix_##type##_Scalar( \ + uint32_t toMix, \ + uint32_t srcChans, \ + uint32_t dstChans, \ + float *restrict srcData, \ + float *restrict dstData, \ + float *restrict coefficients \ + ); +MIX_FUNC(Generic) +MIX_FUNC(1in_1out) +MIX_FUNC(1in_2out) +MIX_FUNC(1in_6out) +MIX_FUNC(1in_8out) +MIX_FUNC(2in_1out) +MIX_FUNC(2in_2out) +MIX_FUNC(2in_6out) +MIX_FUNC(2in_8out) +#undef MIX_FUNC + +void FAudio_INTERNAL_InitSIMDFunctions(uint8_t hasSSE2, uint8_t hasNEON); + +/* Decoders */ + +#define DECODE_FUNC(type) \ + extern void FAudio_INTERNAL_Decode##type( \ + FAudioVoice *voice, \ + FAudioBuffer *buffer, \ + float *decodeCache, \ + uint32_t samples \ + ); +DECODE_FUNC(PCM8) +DECODE_FUNC(PCM16) +DECODE_FUNC(PCM24) +DECODE_FUNC(PCM32) +DECODE_FUNC(PCM32F) +DECODE_FUNC(MonoMSADPCM) +DECODE_FUNC(StereoMSADPCM) +DECODE_FUNC(WMAERROR) +#undef DECODE_FUNC + +/* WMA decoding */ + +#ifdef HAVE_WMADEC +uint32_t FAudio_WMADEC_init(FAudioSourceVoice *pSourceVoice, uint32_t type); +void FAudio_WMADEC_free(FAudioSourceVoice *voice); +void FAudio_WMADEC_end_buffer(FAudioSourceVoice *voice); +#endif /* HAVE_WMADEC */ + +/* Platform Functions */ + +void FAudio_PlatformAddRef(void); +void FAudio_PlatformRelease(void); +void FAudio_PlatformInit( + FAudio *audio, + uint32_t flags, + uint32_t deviceIndex, + FAudioWaveFormatExtensible *mixFormat, + uint32_t *updateSize, + void** platformDevice +); +void FAudio_PlatformQuit(void* platformDevice); + +uint32_t FAudio_PlatformGetDeviceCount(void); +uint32_t FAudio_PlatformGetDeviceDetails( + uint32_t index, + FAudioDeviceDetails *details +); + +/* Threading */ + +FAudioThread FAudio_PlatformCreateThread( + FAudioThreadFunc func, + const char *name, + void* data +); +void FAudio_PlatformWaitThread(FAudioThread thread, int32_t *retval); +void FAudio_PlatformThreadPriority(FAudioThreadPriority priority); +uint64_t FAudio_PlatformGetThreadID(void); +FAudioMutex FAudio_PlatformCreateMutex(void); +void FAudio_PlatformDestroyMutex(FAudioMutex mutex); +void FAudio_PlatformLockMutex(FAudioMutex mutex); +void FAudio_PlatformUnlockMutex(FAudioMutex mutex); +void FAudio_sleep(uint32_t ms); + +/* Time */ + +uint32_t FAudio_timems(void); + +/* WaveFormatExtensible Helpers */ + +static inline uint32_t GetMask(uint16_t channels) +{ + if (channels == 1) return SPEAKER_MONO; + if (channels == 2) return SPEAKER_STEREO; + if (channels == 3) return SPEAKER_2POINT1; + if (channels == 4) return SPEAKER_QUAD; + if (channels == 5) return SPEAKER_4POINT1; + if (channels == 6) return SPEAKER_5POINT1; + if (channels == 8) return SPEAKER_7POINT1; + FAudio_assert(0 && "Unrecognized speaker layout!"); + return 0; +} + +static inline void WriteWaveFormatExtensible( + FAudioWaveFormatExtensible *fmt, + int channels, + int samplerate, + const FAudioGUID *subformat +) { + FAudio_assert(fmt != NULL); + fmt->Format.wBitsPerSample = 32; + fmt->Format.wFormatTag = FAUDIO_FORMAT_EXTENSIBLE; + fmt->Format.nChannels = channels; + fmt->Format.nSamplesPerSec = samplerate; + fmt->Format.nBlockAlign = ( + fmt->Format.nChannels * + (fmt->Format.wBitsPerSample / 8) + ); + fmt->Format.nAvgBytesPerSec = ( + fmt->Format.nSamplesPerSec * + fmt->Format.nBlockAlign + ); + fmt->Format.cbSize = sizeof(FAudioWaveFormatExtensible) - sizeof(FAudioWaveFormatEx); + fmt->Samples.wValidBitsPerSample = 32; + fmt->dwChannelMask = GetMask(fmt->Format.nChannels); + FAudio_memcpy(&fmt->SubFormat, subformat, sizeof(FAudioGUID)); +} + +/* Resampling */ + +/* Okay, so here's what all this fixed-point goo is for: + * + * Inevitably you're going to run into weird sample rates, + * both from WaveBank data and from pitch shifting changes. + * + * How we deal with this is by calculating a fixed "step" + * value that steps from sample to sample at the speed needed + * to get the correct output sample rate, and the offset + * is stored as separate integer and fraction values. + * + * This allows us to do weird fractional steps between samples, + * while at the same time not letting it drift off into death + * thanks to floating point madness. + * + * Steps are stored in fixed-point with 32 bits for the fraction: + * + * 00000000000000000000000000000000 00000000000000000000000000000000 + * ^ Integer block (32) ^ Fraction block (32) + * + * For example, to get 1.5: + * 00000000000000000000000000000001 10000000000000000000000000000000 + * + * The Integer block works exactly like you'd expect. + * The Fraction block is divided by the Integer's "One" value. + * So, the above Fraction represented visually... + * 1 << 31 + * ------- + * 1 << 32 + * ... which, simplified, is... + * 1 << 0 + * ------ + * 1 << 1 + * ... in other words, 1 / 2, or 0.5. + */ +#define FIXED_PRECISION 32 +#define FIXED_ONE (1LL << FIXED_PRECISION) + +/* Quick way to drop parts */ +#define FIXED_FRACTION_MASK (FIXED_ONE - 1) +#define FIXED_INTEGER_MASK ~FIXED_FRACTION_MASK + +/* Helper macros to convert fixed to float */ +#define DOUBLE_TO_FIXED(dbl) \ + ((uint64_t) (dbl * FIXED_ONE + 0.5)) +#define FIXED_TO_DOUBLE(fxd) ( \ + (double) (fxd >> FIXED_PRECISION) + /* Integer part */ \ + ((fxd & FIXED_FRACTION_MASK) * (1.0 / FIXED_ONE)) /* Fraction part */ \ +) +#define FIXED_TO_FLOAT(fxd) ( \ + (float) (fxd >> FIXED_PRECISION) + /* Integer part */ \ + ((fxd & FIXED_FRACTION_MASK) * (1.0f / FIXED_ONE)) /* Fraction part */ \ +) + +#ifdef FAUDIO_DUMP_VOICES +/* File writing structure */ +typedef size_t (FAUDIOCALL * FAudio_writefunc)( + void *data, + const void *src, + size_t size, + size_t count +); +typedef size_t (FAUDIOCALL * FAudio_sizefunc)( + void *data +); +typedef struct FAudioIOStreamOut +{ + void *data; + FAudio_readfunc read; + FAudio_writefunc write; + FAudio_seekfunc seek; + FAudio_sizefunc size; + FAudio_closefunc close; + void *lock; +} FAudioIOStreamOut; + +FAudioIOStreamOut* FAudio_fopen_out(const char *path, const char *mode); +void FAudio_close_out(FAudioIOStreamOut *io); +#endif /* FAUDIO_DUMP_VOICES */ + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/src/FAudio_internal_simd.c b/libs/faudio/src/FAudio_internal_simd.c new file mode 100644 index 00000000000..8746d358cad --- /dev/null +++ b/libs/faudio/src/FAudio_internal_simd.c @@ -0,0 +1,1616 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * Ethan "flibitijibibo" Lee + * + */ + +#include "FAudio_internal.h" + +/* SECTION 0: SSE/NEON Detection */ + +/* The SSE/NEON detection comes from MojoAL: + * https://hg.icculus.org/icculus/mojoAL/file/default/mojoal.c + */ + +#if defined(__x86_64__) || defined(_M_X64) + /* Some platforms fail to define this... */ + #ifndef __SSE2__ + #define __SSE2__ 1 + #endif + + /* x86_64 guarantees SSE2. */ + #define NEED_SCALAR_CONVERTER_FALLBACKS 0 +#elif defined(__aarch64__) || defined(_M_ARM64) + /* Some platforms fail to define this... */ + #ifndef __ARM_NEON__ + #define __ARM_NEON__ 1 + #endif + + /* AArch64 guarantees NEON. */ + #define NEED_SCALAR_CONVERTER_FALLBACKS 0 +#elif __MACOSX__ + /* Some build systems may need to specify this. */ + #if !defined(__SSE2__) && !defined(__ARM_NEON__) + #error macOS does not have SSE2/NEON? Bad compiler? + #endif + + /* Mac OS X/Intel guarantees SSE2. */ + #define NEED_SCALAR_CONVERTER_FALLBACKS 0 +#else + /* Need plain C implementations to support all other hardware */ + #define NEED_SCALAR_CONVERTER_FALLBACKS 1 +#endif + +/* Our NEON paths require AArch64, don't check __ARM_NEON__ here */ +#if defined(__aarch64__) || defined(_M_ARM64) +#include +#define HAVE_NEON_INTRINSICS 1 +#endif + + +#ifdef __SSE2__ +#include +#define HAVE_SSE2_INTRINSICS 1 +#endif + +/* SECTION 1: Type Converters */ + +/* The SSE/NEON converters are based on SDL_audiotypecvt: + * https://hg.libsdl.org/SDL/file/default/src/audio/SDL_audiotypecvt.c + */ + +#define DIVBY128 0.0078125f +#define DIVBY32768 0.000030517578125f +#define DIVBY8388607 0.00000011920930376163766f + +#if NEED_SCALAR_CONVERTER_FALLBACKS +void FAudio_INTERNAL_Convert_U8_To_F32_Scalar( + const uint8_t *restrict src, + float *restrict dst, + uint32_t len +) { + uint32_t i; + for (i = 0; i < len; i += 1) + { + *dst++ = (*src++ * DIVBY128) - 1.0f; + } +} + +void FAudio_INTERNAL_Convert_S16_To_F32_Scalar( + const int16_t *restrict src, + float *restrict dst, + uint32_t len +) { + uint32_t i; + for (i = 0; i < len; i += 1) + { + *dst++ = *src++ * DIVBY32768; + } +} + +void FAudio_INTERNAL_Convert_S32_To_F32_Scalar( + const int32_t *restrict src, + float *restrict dst, + uint32_t len +) { + uint32_t i; + for (i = 0; i < len; i += 1) + { + *dst++ = (*src++ >> 8) * DIVBY8388607; + } +} +#endif /* NEED_SCALAR_CONVERTER_FALLBACKS */ + +#if HAVE_SSE2_INTRINSICS +void FAudio_INTERNAL_Convert_U8_To_F32_SSE2( + const uint8_t *restrict src, + float *restrict dst, + uint32_t len +) { + int i; + src += len - 1; + dst += len - 1; + + /* Get dst aligned to 16 bytes (since buffer is growing, we don't have to worry about overreading from src) */ + for (i = len; i && (((size_t) (dst-15)) & 15); --i, --src, --dst) { + *dst = (((float) *src) * DIVBY128) - 1.0f; + } + + src -= 15; dst -= 15; /* adjust to read SSE blocks from the start. */ + FAudio_assert(!i || ((((size_t) dst) & 15) == 0)); + + /* Make sure src is aligned too. */ + if ((((size_t) src) & 15) == 0) { + /* Aligned! Do SSE blocks as long as we have 16 bytes available. */ + const __m128i *mmsrc = (const __m128i *) src; + const __m128i zero = _mm_setzero_si128(); + const __m128 divby128 = _mm_set1_ps(DIVBY128); + const __m128 minus1 = _mm_set1_ps(-1.0f); + while (i >= 16) { /* 16 * 8-bit */ + const __m128i bytes = _mm_load_si128(mmsrc); /* get 16 uint8 into an XMM register. */ + /* treat as int16, shift left to clear every other sint16, then back right with zero-extend. Now uint16. */ + const __m128i shorts1 = _mm_srli_epi16(_mm_slli_epi16(bytes, 8), 8); + /* right-shift-zero-extend gets us uint16 with the other set of values. */ + const __m128i shorts2 = _mm_srli_epi16(bytes, 8); + /* unpack against zero to make these int32, convert to float, multiply, add. Whew! */ + /* Note that AVX2 can do floating point multiply+add in one instruction, fwiw. SSE2 cannot. */ + const __m128 floats1 = _mm_add_ps(_mm_mul_ps(_mm_cvtepi32_ps(_mm_unpacklo_epi16(shorts1, zero)), divby128), minus1); + const __m128 floats2 = _mm_add_ps(_mm_mul_ps(_mm_cvtepi32_ps(_mm_unpacklo_epi16(shorts2, zero)), divby128), minus1); + const __m128 floats3 = _mm_add_ps(_mm_mul_ps(_mm_cvtepi32_ps(_mm_unpackhi_epi16(shorts1, zero)), divby128), minus1); + const __m128 floats4 = _mm_add_ps(_mm_mul_ps(_mm_cvtepi32_ps(_mm_unpackhi_epi16(shorts2, zero)), divby128), minus1); + /* Interleave back into correct order, store. */ + _mm_store_ps(dst, _mm_unpacklo_ps(floats1, floats2)); + _mm_store_ps(dst+4, _mm_unpackhi_ps(floats1, floats2)); + _mm_store_ps(dst+8, _mm_unpacklo_ps(floats3, floats4)); + _mm_store_ps(dst+12, _mm_unpackhi_ps(floats3, floats4)); + i -= 16; mmsrc--; dst -= 16; + } + + src = (const uint8_t *) mmsrc; + } + + src += 15; dst += 15; /* adjust for any scalar finishing. */ + + /* Finish off any leftovers with scalar operations. */ + while (i) { + *dst = (((float) *src) * DIVBY128) - 1.0f; + i--; src--; dst--; + } +} + +void FAudio_INTERNAL_Convert_S16_To_F32_SSE2( + const int16_t *restrict src, + float *restrict dst, + uint32_t len +) { + int i; + src += len - 1; + dst += len - 1; + + /* Get dst aligned to 16 bytes (since buffer is growing, we don't have to worry about overreading from src) */ + for (i = len; i && (((size_t) (dst-7)) & 15); --i, --src, --dst) { + *dst = ((float) *src) * DIVBY32768; + } + + src -= 7; dst -= 7; /* adjust to read SSE blocks from the start. */ + FAudio_assert(!i || ((((size_t) dst) & 15) == 0)); + + /* Make sure src is aligned too. */ + if ((((size_t) src) & 15) == 0) { + /* Aligned! Do SSE blocks as long as we have 16 bytes available. */ + const __m128 divby32768 = _mm_set1_ps(DIVBY32768); + while (i >= 8) { /* 8 * 16-bit */ + const __m128i ints = _mm_load_si128((__m128i const *) src); /* get 8 sint16 into an XMM register. */ + /* treat as int32, shift left to clear every other sint16, then back right with sign-extend. Now sint32. */ + const __m128i a = _mm_srai_epi32(_mm_slli_epi32(ints, 16), 16); + /* right-shift-sign-extend gets us sint32 with the other set of values. */ + const __m128i b = _mm_srai_epi32(ints, 16); + /* Interleave these back into the right order, convert to float, multiply, store. */ + _mm_store_ps(dst, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpacklo_epi32(a, b)), divby32768)); + _mm_store_ps(dst+4, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpackhi_epi32(a, b)), divby32768)); + i -= 8; src -= 8; dst -= 8; + } + } + + src += 7; dst += 7; /* adjust for any scalar finishing. */ + + /* Finish off any leftovers with scalar operations. */ + while (i) { + *dst = ((float) *src) * DIVBY32768; + i--; src--; dst--; + } +} + +void FAudio_INTERNAL_Convert_S32_To_F32_SSE2( + const int32_t *restrict src, + float *restrict dst, + uint32_t len +) { + int i; + + /* Get dst aligned to 16 bytes */ + for (i = len; i && (((size_t) dst) & 15); --i, ++src, ++dst) { + *dst = ((float) (*src>>8)) * DIVBY8388607; + } + + FAudio_assert(!i || ((((size_t) dst) & 15) == 0)); + + /* Make sure src is aligned too. */ + if ((((size_t) src) & 15) == 0) { + /* Aligned! Do SSE blocks as long as we have 16 bytes available. */ + const __m128 divby8388607 = _mm_set1_ps(DIVBY8388607); + const __m128i *mmsrc = (const __m128i *) src; + while (i >= 4) { /* 4 * sint32 */ + /* shift out lowest bits so int fits in a float32. Small precision loss, but much faster. */ + _mm_store_ps(dst, _mm_mul_ps(_mm_cvtepi32_ps(_mm_srai_epi32(_mm_load_si128(mmsrc), 8)), divby8388607)); + i -= 4; mmsrc++; dst += 4; + } + src = (const int32_t *) mmsrc; + } + + /* Finish off any leftovers with scalar operations. */ + while (i) { + *dst = ((float) (*src>>8)) * DIVBY8388607; + i--; src++; dst++; + } +} +#endif /* HAVE_SSE2_INTRINSICS */ + +#if HAVE_NEON_INTRINSICS +void FAudio_INTERNAL_Convert_U8_To_F32_NEON( + const uint8_t *restrict src, + float *restrict dst, + uint32_t len +) { + int i; + src += len - 1; + dst += len - 1; + + /* Get dst aligned to 16 bytes (since buffer is growing, we don't have to worry about overreading from src) */ + for (i = len; i && (((size_t) (dst-15)) & 15); --i, --src, --dst) { + *dst = (((float) *src) * DIVBY128) - 1.0f; + } + + src -= 15; dst -= 15; /* adjust to read NEON blocks from the start. */ + FAudio_assert(!i || ((((size_t) dst) & 15) == 0)); + + /* Make sure src is aligned too. */ + if ((((size_t) src) & 15) == 0) { + /* Aligned! Do NEON blocks as long as we have 16 bytes available. */ + const uint8_t *mmsrc = (const uint8_t *) src; + const float32x4_t divby128 = vdupq_n_f32(DIVBY128); + const float32x4_t negone = vdupq_n_f32(-1.0f); + while (i >= 16) { /* 16 * 8-bit */ + const uint8x16_t bytes = vld1q_u8(mmsrc); /* get 16 uint8 into a NEON register. */ + 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)); + i -= 16; mmsrc -= 16; dst -= 16; + } + + src = (const uint8_t *) mmsrc; + } + + src += 15; dst += 15; /* adjust for any scalar finishing. */ + + /* Finish off any leftovers with scalar operations. */ + while (i) { + *dst = (((float) *src) * DIVBY128) - 1.0f; + i--; src--; dst--; + } +} + +void FAudio_INTERNAL_Convert_S16_To_F32_NEON( + const int16_t *restrict src, + float *restrict dst, + uint32_t len +) { + int i; + src += len - 1; + dst += len - 1; + + /* Get dst aligned to 16 bytes (since buffer is growing, we don't have to worry about overreading from src) */ + for (i = len; i && (((size_t) (dst-7)) & 15); --i, --src, --dst) { + *dst = ((float) *src) * DIVBY32768; + } + + src -= 7; dst -= 7; /* adjust to read NEON blocks from the start. */ + FAudio_assert(!i || ((((size_t) dst) & 15) == 0)); + + /* Make sure src is aligned too. */ + if ((((size_t) src) & 15) == 0) { + /* Aligned! Do NEON blocks as long as we have 16 bytes available. */ + const float32x4_t divby32768 = vdupq_n_f32(DIVBY32768); + while (i >= 8) { /* 8 * 16-bit */ + const int16x8_t ints = vld1q_s16((int16_t const *) src); /* get 8 sint16 into a NEON register. */ + /* split int16 to two int32, then convert to float, then multiply to normalize, store. */ + vst1q_f32(dst, vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_low_s16(ints))), divby32768)); + vst1q_f32(dst+4, vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_high_s16(ints))), divby32768)); + i -= 8; src -= 8; dst -= 8; + } + } + + src += 7; dst += 7; /* adjust for any scalar finishing. */ + + /* Finish off any leftovers with scalar operations. */ + while (i) { + *dst = ((float) *src) * DIVBY32768; + i--; src--; dst--; + } +} + +void FAudio_INTERNAL_Convert_S32_To_F32_NEON( + const int32_t *restrict src, + float *restrict dst, + uint32_t len +) { + int i; + + /* Get dst aligned to 16 bytes */ + for (i = len; i && (((size_t) dst) & 15); --i, ++src, ++dst) { + *dst = ((float) (*src>>8)) * DIVBY8388607; + } + + FAudio_assert(!i || ((((size_t) dst) & 15) == 0)); + + /* Make sure src is aligned too. */ + if ((((size_t) src) & 15) == 0) { + /* Aligned! Do NEON blocks as long as we have 16 bytes available. */ + const float32x4_t divby8388607 = vdupq_n_f32(DIVBY8388607); + const int32_t *mmsrc = (const int32_t *) src; + while (i >= 4) { /* 4 * sint32 */ + /* shift out lowest bits so int fits in a float32. Small precision loss, but much faster. */ + vst1q_f32(dst, vmulq_f32(vcvtq_f32_s32(vshrq_n_s32(vld1q_s32(mmsrc), 8)), divby8388607)); + i -= 4; mmsrc += 4; dst += 4; + } + src = (const int32_t *) mmsrc; + } + + /* Finish off any leftovers with scalar operations. */ + while (i) { + *dst = ((float) (*src>>8)) * DIVBY8388607; + i--; src++; dst++; + } +} +#endif /* HAVE_NEON_INTRINSICS */ + +/* SECTION 2: Linear Resamplers */ + +void FAudio_INTERNAL_ResampleGeneric( + float *restrict dCache, + float *restrict resampleCache, + uint64_t *resampleOffset, + uint64_t resampleStep, + uint64_t toResample, + uint8_t channels +) { + uint32_t i, j; + uint64_t cur = *resampleOffset & FIXED_FRACTION_MASK; + for (i = 0; i < toResample; i += 1) + { + for (j = 0; j < channels; j += 1) + { + /* lerp, then convert to float value */ + *resampleCache++ = (float) ( + dCache[j] + + (dCache[j + channels] - dCache[j]) * + FIXED_TO_DOUBLE(cur) + ); + } + + /* Increment fraction offset by the stepping value */ + *resampleOffset += resampleStep; + cur += resampleStep; + + /* Only increment the sample offset by integer values. + * Sometimes this will be 0 until cur accumulates + * enough steps, especially for "slow" rates. + */ + dCache += (cur >> FIXED_PRECISION) * channels; + + /* Now that any integer has been added, drop it. + * The offset pointer will preserve the total. + */ + cur &= FIXED_FRACTION_MASK; + } +} + +#if NEED_SCALAR_CONVERTER_FALLBACKS +void FAudio_INTERNAL_ResampleMono_Scalar( + float *restrict dCache, + float *restrict resampleCache, + uint64_t *resampleOffset, + uint64_t resampleStep, + uint64_t toResample, + uint8_t UNUSED +) { + uint32_t i; + uint64_t cur = *resampleOffset & FIXED_FRACTION_MASK; + for (i = 0; i < toResample; i += 1) + { + /* lerp, then convert to float value */ + *resampleCache++ = (float) ( + dCache[0] + + (dCache[1] - dCache[0]) * + FIXED_TO_DOUBLE(cur) + ); + + /* Increment fraction offset by the stepping value */ + *resampleOffset += resampleStep; + cur += resampleStep; + + /* Only increment the sample offset by integer values. + * Sometimes this will be 0 until cur accumulates + * enough steps, especially for "slow" rates. + */ + dCache += (cur >> FIXED_PRECISION); + + /* Now that any integer has been added, drop it. + * The offset pointer will preserve the total. + */ + cur &= FIXED_FRACTION_MASK; + } +} + +void FAudio_INTERNAL_ResampleStereo_Scalar( + float *restrict dCache, + float *restrict resampleCache, + uint64_t *resampleOffset, + uint64_t resampleStep, + uint64_t toResample, + uint8_t UNUSED +) { + uint32_t i; + uint64_t cur = *resampleOffset & FIXED_FRACTION_MASK; + for (i = 0; i < toResample; i += 1) + { + /* lerp, then convert to float value */ + *resampleCache++ = (float) ( + dCache[0] + + (dCache[2] - dCache[0]) * + FIXED_TO_DOUBLE(cur) + ); + *resampleCache++ = (float) ( + dCache[1] + + (dCache[3] - dCache[1]) * + FIXED_TO_DOUBLE(cur) + ); + + /* Increment fraction offset by the stepping value */ + *resampleOffset += resampleStep; + cur += resampleStep; + + /* Only increment the sample offset by integer values. + * Sometimes this will be 0 until cur accumulates + * enough steps, especially for "slow" rates. + */ + dCache += (cur >> FIXED_PRECISION) * 2; + + /* Now that any integer has been added, drop it. + * The offset pointer will preserve the total. + */ + cur &= FIXED_FRACTION_MASK; + } +} +#endif /* NEED_SCALAR_CONVERTER_FALLBACKS */ + +/* The SSE2 versions of the resamplers come from @8thMage! */ + +#if HAVE_SSE2_INTRINSICS +void FAudio_INTERNAL_ResampleMono_SSE2( + float *restrict dCache, + float *restrict resampleCache, + uint64_t *resampleOffset, + uint64_t resampleStep, + uint64_t toResample, + uint8_t UNUSED +) { + uint32_t i, header, tail; + uint64_t cur_scalar_1, cur_scalar_2, cur_scalar_3; + float *dCache_1, *dCache_2, *dCache_3; + uint64_t cur_scalar = *resampleOffset & FIXED_FRACTION_MASK; + __m128 one_over_fixed_one, half, current_next_0_1, current_next_2_3, + current, next, sub, cur_fixed, mul, res; + __m128i cur_frac, adder_frac, adder_frac_loop; + + /* This is the header, the Dest needs to be aligned to 16B */ + header = (16 - ((size_t) resampleCache) % 16) / 4; + if (header == 4) + { + header = 0; + } + for (i = 0; i < header; i += 1) + { + /* lerp, then convert to float value */ + *resampleCache++ = (float) ( + dCache[0] + + (dCache[1] - dCache[0]) * + FIXED_TO_FLOAT(cur_scalar) + ); + + /* Increment fraction offset by the stepping value */ + *resampleOffset += resampleStep; + cur_scalar += resampleStep; + + /* Only increment the sample offset by integer values. + * Sometimes this will be 0 until cur accumulates + * enough steps, especially for "slow" rates. + */ + dCache += (cur_scalar >> FIXED_PRECISION); + + /* Now that any integer has been added, drop it. + * The offset pointer will preserve the total. + */ + cur_scalar &= FIXED_FRACTION_MASK; + } + + toResample -= header; + + /* initialising the varius cur + * cur_frac is the fractional part of cur with 4 samples. as the + * fractional part is 32 bit unsigned value, it can be just added + * and the modulu operation for keeping the fractional part will be implicit. + * the 0.5 is for converting signed values to float (no unsigned convert), + * the 0.5 is added later. + */ + cur_frac = _mm_set1_epi32( + (uint32_t) (cur_scalar & FIXED_FRACTION_MASK) - DOUBLE_TO_FIXED(0.5) + ); + adder_frac = _mm_setr_epi32( + 0, + (uint32_t) (resampleStep & FIXED_FRACTION_MASK), + (uint32_t) ((resampleStep * 2) & FIXED_FRACTION_MASK), + (uint32_t) ((resampleStep * 3) & FIXED_FRACTION_MASK) + ); + cur_frac = _mm_add_epi32(cur_frac, adder_frac); + + /* The various cur_scalar is for the different samples + * (1, 2, 3 compared to original cur_scalar = 0) + */ + cur_scalar_1 = cur_scalar + resampleStep; + cur_scalar_2 = cur_scalar + resampleStep * 2; + cur_scalar_3 = cur_scalar + resampleStep * 3; + dCache_1 = dCache + (cur_scalar_1 >> FIXED_PRECISION); + dCache_2 = dCache + (cur_scalar_2 >> FIXED_PRECISION); + dCache_3 = dCache + (cur_scalar_3 >> FIXED_PRECISION); + cur_scalar &= FIXED_FRACTION_MASK; + cur_scalar_1 &= FIXED_FRACTION_MASK; + cur_scalar_2 &= FIXED_FRACTION_MASK; + cur_scalar_3 &= FIXED_FRACTION_MASK; + + /* FIXME: These should be _mm_undefined_ps! */ + current_next_0_1 = _mm_setzero_ps(); + current_next_2_3 = _mm_setzero_ps(); + + /* Constants */ + one_over_fixed_one = _mm_set1_ps(1.0f / FIXED_ONE); + half = _mm_set1_ps(0.5f); + adder_frac_loop = _mm_set1_epi32( + (uint32_t) ((resampleStep * 4) & FIXED_FRACTION_MASK) + ); + + tail = toResample % 4; + for (i = 0; i < toResample - tail; i += 4, resampleCache += 4) + { + /* current next holds 2 pairs of the sample and the sample + 1 + * after that need to seperate them. + */ + + current_next_0_1 = _mm_loadl_pi(current_next_0_1, (__m64*) dCache); + current_next_0_1 = _mm_loadh_pi(current_next_0_1, (__m64*) dCache_1); + current_next_2_3 = _mm_loadl_pi(current_next_2_3, (__m64*) dCache_2); + current_next_2_3 = _mm_loadh_pi(current_next_2_3, (__m64*) dCache_3); + + /* Unpack them to have seperate current and next in 2 vectors. */ + current = _mm_shuffle_ps(current_next_0_1, current_next_2_3, 0x88); /* 0b1000 */ + next = _mm_shuffle_ps(current_next_0_1, current_next_2_3, 0xdd); /* 0b1101 */ + + sub = _mm_sub_ps(next, current); + + /* Convert the fractional part to float and then mul to get the fractions out. + * then add back the 0.5 we subtracted before. + */ + cur_fixed = _mm_add_ps( + _mm_mul_ps( + _mm_cvtepi32_ps(cur_frac), + one_over_fixed_one + ), + half + ); + mul = _mm_mul_ps(sub, cur_fixed); + res = _mm_add_ps(current, mul); + + /* Store back */ + _mm_store_ps(resampleCache, res); + + /* Update dCaches for next iteration */ + cur_scalar += resampleStep * 4; + cur_scalar_1 += resampleStep * 4; + cur_scalar_2 += resampleStep * 4; + cur_scalar_3 += resampleStep * 4; + dCache = dCache + (cur_scalar >> FIXED_PRECISION); + dCache_1 = dCache_1 + (cur_scalar_1 >> FIXED_PRECISION); + dCache_2 = dCache_2 + (cur_scalar_2 >> FIXED_PRECISION); + dCache_3 = dCache_3 + (cur_scalar_3 >> FIXED_PRECISION); + cur_scalar &= FIXED_FRACTION_MASK; + cur_scalar_1 &= FIXED_FRACTION_MASK; + cur_scalar_2 &= FIXED_FRACTION_MASK; + cur_scalar_3 &= FIXED_FRACTION_MASK; + + cur_frac = _mm_add_epi32(cur_frac, adder_frac_loop); + } + *resampleOffset += resampleStep * (toResample - tail); + + /* This is the tail. */ + for (i = 0; i < tail; i += 1) + { + /* lerp, then convert to float value */ + *resampleCache++ = (float) ( + dCache[0] + + (dCache[1] - dCache[0]) * + FIXED_TO_FLOAT(cur_scalar) + ); + + /* Increment fraction offset by the stepping value */ + *resampleOffset += resampleStep; + cur_scalar += resampleStep; + + /* Only increment the sample offset by integer values. + * Sometimes this will be 0 until cur accumulates + * enough steps, especially for "slow" rates. + */ + dCache += (cur_scalar >> FIXED_PRECISION); + + /* Now that any integer has been added, drop it. + * The offset pointer will preserve the total. + */ + cur_scalar &= FIXED_FRACTION_MASK; + } +} + +void FAudio_INTERNAL_ResampleStereo_SSE2( + float *restrict dCache, + float *restrict resampleCache, + uint64_t *resampleOffset, + uint64_t resampleStep, + uint64_t toResample, + uint8_t UNUSED +) { + uint32_t i, header, tail; + uint64_t cur_scalar, cur_scalar_1; + float *dCache_1; + __m128 one_over_fixed_one, half, current_next_1, current_next_2, + current, next, sub, cur_fixed, mul, res; + __m128i cur_frac, adder_frac, adder_frac_loop; + + /* This is the header, the Dest needs to be aligned to 16B */ + header = (16 - ((size_t) resampleCache) % 16) / 8; + if (header == 2) + { + header = 0; + } + cur_scalar = *resampleOffset & FIXED_FRACTION_MASK; + for (i = 0; i < header; i += 2) + { + /* lerp, then convert to float value */ + *resampleCache++ = (float) ( + dCache[0] + + (dCache[2] - dCache[0]) * + FIXED_TO_FLOAT(cur_scalar) + ); + *resampleCache++ = (float) ( + dCache[1] + + (dCache[3] - dCache[1]) * + FIXED_TO_FLOAT(cur_scalar) + ); + + /* Increment fraction offset by the stepping value */ + *resampleOffset += resampleStep; + cur_scalar += resampleStep; + + /* Only increment the sample offset by integer values. + * Sometimes this will be 0 until cur accumulates + * enough steps, especially for "slow" rates. + */ + dCache += (cur_scalar >> FIXED_PRECISION) * 2; + + /* Now that any integer has been added, drop it. + * The offset pointer will preserve the total. + */ + cur_scalar &= FIXED_FRACTION_MASK; + } + + toResample -= header; + + /* initialising the varius cur. + * cur_frac holds the fractional part of cur. + * to avoid duplication please see the mono part for a thorough + * explanation. + */ + cur_frac = _mm_set1_epi32( + (uint32_t) (cur_scalar & FIXED_FRACTION_MASK) - DOUBLE_TO_FIXED(0.5) + ); + adder_frac = _mm_setr_epi32( + 0, + 0, + (uint32_t) (resampleStep & FIXED_FRACTION_MASK), + (uint32_t) (resampleStep & FIXED_FRACTION_MASK) + ); + cur_frac = _mm_add_epi32(cur_frac, adder_frac); + + /* dCache_1 is the pointer for dcache in the next resample pos. */ + cur_scalar_1 = cur_scalar + resampleStep; + dCache_1 = dCache + (cur_scalar_1 >> FIXED_PRECISION) * 2; + cur_scalar_1 &= FIXED_FRACTION_MASK; + + one_over_fixed_one = _mm_set1_ps(1.0f / FIXED_ONE); + half = _mm_set1_ps(0.5f); + adder_frac_loop = _mm_set1_epi32( + (uint32_t) ((resampleStep * 2) & FIXED_FRACTION_MASK) + ); + + tail = toResample % 2; + for (i = 0; i < toResample - tail; i += 2, resampleCache += 4) + { + /* Current_next_1 and current_next_2 each holds 4 src + * sample points for getting 4 dest resample point at the end. + * current_next_1 holds: + * (current_ch_1, current_ch_2, next_ch_1, next_ch_2) + * for the first resample position, while current_next_2 holds + * the same for the 2nd resample position + */ + current_next_1 = _mm_loadu_ps(dCache); /* A1B1A2B2 */ + current_next_2 = _mm_loadu_ps(dCache_1); /* A3B3A4B4 */ + + /* Unpack them to get the current and the next in seperate vectors. */ + current = _mm_castpd_ps( + _mm_unpacklo_pd( + _mm_castps_pd(current_next_1), + _mm_castps_pd(current_next_2) + ) + ); + next = _mm_castpd_ps( + _mm_unpackhi_pd( + _mm_castps_pd(current_next_1), + _mm_castps_pd(current_next_2) + ) + ); + + sub = _mm_sub_ps(next, current); + + /* Adding the 0.5 back. + * See mono explanation for more elaborate explanation. + */ + cur_fixed = _mm_add_ps( + _mm_mul_ps( + _mm_cvtepi32_ps(cur_frac), + one_over_fixed_one + ), + half + ); + mul = _mm_mul_ps(sub, cur_fixed); + res = _mm_add_ps(current, mul); + + /* Store the results */ + _mm_store_ps(resampleCache, res); + + /* Update dCaches for next iteration */ + cur_scalar += resampleStep * 2; + cur_scalar_1 += resampleStep * 2; + dCache = dCache + (cur_scalar >> FIXED_PRECISION) * 2; + dCache_1 = dCache_1 + (cur_scalar_1 >> FIXED_PRECISION) * 2; + cur_scalar &= FIXED_FRACTION_MASK; + cur_scalar_1 &= FIXED_FRACTION_MASK; + + cur_frac = _mm_add_epi32(cur_frac, adder_frac_loop); + } + *resampleOffset += resampleStep * (toResample - tail); + + /* This is the tail. */ + for (i = 0; i < tail; i += 1) + { + /* lerp, then convert to float value */ + *resampleCache++ = (float) ( + dCache[0] + + (dCache[2] - dCache[0]) * + FIXED_TO_FLOAT(cur_scalar) + ); + *resampleCache++ = (float) ( + dCache[1] + + (dCache[3] - dCache[1]) * + FIXED_TO_FLOAT(cur_scalar) + ); + + /* Increment fraction offset by the stepping value */ + *resampleOffset += resampleStep; + cur_scalar += resampleStep; + + /* Only increment the sample offset by integer values. + * Sometimes this will be 0 until cur accumulates + * enough steps, especially for "slow" rates. + */ + dCache += (cur_scalar >> FIXED_PRECISION) * 2; + + /* Now that any integer has been added, drop it. + * The offset pointer will preserve the total. + */ + cur_scalar &= FIXED_FRACTION_MASK; + } +} +#endif /* HAVE_SSE2_INTRINSICS */ + +#if HAVE_NEON_INTRINSICS +void FAudio_INTERNAL_ResampleMono_NEON( + float *restrict dCache, + float *restrict resampleCache, + uint64_t *resampleOffset, + uint64_t resampleStep, + uint64_t toResample, + uint8_t UNUSED +) { + uint32_t i, header, tail; + uint64_t cur_scalar_1, cur_scalar_2, cur_scalar_3; + float *dCache_1, *dCache_2, *dCache_3; + uint64_t cur_scalar = *resampleOffset & FIXED_FRACTION_MASK; + float32x4_t one_over_fixed_one, half, current_next_0_1, current_next_2_3, + current, next, sub, cur_fixed, mul, res; + int32x4_t cur_frac, adder_frac, adder_frac_loop; + + /* This is the header, the Dest needs to be aligned to 16B */ + header = (16 - ((size_t) resampleCache) % 16) / 4; + if (header == 4) + { + header = 0; + } + for (i = 0; i < header; i += 1) + { + /* lerp, then convert to float value */ + *resampleCache++ = (float) ( + dCache[0] + + (dCache[1] - dCache[0]) * + FIXED_TO_FLOAT(cur_scalar) + ); + + /* Increment fraction offset by the stepping value */ + *resampleOffset += resampleStep; + cur_scalar += resampleStep; + + /* Only increment the sample offset by integer values. + * Sometimes this will be 0 until cur accumulates + * enough steps, especially for "slow" rates. + */ + dCache += (cur_scalar >> FIXED_PRECISION); + + /* Now that any integer has been added, drop it. + * The offset pointer will preserve the total. + */ + cur_scalar &= FIXED_FRACTION_MASK; + } + + toResample -= header; + + /* initialising the varius cur + * cur_frac is the fractional part of cur with 4 samples. as the + * fractional part is 32 bit unsigned value, it can be just added + * and the modulu operation for keeping the fractional part will be implicit. + * the 0.5 is for converting signed values to float (no unsigned convert), + * the 0.5 is added later. + */ + cur_frac = vdupq_n_s32( + (uint32_t) (cur_scalar & FIXED_FRACTION_MASK) - DOUBLE_TO_FIXED(0.5) + ); + int32_t __attribute__((aligned(16))) data[4] = + { + 0, + (uint32_t) (resampleStep & FIXED_FRACTION_MASK), + (uint32_t) ((resampleStep * 2) & FIXED_FRACTION_MASK), + (uint32_t) ((resampleStep * 3) & FIXED_FRACTION_MASK) + }; + adder_frac = vld1q_s32(data); + cur_frac = vaddq_s32(cur_frac, adder_frac); + + /* The various cur_scalar is for the different samples + * (1, 2, 3 compared to original cur_scalar = 0) + */ + cur_scalar_1 = cur_scalar + resampleStep; + cur_scalar_2 = cur_scalar + resampleStep * 2; + cur_scalar_3 = cur_scalar + resampleStep * 3; + dCache_1 = dCache + (cur_scalar_1 >> FIXED_PRECISION); + dCache_2 = dCache + (cur_scalar_2 >> FIXED_PRECISION); + dCache_3 = dCache + (cur_scalar_3 >> FIXED_PRECISION); + cur_scalar &= FIXED_FRACTION_MASK; + cur_scalar_1 &= FIXED_FRACTION_MASK; + cur_scalar_2 &= FIXED_FRACTION_MASK; + cur_scalar_3 &= FIXED_FRACTION_MASK; + + /* Constants */ + one_over_fixed_one = vdupq_n_f32(1.0f / FIXED_ONE); + half = vdupq_n_f32(0.5f); + adder_frac_loop = vdupq_n_s32( + (uint32_t) ((resampleStep * 4) & FIXED_FRACTION_MASK) + ); + + tail = toResample % 4; + for (i = 0; i < toResample - tail; i += 4, resampleCache += 4) + { + /* current next holds 2 pairs of the sample and the sample + 1 + * after that need to separate them. + */ + current_next_0_1 = vcombine_f32( + vld1_f32(dCache), + vld1_f32(dCache_1) + ); + current_next_2_3 = vcombine_f32( + vld1_f32(dCache_2), + vld1_f32(dCache_3) + ); + + /* Unpack them to have seperate current and next in 2 vectors. */ + current = vuzp1q_f32(current_next_0_1, current_next_2_3); + next = vuzp2q_f32(current_next_0_1, current_next_2_3); + + sub = vsubq_f32(next, current); + + /* Convert the fractional part to float and then mul to get the fractions out. + * then add back the 0.5 we subtracted before. + */ + cur_fixed = vaddq_f32( + vmulq_f32( + vcvtq_f32_s32(cur_frac), + one_over_fixed_one + ), + half + ); + mul = vmulq_f32(sub, cur_fixed); + res = vaddq_f32(current, mul); + + /* Store back */ + vst1q_f32(resampleCache, res); + + /* Update dCaches for next iteration */ + cur_scalar += resampleStep * 4; + cur_scalar_1 += resampleStep * 4; + cur_scalar_2 += resampleStep * 4; + cur_scalar_3 += resampleStep * 4; + dCache = dCache + (cur_scalar >> FIXED_PRECISION); + dCache_1 = dCache_1 + (cur_scalar_1 >> FIXED_PRECISION); + dCache_2 = dCache_2 + (cur_scalar_2 >> FIXED_PRECISION); + dCache_3 = dCache_3 + (cur_scalar_3 >> FIXED_PRECISION); + cur_scalar &= FIXED_FRACTION_MASK; + cur_scalar_1 &= FIXED_FRACTION_MASK; + cur_scalar_2 &= FIXED_FRACTION_MASK; + cur_scalar_3 &= FIXED_FRACTION_MASK; + + cur_frac = vaddq_s32(cur_frac, adder_frac_loop); + } + *resampleOffset += resampleStep * (toResample - tail); + + /* This is the tail. */ + for (i = 0; i < tail; i += 1) + { + /* lerp, then convert to float value */ + *resampleCache++ = (float) ( + dCache[0] + + (dCache[1] - dCache[0]) * + FIXED_TO_FLOAT(cur_scalar) + ); + + /* Increment fraction offset by the stepping value */ + *resampleOffset += resampleStep; + cur_scalar += resampleStep; + + /* Only increment the sample offset by integer values. + * Sometimes this will be 0 until cur accumulates + * enough steps, especially for "slow" rates. + */ + dCache += (cur_scalar >> FIXED_PRECISION); + + /* Now that any integer has been added, drop it. + * The offset pointer will preserve the total. + */ + cur_scalar &= FIXED_FRACTION_MASK; + } +} + +void FAudio_INTERNAL_ResampleStereo_NEON( + float *restrict dCache, + float *restrict resampleCache, + uint64_t *resampleOffset, + uint64_t resampleStep, + uint64_t toResample, + uint8_t channels +) { + uint32_t i, header, tail; + uint64_t cur_scalar, cur_scalar_1; + float *dCache_1; + float32x4_t one_over_fixed_one, half, current, next, sub, cur_fixed, mul, res; + int32x4_t cur_frac, adder_frac, adder_frac_loop; + + /* This is the header, the Dest needs to be aligned to 16B */ + header = (16 - ((size_t) resampleCache) % 16) / 8; + if (header == 2) + { + header = 0; + } + cur_scalar = *resampleOffset & FIXED_FRACTION_MASK; + for (i = 0; i < header; i += 2) + { + /* lerp, then convert to float value */ + *resampleCache++ = (float) ( + dCache[0] + + (dCache[2] - dCache[0]) * + FIXED_TO_FLOAT(cur_scalar) + ); + *resampleCache++ = (float) ( + dCache[1] + + (dCache[3] - dCache[1]) * + FIXED_TO_FLOAT(cur_scalar) + ); + + /* Increment fraction offset by the stepping value */ + *resampleOffset += resampleStep; + cur_scalar += resampleStep; + + /* Only increment the sample offset by integer values. + * Sometimes this will be 0 until cur accumulates + * enough steps, especially for "slow" rates. + */ + dCache += (cur_scalar >> FIXED_PRECISION) * 2; + + /* Now that any integer has been added, drop it. + * The offset pointer will preserve the total. + */ + cur_scalar &= FIXED_FRACTION_MASK; + } + + toResample -= header; + + /* initialising the varius cur. + * cur_frac holds the fractional part of cur. + * to avoid duplication please see the mono part for a thorough + * explanation. + */ + cur_frac = vdupq_n_s32( + (uint32_t) (cur_scalar & FIXED_FRACTION_MASK) - DOUBLE_TO_FIXED(0.5) + ); + int32_t __attribute__((aligned(16))) data[4] = + { + 0, + 0, + (uint32_t) (resampleStep & FIXED_FRACTION_MASK), + (uint32_t) (resampleStep & FIXED_FRACTION_MASK) + }; + adder_frac = vld1q_s32(data); + cur_frac = vaddq_s32(cur_frac, adder_frac); + + /* dCache_1 is the pointer for dcache in the next resample pos. */ + cur_scalar_1 = cur_scalar + resampleStep; + dCache_1 = dCache + (cur_scalar_1 >> FIXED_PRECISION) * 2; + cur_scalar_1 &= FIXED_FRACTION_MASK; + + one_over_fixed_one = vdupq_n_f32(1.0f / FIXED_ONE); + half = vdupq_n_f32(0.5f); + adder_frac_loop = vdupq_n_s32( + (uint32_t) ((resampleStep * 2) & FIXED_FRACTION_MASK) + ); + + tail = toResample % 2; + for (i = 0; i < toResample - tail; i += 2, resampleCache += 4) + { + /* Current_next_1 and current_next_2 each holds 4 src + * sample points for getting 4 dest resample point at the end. + * current_next_1 holds: + * (current_ch_1, current_ch_2, next_ch_1, next_ch_2) + * for the first resample position, while current_next_2 holds + * the same for the 2nd resample position + */ + current = vcombine_f32( + vld1_f32(dCache), /* A1B1 */ + vld1_f32(dCache_1) /* A3B3 */ + ); + next = vcombine_f32( + vld1_f32(dCache + 2), /* A2B2 */ + vld1_f32(dCache_1 + 2) /* A4B4 */ + ); + + sub = vsubq_f32(next, current); + + /* Adding the 0.5 back. + * See mono explanation for more elaborate explanation. + */ + cur_fixed = vaddq_f32( + vmulq_f32( + vcvtq_f32_s32(cur_frac), + one_over_fixed_one + ), + half + ); + mul = vmulq_f32(sub, cur_fixed); + res = vaddq_f32(current, mul); + + /* Store the results */ + vst1q_f32(resampleCache, res); + + /* Update dCaches for next iteration */ + cur_scalar += resampleStep * 2; + cur_scalar_1 += resampleStep * 2; + dCache = dCache + (cur_scalar >> FIXED_PRECISION) * 2; + dCache_1 = dCache_1 + (cur_scalar_1 >> FIXED_PRECISION) * 2; + cur_scalar &= FIXED_FRACTION_MASK; + cur_scalar_1 &= FIXED_FRACTION_MASK; + + cur_frac = vaddq_s32(cur_frac, adder_frac_loop); + } + *resampleOffset += resampleStep * (toResample - tail); + + /* This is the tail. */ + for (i = 0; i < tail; i += 1) + { + /* lerp, then convert to float value */ + *resampleCache++ = (float) ( + dCache[0] + + (dCache[2] - dCache[0]) * + FIXED_TO_FLOAT(cur_scalar) + ); + *resampleCache++ = (float) ( + dCache[1] + + (dCache[3] - dCache[1]) * + FIXED_TO_FLOAT(cur_scalar) + ); + + /* Increment fraction offset by the stepping value */ + *resampleOffset += resampleStep; + cur_scalar += resampleStep; + + /* Only increment the sample offset by integer values. + * Sometimes this will be 0 until cur accumulates + * enough steps, especially for "slow" rates. + */ + dCache += (cur_scalar >> FIXED_PRECISION) * 2; + + /* Now that any integer has been added, drop it. + * The offset pointer will preserve the total. + */ + cur_scalar &= FIXED_FRACTION_MASK; + } +} +#endif /* HAVE_NEON_INTRINSICS */ + +/* SECTION 3: Amplifiers */ + +#if NEED_SCALAR_CONVERTER_FALLBACKS +void FAudio_INTERNAL_Amplify_Scalar( + float* output, + uint32_t totalSamples, + float volume +) { + uint32_t i; + for (i = 0; i < totalSamples; i += 1) + { + output[i] *= volume; + } +} +#endif /* NEED_SCALAR_CONVERTER_FALLBACKS */ + +/* The SSE2 version of the amplifier comes from @8thMage! */ + +#if HAVE_SSE2_INTRINSICS +void FAudio_INTERNAL_Amplify_SSE2( + float* output, + uint32_t totalSamples, + float volume +) { + uint32_t i; + uint32_t header = (16 - (((size_t) output) % 16)) / 4; + uint32_t tail = (totalSamples - header) % 4; + __m128 volumeVec, outVec; + if (header == 4) + { + header = 0; + } + if (tail == 4) + { + tail = 0; + } + + for (i = 0; i < header; i += 1) + { + output[i] *= volume; + } + + volumeVec = _mm_set1_ps(volume); + for (i = header; i < totalSamples - tail; i += 4) + { + outVec = _mm_load_ps(output + i); + outVec = _mm_mul_ps(outVec, volumeVec); + _mm_store_ps(output + i, outVec); + } + + for (i = totalSamples - tail; i < totalSamples; i += 1) + { + output[i] *= volume; + } +} +#endif /* HAVE_SSE2_INTRINSICS */ + +#if HAVE_NEON_INTRINSICS +void FAudio_INTERNAL_Amplify_NEON( + float* output, + uint32_t totalSamples, + float volume +) { + uint32_t i; + uint32_t header = (16 - (((size_t) output) % 16)) / 4; + uint32_t tail = (totalSamples - header) % 4; + float32x4_t volumeVec, outVec; + if (header == 4) + { + header = 0; + } + if (tail == 4) + { + tail = 0; + } + + for (i = 0; i < header; i += 1) + { + output[i] *= volume; + } + + volumeVec = vdupq_n_f32(volume); + for (i = header; i < totalSamples - tail; i += 4) + { + outVec = vld1q_f32(output + i); + outVec = vmulq_f32(outVec, volumeVec); + vst1q_f32(output + i, outVec); + } + + for (i = totalSamples - tail; i < totalSamples; i += 1) + { + output[i] *= volume; + } +} +#endif /* HAVE_NEON_INTRINSICS */ + +/* SECTION 4: Mixer Functions */ + +void FAudio_INTERNAL_Mix_Generic_Scalar( + uint32_t toMix, + uint32_t srcChans, + uint32_t dstChans, + float *restrict src, + float *restrict dst, + float *restrict coefficients +) { + uint32_t i, co, ci; + for (i = 0; i < toMix; i += 1, src += srcChans, dst += dstChans) + for (co = 0; co < dstChans; co += 1) + { + for (ci = 0; ci < srcChans; ci += 1) + { + dst[co] += ( + src[ci] * + coefficients[co * srcChans + ci] + ); + } + } +} + +#if HAVE_SSE2_INTRINSICS +/* SSE horizontal add by Peter Cordes, CC-BY-SA. + * From https://stackoverflow.com/a/35270026 */ +static inline float FAudio_simd_hadd(__m128 v) +{ + __m128 shuf = _mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 3, 0, 1)); + __m128 sums = _mm_add_ps(v, shuf); + shuf = _mm_movehl_ps(shuf, sums); + sums = _mm_add_ss(sums, shuf); + return _mm_cvtss_f32(sums); +} + +void FAudio_INTERNAL_Mix_Generic_SSE2( + uint32_t toMix, + uint32_t srcChans, + uint32_t dstChans, + float *restrict src, + float *restrict dst, + float *restrict coefficients +) { + uint32_t i, co, ci; + for (i = 0; i < toMix; i += 1, src += srcChans, dst += dstChans) + for (co = 0; co < dstChans; co += 1) + { + for (ci = 0; srcChans - ci >= 4; ci += 4) + { + /* do SIMD */ + const __m128 vols = _mm_loadu_ps(&coefficients[co * srcChans + ci]); + const __m128 dat = _mm_loadu_ps(&src[ci]); + dst[co] += FAudio_simd_hadd(_mm_mul_ps(dat, vols)); + } + + for (; ci < srcChans; ci += 1) + { + /* do scalar */ + dst[co] += ( + src[ci] * + coefficients[co * srcChans + ci] + ); + } + } +} +#endif /* HAVE_SSE2_INTRINSICS */ + +void FAudio_INTERNAL_Mix_1in_1out_Scalar( + uint32_t toMix, + uint32_t UNUSED1, + uint32_t UNUSED2, + float *restrict src, + float *restrict dst, + float *restrict coefficients +) { + uint32_t i; + for (i = 0; i < toMix; i += 1, src += 1, dst += 1) + { + /* Base source data, combined with the coefficients */ + dst[0] += src[0] * coefficients[0]; + } +} + +void FAudio_INTERNAL_Mix_1in_2out_Scalar( + uint32_t toMix, + uint32_t UNUSED1, + uint32_t UNUSED2, + float *restrict src, + float *restrict dst, + float *restrict coefficients +) { + uint32_t i; + for (i = 0; i < toMix; i += 1, src += 1, dst += 2) + { + dst[0] += src[0] * coefficients[0]; + dst[1] += src[0] * coefficients[1]; + } +} + +void FAudio_INTERNAL_Mix_1in_6out_Scalar( + uint32_t toMix, + uint32_t UNUSED1, + uint32_t UNUSED2, + float *restrict src, + float *restrict dst, + float *restrict coefficients +) { + uint32_t i; + for (i = 0; i < toMix; i += 1, src += 1, dst += 6) + { + dst[0] += src[0] * coefficients[0]; + dst[1] += src[0] * coefficients[1]; + dst[2] += src[0] * coefficients[2]; + dst[3] += src[0] * coefficients[3]; + dst[4] += src[0] * coefficients[4]; + dst[5] += src[0] * coefficients[5]; + } +} + +void FAudio_INTERNAL_Mix_1in_8out_Scalar( + uint32_t toMix, + uint32_t UNUSED1, + uint32_t UNUSED2, + float *restrict src, + float *restrict dst, + float *restrict coefficients +) { + uint32_t i; + for (i = 0; i < toMix; i += 1, src += 1, dst += 8) + { + dst[0] += src[0] * coefficients[0]; + dst[1] += src[0] * coefficients[1]; + dst[2] += src[0] * coefficients[2]; + dst[3] += src[0] * coefficients[3]; + dst[4] += src[0] * coefficients[4]; + dst[5] += src[0] * coefficients[5]; + dst[6] += src[0] * coefficients[6]; + dst[7] += src[0] * coefficients[7]; + } +} + +void FAudio_INTERNAL_Mix_2in_1out_Scalar( + uint32_t toMix, + uint32_t UNUSED1, + uint32_t UNUSED2, + float *restrict src, + float *restrict dst, + float *restrict coefficients +) { + uint32_t i; + for (i = 0; i < toMix; i += 1, src += 2, dst += 1) + { + /* Base source data, combined with the coefficients */ + dst[0] += ( + (src[0] * coefficients[0]) + + (src[1] * coefficients[1]) + ); + } +} + +void FAudio_INTERNAL_Mix_2in_2out_Scalar( + uint32_t toMix, + uint32_t UNUSED1, + uint32_t UNUSED2, + float *restrict src, + float *restrict dst, + float *restrict coefficients +) { + uint32_t i; + for (i = 0; i < toMix; i += 1, src += 2, dst += 2) + { + dst[0] += ( + (src[0] * coefficients[0]) + + (src[1] * coefficients[1]) + ); + dst[1] += ( + (src[0] * coefficients[2]) + + (src[1] * coefficients[3]) + ); + } +} + +void FAudio_INTERNAL_Mix_2in_6out_Scalar( + uint32_t toMix, + uint32_t UNUSED1, + uint32_t UNUSED2, + float *restrict src, + float *restrict dst, + float *restrict coefficients +) { + uint32_t i; + for (i = 0; i < toMix; i += 1, src += 2, dst += 6) + { + dst[0] += ( + (src[0] * coefficients[0]) + + (src[1] * coefficients[1]) + ); + dst[1] += ( + (src[0] * coefficients[2]) + + (src[1] * coefficients[3]) + ); + dst[2] += ( + (src[0] * coefficients[4]) + + (src[1] * coefficients[5]) + ); + dst[3] += ( + (src[0] * coefficients[6]) + + (src[1] * coefficients[7]) + ); + dst[4] += ( + (src[0] * coefficients[8]) + + (src[1] * coefficients[9]) + ); + dst[5] += ( + (src[0] * coefficients[10]) + + (src[1] * coefficients[11]) + ); + } +} + +void FAudio_INTERNAL_Mix_2in_8out_Scalar( + uint32_t toMix, + uint32_t UNUSED1, + uint32_t UNUSED2, + float *restrict src, + float *restrict dst, + float *restrict coefficients +) { + uint32_t i; + for (i = 0; i < toMix; i += 1, src += 2, dst += 8) + { + dst[0] += ( + (src[0] * coefficients[0]) + + (src[1] * coefficients[1]) + ); + dst[1] += ( + (src[0] * coefficients[2]) + + (src[1] * coefficients[3]) + ); + dst[2] += ( + (src[0] * coefficients[4]) + + (src[1] * coefficients[5]) + ); + dst[3] += ( + (src[0] * coefficients[6]) + + (src[1] * coefficients[7]) + ); + dst[4] += ( + (src[0] * coefficients[8]) + + (src[1] * coefficients[9]) + ); + dst[5] += ( + (src[0] * coefficients[10]) + + (src[1] * coefficients[11]) + ); + dst[6] += ( + (src[0] * coefficients[12]) + + (src[1] * coefficients[13]) + ); + dst[7] += ( + (src[0] * coefficients[14]) + + (src[1] * coefficients[15]) + ); + } +} + +/* SECTION 5: InitSIMDFunctions. Assigns based on SSE2/NEON support. */ + +void (*FAudio_INTERNAL_Convert_U8_To_F32)( + const uint8_t *restrict src, + float *restrict dst, + uint32_t len +); +void (*FAudio_INTERNAL_Convert_S16_To_F32)( + const int16_t *restrict src, + float *restrict dst, + uint32_t len +); +void (*FAudio_INTERNAL_Convert_S32_To_F32)( + const int32_t *restrict src, + float *restrict dst, + uint32_t len +); + +FAudioResampleCallback FAudio_INTERNAL_ResampleMono; +FAudioResampleCallback FAudio_INTERNAL_ResampleStereo; + +void (*FAudio_INTERNAL_Amplify)( + float *output, + uint32_t totalSamples, + float volume +); + +FAudioMixCallback FAudio_INTERNAL_Mix_Generic; + +void FAudio_INTERNAL_InitSIMDFunctions(uint8_t hasSSE2, uint8_t hasNEON) +{ +#if HAVE_SSE2_INTRINSICS + if (hasSSE2) + { + FAudio_INTERNAL_Convert_U8_To_F32 = FAudio_INTERNAL_Convert_U8_To_F32_SSE2; + FAudio_INTERNAL_Convert_S16_To_F32 = FAudio_INTERNAL_Convert_S16_To_F32_SSE2; + FAudio_INTERNAL_Convert_S32_To_F32 = FAudio_INTERNAL_Convert_S32_To_F32_SSE2; + FAudio_INTERNAL_ResampleMono = FAudio_INTERNAL_ResampleMono_SSE2; + FAudio_INTERNAL_ResampleStereo = FAudio_INTERNAL_ResampleStereo_SSE2; + FAudio_INTERNAL_Amplify = FAudio_INTERNAL_Amplify_SSE2; + FAudio_INTERNAL_Mix_Generic = FAudio_INTERNAL_Mix_Generic_SSE2; + return; + } +#endif +#if HAVE_NEON_INTRINSICS + if (hasNEON) + { + FAudio_INTERNAL_Convert_U8_To_F32 = FAudio_INTERNAL_Convert_U8_To_F32_NEON; + FAudio_INTERNAL_Convert_S16_To_F32 = FAudio_INTERNAL_Convert_S16_To_F32_NEON; + FAudio_INTERNAL_Convert_S32_To_F32 = FAudio_INTERNAL_Convert_S32_To_F32_NEON; + FAudio_INTERNAL_ResampleMono = FAudio_INTERNAL_ResampleMono_NEON; + FAudio_INTERNAL_ResampleStereo = FAudio_INTERNAL_ResampleStereo_NEON; + FAudio_INTERNAL_Amplify = FAudio_INTERNAL_Amplify_NEON; + FAudio_INTERNAL_Mix_Generic = FAudio_INTERNAL_Mix_Generic_Scalar; + return; + } +#endif +#if NEED_SCALAR_CONVERTER_FALLBACKS + FAudio_INTERNAL_Convert_U8_To_F32 = FAudio_INTERNAL_Convert_U8_To_F32_Scalar; + FAudio_INTERNAL_Convert_S16_To_F32 = FAudio_INTERNAL_Convert_S16_To_F32_Scalar; + FAudio_INTERNAL_Convert_S32_To_F32 = FAudio_INTERNAL_Convert_S32_To_F32_Scalar; + FAudio_INTERNAL_ResampleMono = FAudio_INTERNAL_ResampleMono_Scalar; + FAudio_INTERNAL_ResampleStereo = FAudio_INTERNAL_ResampleStereo_Scalar; + FAudio_INTERNAL_Amplify = FAudio_INTERNAL_Amplify_Scalar; + FAudio_INTERNAL_Mix_Generic = FAudio_INTERNAL_Mix_Generic_Scalar; +#else + FAudio_assert(0 && "Need converter functions!"); +#endif +} + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/src/FAudio_operationset.c b/libs/faudio/src/FAudio_operationset.c new file mode 100644 index 00000000000..f17cd9270df --- /dev/null +++ b/libs/faudio/src/FAudio_operationset.c @@ -0,0 +1,777 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2021 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 + * + */ + +/* FAudio_operationset.c originally written by Tyler Glaiel */ + +#include "FAudio_internal.h" + +/* Core OperationSet Types */ + +typedef enum FAudio_OPERATIONSET_Type +{ + FAUDIOOP_ENABLEEFFECT, + FAUDIOOP_DISABLEEFFECT, + FAUDIOOP_SETEFFECTPARAMETERS, + FAUDIOOP_SETFILTERPARAMETERS, + FAUDIOOP_SETOUTPUTFILTERPARAMETERS, + FAUDIOOP_SETVOLUME, + FAUDIOOP_SETCHANNELVOLUMES, + FAUDIOOP_SETOUTPUTMATRIX, + FAUDIOOP_START, + FAUDIOOP_STOP, + FAUDIOOP_EXITLOOP, + FAUDIOOP_SETFREQUENCYRATIO +} FAudio_OPERATIONSET_Type; + +struct FAudio_OPERATIONSET_Operation +{ + FAudio_OPERATIONSET_Type Type; + uint32_t OperationSet; + FAudioVoice *Voice; + + union + { + struct + { + uint32_t EffectIndex; + } EnableEffect; + struct + { + uint32_t EffectIndex; + } DisableEffect; + struct + { + uint32_t EffectIndex; + void *pParameters; + uint32_t ParametersByteSize; + } SetEffectParameters; + struct + { + FAudioFilterParameters Parameters; + } SetFilterParameters; + struct + { + FAudioVoice *pDestinationVoice; + FAudioFilterParameters Parameters; + } SetOutputFilterParameters; + struct + { + float Volume; + } SetVolume; + struct + { + uint32_t Channels; + float *pVolumes; + } SetChannelVolumes; + struct + { + FAudioVoice *pDestinationVoice; + uint32_t SourceChannels; + uint32_t DestinationChannels; + float *pLevelMatrix; + } SetOutputMatrix; + struct + { + uint32_t Flags; + } Start; + struct + { + uint32_t Flags; + } Stop; + /* No special data for ExitLoop + struct + { + } ExitLoop; + */ + struct + { + float Ratio; + } SetFrequencyRatio; + } Data; + + FAudio_OPERATIONSET_Operation *next; +}; + +/* Used by both Commit and Clear routines */ + +static inline void DeleteOperation( + FAudio_OPERATIONSET_Operation *op, + FAudioFreeFunc pFree +) { + if (op->Type == FAUDIOOP_SETEFFECTPARAMETERS) + { + pFree(op->Data.SetEffectParameters.pParameters); + } + else if (op->Type == FAUDIOOP_SETCHANNELVOLUMES) + { + pFree(op->Data.SetChannelVolumes.pVolumes); + } + else if (op->Type == FAUDIOOP_SETOUTPUTMATRIX) + { + pFree(op->Data.SetOutputMatrix.pLevelMatrix); + } + pFree(op); +} + +/* OperationSet Execution */ + +static inline void ExecuteOperation(FAudio_OPERATIONSET_Operation *op) +{ + switch (op->Type) + { + case FAUDIOOP_ENABLEEFFECT: + FAudioVoice_EnableEffect( + op->Voice, + op->Data.EnableEffect.EffectIndex, + FAUDIO_COMMIT_NOW + ); + break; + + case FAUDIOOP_DISABLEEFFECT: + FAudioVoice_DisableEffect( + op->Voice, + op->Data.DisableEffect.EffectIndex, + FAUDIO_COMMIT_NOW + ); + break; + + case FAUDIOOP_SETEFFECTPARAMETERS: + FAudioVoice_SetEffectParameters( + op->Voice, + op->Data.SetEffectParameters.EffectIndex, + op->Data.SetEffectParameters.pParameters, + op->Data.SetEffectParameters.ParametersByteSize, + FAUDIO_COMMIT_NOW + ); + break; + + case FAUDIOOP_SETFILTERPARAMETERS: + FAudioVoice_SetFilterParameters( + op->Voice, + &op->Data.SetFilterParameters.Parameters, + FAUDIO_COMMIT_NOW + ); + break; + + case FAUDIOOP_SETOUTPUTFILTERPARAMETERS: + FAudioVoice_SetOutputFilterParameters( + op->Voice, + op->Data.SetOutputFilterParameters.pDestinationVoice, + &op->Data.SetOutputFilterParameters.Parameters, + FAUDIO_COMMIT_NOW + ); + break; + + case FAUDIOOP_SETVOLUME: + FAudioVoice_SetVolume( + op->Voice, + op->Data.SetVolume.Volume, + FAUDIO_COMMIT_NOW + ); + break; + + case FAUDIOOP_SETCHANNELVOLUMES: + FAudioVoice_SetChannelVolumes( + op->Voice, + op->Data.SetChannelVolumes.Channels, + op->Data.SetChannelVolumes.pVolumes, + FAUDIO_COMMIT_NOW + ); + break; + + case FAUDIOOP_SETOUTPUTMATRIX: + FAudioVoice_SetOutputMatrix( + op->Voice, + op->Data.SetOutputMatrix.pDestinationVoice, + op->Data.SetOutputMatrix.SourceChannels, + op->Data.SetOutputMatrix.DestinationChannels, + op->Data.SetOutputMatrix.pLevelMatrix, + FAUDIO_COMMIT_NOW + ); + break; + + case FAUDIOOP_START: + FAudioSourceVoice_Start( + op->Voice, + op->Data.Start.Flags, + FAUDIO_COMMIT_NOW + ); + break; + + case FAUDIOOP_STOP: + FAudioSourceVoice_Stop( + op->Voice, + op->Data.Stop.Flags, + FAUDIO_COMMIT_NOW + ); + break; + + case FAUDIOOP_EXITLOOP: + FAudioSourceVoice_ExitLoop( + op->Voice, + FAUDIO_COMMIT_NOW + ); + break; + + case FAUDIOOP_SETFREQUENCYRATIO: + FAudioSourceVoice_SetFrequencyRatio( + op->Voice, + op->Data.SetFrequencyRatio.Ratio, + FAUDIO_COMMIT_NOW + ); + break; + + default: + FAudio_assert(0 && "Unrecognized operation type!"); + break; + } +} + +void FAudio_OPERATIONSET_CommitAll(FAudio *audio) +{ + FAudio_OPERATIONSET_Operation *op, *next, **committed_end; + + FAudio_PlatformLockMutex(audio->operationLock); + LOG_MUTEX_LOCK(audio, audio->operationLock) + + if (audio->queuedOperations == NULL) + { + FAudio_PlatformUnlockMutex(audio->operationLock); + LOG_MUTEX_UNLOCK(audio, audio->operationLock) + return; + } + + committed_end = &audio->committedOperations; + while (*committed_end) + { + committed_end = &((*committed_end)->next); + } + + op = audio->queuedOperations; + do + { + next = op->next; + + *committed_end = op; + op->next = NULL; + committed_end = &op->next; + + op = next; + } while (op != NULL); + audio->queuedOperations = NULL; + + FAudio_PlatformUnlockMutex(audio->operationLock); + LOG_MUTEX_UNLOCK(audio, audio->operationLock) +} + +void FAudio_OPERATIONSET_Commit(FAudio *audio, uint32_t OperationSet) +{ + FAudio_OPERATIONSET_Operation *op, *next, *prev, **committed_end; + + FAudio_PlatformLockMutex(audio->operationLock); + LOG_MUTEX_LOCK(audio, audio->operationLock) + + if (audio->queuedOperations == NULL) + { + FAudio_PlatformUnlockMutex(audio->operationLock); + LOG_MUTEX_UNLOCK(audio, audio->operationLock) + return; + } + + committed_end = &audio->committedOperations; + while (*committed_end) + { + committed_end = &((*committed_end)->next); + } + + op = audio->queuedOperations; + prev = NULL; + do + { + next = op->next; + if (op->OperationSet == OperationSet) + { + if (prev == NULL) /* Start of linked list */ + { + audio->queuedOperations = next; + } + else + { + prev->next = next; + } + + *committed_end = op; + op->next = NULL; + committed_end = &op->next; + } + else + { + prev = op; + } + op = next; + } while (op != NULL); + + FAudio_PlatformUnlockMutex(audio->operationLock); + LOG_MUTEX_UNLOCK(audio, audio->operationLock) +} + +void FAudio_OPERATIONSET_Execute(FAudio *audio) +{ + FAudio_OPERATIONSET_Operation *op, *next; + + FAudio_PlatformLockMutex(audio->operationLock); + LOG_MUTEX_LOCK(audio, audio->operationLock) + + op = audio->committedOperations; + while (op != NULL) + { + next = op->next; + ExecuteOperation(op); + DeleteOperation(op, audio->pFree); + op = next; + } + audio->committedOperations = NULL; + + FAudio_PlatformUnlockMutex(audio->operationLock); + LOG_MUTEX_UNLOCK(audio, audio->operationLock) +} + +/* OperationSet Compilation */ + +static inline FAudio_OPERATIONSET_Operation* QueueOperation( + FAudioVoice *voice, + FAudio_OPERATIONSET_Type type, + uint32_t operationSet +) { + FAudio_OPERATIONSET_Operation *latest; + FAudio_OPERATIONSET_Operation *newop = voice->audio->pMalloc( + sizeof(FAudio_OPERATIONSET_Operation) + ); + + newop->Type = type; + newop->Voice = voice; + newop->OperationSet = operationSet; + newop->next = NULL; + + if (voice->audio->queuedOperations == NULL) + { + voice->audio->queuedOperations = newop; + } + else + { + latest = voice->audio->queuedOperations; + while (latest->next != NULL) + { + latest = latest->next; + } + latest->next = newop; + } + + return newop; +} + +void FAudio_OPERATIONSET_QueueEnableEffect( + FAudioVoice *voice, + uint32_t EffectIndex, + uint32_t OperationSet +) { + FAudio_OPERATIONSET_Operation *op; + + FAudio_PlatformLockMutex(voice->audio->operationLock); + LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock) + + op = QueueOperation( + voice, + FAUDIOOP_ENABLEEFFECT, + OperationSet + ); + + op->Data.EnableEffect.EffectIndex = EffectIndex; + + FAudio_PlatformUnlockMutex(voice->audio->operationLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock) +} + +void FAudio_OPERATIONSET_QueueDisableEffect( + FAudioVoice *voice, + uint32_t EffectIndex, + uint32_t OperationSet +) { + FAudio_OPERATIONSET_Operation *op; + + FAudio_PlatformLockMutex(voice->audio->operationLock); + LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock) + + op = QueueOperation( + voice, + FAUDIOOP_DISABLEEFFECT, + OperationSet + ); + + op->Data.DisableEffect.EffectIndex = EffectIndex; + + FAudio_PlatformUnlockMutex(voice->audio->operationLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock) +} + +void FAudio_OPERATIONSET_QueueSetEffectParameters( + FAudioVoice *voice, + uint32_t EffectIndex, + const void *pParameters, + uint32_t ParametersByteSize, + uint32_t OperationSet +) { + FAudio_OPERATIONSET_Operation *op; + + FAudio_PlatformLockMutex(voice->audio->operationLock); + LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock) + + op = QueueOperation( + voice, + FAUDIOOP_SETEFFECTPARAMETERS, + OperationSet + ); + + op->Data.SetEffectParameters.EffectIndex = EffectIndex; + op->Data.SetEffectParameters.pParameters = voice->audio->pMalloc( + ParametersByteSize + ); + FAudio_memcpy( + op->Data.SetEffectParameters.pParameters, + pParameters, + ParametersByteSize + ); + op->Data.SetEffectParameters.ParametersByteSize = ParametersByteSize; + + FAudio_PlatformUnlockMutex(voice->audio->operationLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock) +} + +void FAudio_OPERATIONSET_QueueSetFilterParameters( + FAudioVoice *voice, + const FAudioFilterParameters *pParameters, + uint32_t OperationSet +) { + FAudio_OPERATIONSET_Operation *op; + + FAudio_PlatformLockMutex(voice->audio->operationLock); + LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock) + + op = QueueOperation( + voice, + FAUDIOOP_SETFILTERPARAMETERS, + OperationSet + ); + + FAudio_memcpy( + &op->Data.SetFilterParameters.Parameters, + pParameters, + sizeof(FAudioFilterParameters) + ); + + FAudio_PlatformUnlockMutex(voice->audio->operationLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock) +} + +void FAudio_OPERATIONSET_QueueSetOutputFilterParameters( + FAudioVoice *voice, + FAudioVoice *pDestinationVoice, + const FAudioFilterParameters *pParameters, + uint32_t OperationSet +) { + FAudio_OPERATIONSET_Operation *op; + + FAudio_PlatformLockMutex(voice->audio->operationLock); + LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock) + + op = QueueOperation( + voice, + FAUDIOOP_SETOUTPUTFILTERPARAMETERS, + OperationSet + ); + + op->Data.SetOutputFilterParameters.pDestinationVoice = pDestinationVoice; + FAudio_memcpy( + &op->Data.SetOutputFilterParameters.Parameters, + pParameters, + sizeof(FAudioFilterParameters) + ); + + FAudio_PlatformUnlockMutex(voice->audio->operationLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock) +} + +void FAudio_OPERATIONSET_QueueSetVolume( + FAudioVoice *voice, + float Volume, + uint32_t OperationSet +) { + FAudio_OPERATIONSET_Operation *op; + + FAudio_PlatformLockMutex(voice->audio->operationLock); + LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock) + + op = QueueOperation( + voice, + FAUDIOOP_SETVOLUME, + OperationSet + ); + + op->Data.SetVolume.Volume = Volume; + + FAudio_PlatformUnlockMutex(voice->audio->operationLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock) +} + +void FAudio_OPERATIONSET_QueueSetChannelVolumes( + FAudioVoice *voice, + uint32_t Channels, + const float *pVolumes, + uint32_t OperationSet +) { + FAudio_OPERATIONSET_Operation *op; + + FAudio_PlatformLockMutex(voice->audio->operationLock); + LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock) + + op = QueueOperation( + voice, + FAUDIOOP_SETCHANNELVOLUMES, + OperationSet + ); + + op->Data.SetChannelVolumes.Channels = Channels; + op->Data.SetChannelVolumes.pVolumes = voice->audio->pMalloc( + sizeof(float) * Channels + ); + FAudio_memcpy( + op->Data.SetChannelVolumes.pVolumes, + pVolumes, + sizeof(float) * Channels + ); + + FAudio_PlatformUnlockMutex(voice->audio->operationLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock) +} + +void FAudio_OPERATIONSET_QueueSetOutputMatrix( + FAudioVoice *voice, + FAudioVoice *pDestinationVoice, + uint32_t SourceChannels, + uint32_t DestinationChannels, + const float *pLevelMatrix, + uint32_t OperationSet +) { + FAudio_OPERATIONSET_Operation *op; + + FAudio_PlatformLockMutex(voice->audio->operationLock); + LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock) + + op = QueueOperation( + voice, + FAUDIOOP_SETOUTPUTMATRIX, + OperationSet + ); + + op->Data.SetOutputMatrix.pDestinationVoice = pDestinationVoice; + op->Data.SetOutputMatrix.SourceChannels = SourceChannels; + op->Data.SetOutputMatrix.DestinationChannels = DestinationChannels; + op->Data.SetOutputMatrix.pLevelMatrix = voice->audio->pMalloc( + sizeof(float) * SourceChannels * DestinationChannels + ); + FAudio_memcpy( + op->Data.SetOutputMatrix.pLevelMatrix, + pLevelMatrix, + sizeof(float) * SourceChannels * DestinationChannels + ); + + FAudio_PlatformUnlockMutex(voice->audio->operationLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock) +} + +void FAudio_OPERATIONSET_QueueStart( + FAudioSourceVoice *voice, + uint32_t Flags, + uint32_t OperationSet +) { + FAudio_OPERATIONSET_Operation *op; + + FAudio_PlatformLockMutex(voice->audio->operationLock); + LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock) + + op = QueueOperation( + voice, + FAUDIOOP_START, + OperationSet + ); + + op->Data.Start.Flags = Flags; + + FAudio_PlatformUnlockMutex(voice->audio->operationLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock) +} + +void FAudio_OPERATIONSET_QueueStop( + FAudioSourceVoice *voice, + uint32_t Flags, + uint32_t OperationSet +) { + FAudio_OPERATIONSET_Operation *op; + + FAudio_PlatformLockMutex(voice->audio->operationLock); + LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock) + + op = QueueOperation( + voice, + FAUDIOOP_STOP, + OperationSet + ); + + op->Data.Stop.Flags = Flags; + + FAudio_PlatformUnlockMutex(voice->audio->operationLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock) +} + +void FAudio_OPERATIONSET_QueueExitLoop( + FAudioSourceVoice *voice, + uint32_t OperationSet +) { + FAudio_PlatformLockMutex(voice->audio->operationLock); + LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock) + + QueueOperation( + voice, + FAUDIOOP_EXITLOOP, + OperationSet + ); + + /* No special data for ExitLoop */ + + FAudio_PlatformUnlockMutex(voice->audio->operationLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock) +} + +void FAudio_OPERATIONSET_QueueSetFrequencyRatio( + FAudioSourceVoice *voice, + float Ratio, + uint32_t OperationSet +) { + FAudio_OPERATIONSET_Operation *op; + + FAudio_PlatformLockMutex(voice->audio->operationLock); + LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock) + + op = QueueOperation( + voice, + FAUDIOOP_SETFREQUENCYRATIO, + OperationSet + ); + + op->Data.SetFrequencyRatio.Ratio = Ratio; + + FAudio_PlatformUnlockMutex(voice->audio->operationLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock) +} + +/* Called when releasing the engine */ + +void FAudio_OPERATIONSET_ClearAll(FAudio *audio) +{ + FAudio_OPERATIONSET_Operation *current, *next; + + FAudio_PlatformLockMutex(audio->operationLock); + LOG_MUTEX_LOCK(audio, audio->operationLock) + + current = audio->queuedOperations; + while (current != NULL) + { + next = current->next; + DeleteOperation(current, audio->pFree); + current = next; + } + audio->queuedOperations = NULL; + + FAudio_PlatformUnlockMutex(audio->operationLock); + LOG_MUTEX_UNLOCK(audio, audio->operationLock) +} + +/* Called when releasing a voice */ + +static inline void RemoveFromList( + FAudioVoice *voice, + FAudio_OPERATIONSET_Operation **list +) { + FAudio_OPERATIONSET_Operation *current, *next, *prev; + + current = *list; + prev = NULL; + while (current != NULL) + { + const uint8_t baseVoice = (voice == current->Voice); + const uint8_t dstVoice = ( + current->Type == FAUDIOOP_SETOUTPUTFILTERPARAMETERS && + voice == current->Data.SetOutputFilterParameters.pDestinationVoice + ) || ( + current->Type == FAUDIOOP_SETOUTPUTMATRIX && + voice == current->Data.SetOutputMatrix.pDestinationVoice + ); + + next = current->next; + if (baseVoice || dstVoice) + { + if (prev == NULL) /* Start of linked list */ + { + *list = next; + } + else + { + prev->next = next; + } + + DeleteOperation(current, voice->audio->pFree); + } + else + { + prev = current; + } + current = next; + } +} + +void FAudio_OPERATIONSET_ClearAllForVoice(FAudioVoice *voice) +{ + FAudio_PlatformLockMutex(voice->audio->operationLock); + LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock) + + RemoveFromList(voice, &voice->audio->queuedOperations); + RemoveFromList(voice, &voice->audio->committedOperations); + + FAudio_PlatformUnlockMutex(voice->audio->operationLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock) +} + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ diff --git a/libs/faudio/src/FAudio_platform_win32.c b/libs/faudio/src/FAudio_platform_win32.c new file mode 100644 index 00000000000..f70e260c542 --- /dev/null +++ b/libs/faudio/src/FAudio_platform_win32.c @@ -0,0 +1,1583 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2020 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * Ethan "flibitijibibo" Lee + * + */ + +#ifdef FAUDIO_WIN32_PLATFORM + +#include "FAudio_internal.h" + +#include + +#define COBJMACROS +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +DEFINE_GUID(CLSID_CWMADecMediaObject, 0x2eeb4adf, 0x4578, 0x4d10, 0xbc, 0xa7, 0xbb, 0x95, 0x5f, 0x56, 0x32, 0x0a); +DEFINE_MEDIATYPE_GUID(MFAudioFormat_XMAudio2, FAUDIO_FORMAT_XMAUDIO2); + +static CRITICAL_SECTION faudio_cs = { NULL, -1, 0, 0, 0, 0 }; +static IMMDeviceEnumerator *device_enumerator; +static HRESULT init_hr; + +struct FAudioWin32PlatformData +{ + IAudioClient *client; + HANDLE audioThread; + HANDLE stopEvent; +}; + +struct FAudioAudioClientThreadArgs +{ + WAVEFORMATEXTENSIBLE format; + IAudioClient *client; + HANDLE events[2]; + FAudio *audio; + UINT updateSize; +}; + +void FAudio_Log(char const *msg) +{ + OutputDebugStringA(msg); +} + +static HRESULT FAudio_FillAudioClientBuffer( + struct FAudioAudioClientThreadArgs *args, + IAudioRenderClient *client, + UINT frames, + UINT padding +) { + HRESULT hr = S_OK; + BYTE *buffer; + + while (padding + args->updateSize <= frames) + { + hr = IAudioRenderClient_GetBuffer( + client, + frames - padding, + &buffer + ); + if (FAILED(hr)) return hr; + + FAudio_zero( + buffer, + args->updateSize * args->format.Format.nBlockAlign + ); + + if (args->audio->active) + { + FAudio_INTERNAL_UpdateEngine( + args->audio, + (float*) buffer + ); + } + + hr = IAudioRenderClient_ReleaseBuffer( + client, + args->updateSize, + 0 + ); + if (FAILED(hr)) return hr; + + padding += args->updateSize; + } + + return hr; +} + +static DWORD WINAPI FAudio_AudioClientThread(void *user) +{ + struct FAudioAudioClientThreadArgs *args = user; + IAudioRenderClient *render_client; + HRESULT hr = S_OK; + UINT frames, padding = 0; + + hr = IAudioClient_GetService( + args->client, + &IID_IAudioRenderClient, + (void **)&render_client + ); + FAudio_assert(!FAILED(hr) && "Failed to get IAudioRenderClient service!"); + + hr = IAudioClient_GetBufferSize(args->client, &frames); + FAudio_assert(!FAILED(hr) && "Failed to get IAudioClient buffer size!"); + + hr = FAudio_FillAudioClientBuffer(args, render_client, frames, 0); + FAudio_assert(!FAILED(hr) && "Failed to initialize IAudioClient buffer!"); + + hr = IAudioClient_Start(args->client); + FAudio_assert(!FAILED(hr) && "Failed to start IAudioClient!"); + + while (WaitForMultipleObjects(2, args->events, FALSE, INFINITE) == WAIT_OBJECT_0) + { + hr = IAudioClient_GetCurrentPadding(args->client, &padding); + FAudio_assert(!FAILED(hr) && "Failed to get IAudioClient current padding!"); + + hr = FAudio_FillAudioClientBuffer(args, render_client, frames, padding); + FAudio_assert(!FAILED(hr) && "Failed to fill IAudioClient buffer!"); + } + + hr = IAudioClient_Stop(args->client); + FAudio_assert(!FAILED(hr) && "Failed to stop IAudioClient!"); + + IAudioRenderClient_Release(render_client); + FAudio_free(args); + return 0; +} + +void FAudio_PlatformInit( + FAudio *audio, + uint32_t flags, + uint32_t deviceIndex, + FAudioWaveFormatExtensible *mixFormat, + uint32_t *updateSize, + void** platformDevice +) { + struct FAudioAudioClientThreadArgs *args; + struct FAudioWin32PlatformData *data; + REFERENCE_TIME duration; + WAVEFORMATEX *closest; + IMMDevice *device = NULL; + HRESULT hr; + HANDLE audioEvent = NULL; + BOOL has_sse2 = IsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE); + + FAudio_INTERNAL_InitSIMDFunctions(has_sse2, FALSE); + + FAudio_PlatformAddRef(); + + *platformDevice = NULL; + if (deviceIndex > 0) return; + + args = FAudio_malloc(sizeof(*args)); + FAudio_assert(!!args && "Failed to allocate FAudio thread args!"); + + data = FAudio_malloc(sizeof(*data)); + FAudio_assert(!!data && "Failed to allocate FAudio platform data!"); + FAudio_zero(data, sizeof(*data)); + + args->format.Format.wFormatTag = mixFormat->Format.wFormatTag; + args->format.Format.nChannels = mixFormat->Format.nChannels; + args->format.Format.nSamplesPerSec = mixFormat->Format.nSamplesPerSec; + args->format.Format.nAvgBytesPerSec = mixFormat->Format.nAvgBytesPerSec; + args->format.Format.nBlockAlign = mixFormat->Format.nBlockAlign; + args->format.Format.wBitsPerSample = mixFormat->Format.wBitsPerSample; + args->format.Format.cbSize = mixFormat->Format.cbSize; + + if (args->format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) + { + args->format.Samples.wValidBitsPerSample = mixFormat->Samples.wValidBitsPerSample; + args->format.dwChannelMask = mixFormat->dwChannelMask; + FAudio_memcpy( + &args->format.SubFormat, + &mixFormat->SubFormat, + sizeof(GUID) + ); + } + + audioEvent = CreateEventW(NULL, FALSE, FALSE, NULL); + FAudio_assert(!!audioEvent && "Failed to create FAudio thread buffer event!"); + + 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 = IMMDevice_Activate( + device, + &IID_IAudioClient, + CLSCTX_ALL, + NULL, + (void **)&data->client + ); + FAudio_assert(!FAILED(hr) && "Failed to create audio client!"); + IMMDevice_Release(device); + + if (flags & FAUDIO_1024_QUANTUM) duration = 21330; + else duration = 30000; + + hr = IAudioClient_IsFormatSupported( + data->client, + AUDCLNT_SHAREMODE_SHARED, + &args->format.Format, + &closest + ); + FAudio_assert(!FAILED(hr) && "Failed to find supported audio format!"); + + if (closest) + { + if (closest->wFormatTag != WAVE_FORMAT_EXTENSIBLE) args->format.Format = *closest; + else args->format = *(WAVEFORMATEXTENSIBLE *)closest; + CoTaskMemFree(closest); + } + + hr = IAudioClient_Initialize( + data->client, + AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + duration, + 0, + &args->format.Format, + &GUID_NULL + ); + FAudio_assert(!FAILED(hr) && "Failed to initialize audio client!"); + + hr = IAudioClient_SetEventHandle(data->client, audioEvent); + FAudio_assert(!FAILED(hr) && "Failed to set audio client event!"); + + mixFormat->Format.wFormatTag = args->format.Format.wFormatTag; + mixFormat->Format.nChannels = args->format.Format.nChannels; + mixFormat->Format.nSamplesPerSec = args->format.Format.nSamplesPerSec; + mixFormat->Format.nAvgBytesPerSec = args->format.Format.nAvgBytesPerSec; + mixFormat->Format.nBlockAlign = args->format.Format.nBlockAlign; + mixFormat->Format.wBitsPerSample = args->format.Format.wBitsPerSample; + + if (args->format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) + { + mixFormat->Format.cbSize = sizeof(FAudioWaveFormatExtensible) - sizeof(FAudioWaveFormatEx); + mixFormat->Samples.wValidBitsPerSample = args->format.Samples.wValidBitsPerSample; + mixFormat->dwChannelMask = args->format.dwChannelMask; + FAudio_memcpy( + &mixFormat->SubFormat, + &args->format.SubFormat, + sizeof(GUID) + ); + } + else + { + mixFormat->Format.cbSize = sizeof(FAudioWaveFormatEx); + } + + args->client = data->client; + args->events[0] = audioEvent; + args->events[1] = data->stopEvent; + args->audio = audio; + 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!"); + + *updateSize = args->updateSize; + *platformDevice = data; + return; +} + +void FAudio_PlatformQuit(void* platformDevice) +{ + struct FAudioWin32PlatformData *data = platformDevice; + + SetEvent(data->stopEvent); + WaitForSingleObject(data->audioThread, INFINITE); + if (data->client) IAudioClient_Release(data->client); + FAudio_PlatformRelease(); +} + +void FAudio_PlatformAddRef() +{ + HRESULT hr; + EnterCriticalSection(&faudio_cs); + if (!device_enumerator) + { + init_hr = CoInitialize(NULL); + hr = CoCreateInstance( + &CLSID_MMDeviceEnumerator, + NULL, + CLSCTX_INPROC_SERVER, + &IID_IMMDeviceEnumerator, + (void**)&device_enumerator + ); + FAudio_assert(!FAILED(hr) && "CoCreateInstance failed!"); + } + else IMMDeviceEnumerator_AddRef(device_enumerator); + LeaveCriticalSection(&faudio_cs); +} + +void FAudio_PlatformRelease() +{ + EnterCriticalSection(&faudio_cs); + if (!IMMDeviceEnumerator_Release(device_enumerator)) + { + device_enumerator = NULL; + if (SUCCEEDED(init_hr)) CoUninitialize(); + } + LeaveCriticalSection(&faudio_cs); +} + +uint32_t FAudio_PlatformGetDeviceCount(void) +{ + IMMDevice *device; + uint32_t count; + HRESULT hr; + + FAudio_PlatformAddRef(); + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint( + device_enumerator, + eRender, + eConsole, + &device + ); + FAudio_assert(!FAILED(hr) && "Failed to get default audio endpoint!"); + + IMMDevice_Release(device); + FAudio_PlatformRelease(); + + return 1; +} + +uint32_t FAudio_PlatformGetDeviceDetails( + uint32_t index, + FAudioDeviceDetails *details +) { + WAVEFORMATEXTENSIBLE *ext; + WAVEFORMATEX *format; + IAudioClient *client; + IMMDevice *device; + uint32_t ret = 0; + HRESULT hr; + WCHAR *str; + + 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!"); + + details->Role = FAudioGlobalDefaultDevice; + + 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); + CoTaskMemFree(str); + + hr = IMMDevice_Activate( + device, + &IID_IAudioClient, + CLSCTX_ALL, + NULL, + (void **)&client + ); + FAudio_assert(!FAILED(hr) && "Failed to activate audio client!"); + + hr = IAudioClient_GetMixFormat(client, &format); + FAudio_assert(!FAILED(hr) && "Failed to get audio client mix format!"); + + details->OutputFormat.Format.wFormatTag = format->wFormatTag; + details->OutputFormat.Format.nChannels = format->nChannels; + details->OutputFormat.Format.nSamplesPerSec = format->nSamplesPerSec; + details->OutputFormat.Format.nAvgBytesPerSec = format->nAvgBytesPerSec; + details->OutputFormat.Format.nBlockAlign = format->nBlockAlign; + details->OutputFormat.Format.wBitsPerSample = format->wBitsPerSample; + details->OutputFormat.Format.cbSize = format->cbSize; + + if (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) + { + ext = (WAVEFORMATEXTENSIBLE *)format; + details->OutputFormat.Samples.wValidBitsPerSample = ext->Samples.wValidBitsPerSample; + details->OutputFormat.dwChannelMask = ext->dwChannelMask; + FAudio_memcpy( + &details->OutputFormat.SubFormat, + &ext->SubFormat, + sizeof(GUID) + ); + } + + IAudioClient_Release(client); + + IMMDevice_Release(device); + + FAudio_PlatformRelease(); + + return ret; +} + +FAudioMutex FAudio_PlatformCreateMutex(void) +{ + CRITICAL_SECTION *cs; + + cs = FAudio_malloc(sizeof(CRITICAL_SECTION)); + if (!cs) return NULL; + + InitializeCriticalSection(cs); + + return cs; +} + +void FAudio_PlatformLockMutex(FAudioMutex mutex) +{ + if (mutex) EnterCriticalSection(mutex); +} + +void FAudio_PlatformUnlockMutex(FAudioMutex mutex) +{ + if (mutex) LeaveCriticalSection(mutex); +} + +void FAudio_PlatformDestroyMutex(FAudioMutex mutex) +{ + if (mutex) DeleteCriticalSection(mutex); + FAudio_free(mutex); +} + +struct FAudioThreadArgs +{ + FAudioThreadFunc func; + const char *name; + void* data; +}; + +static DWORD WINAPI FaudioThreadWrapper(void *user) +{ + struct FAudioThreadArgs *args = user; + DWORD ret; + + ret = args->func(args->data); + + FAudio_free(args); + return ret; +} + +FAudioThread FAudio_PlatformCreateThread( + FAudioThreadFunc func, + const char *name, + void* data +) { + struct FAudioThreadArgs *args; + + if (!(args = FAudio_malloc(sizeof(*args)))) return NULL; + args->func = func; + args->name = name; + args->data = data; + + return CreateThread(NULL, 0, &FaudioThreadWrapper, args, 0, NULL); +} + +void FAudio_PlatformWaitThread(FAudioThread thread, int32_t *retval) +{ + WaitForSingleObject(thread, INFINITE); + GetExitCodeThread(thread, (DWORD *)retval); +} + +void FAudio_PlatformThreadPriority(FAudioThreadPriority priority) +{ + /* FIXME */ +} + +uint64_t FAudio_PlatformGetThreadID(void) +{ + return GetCurrentThreadId(); +} + +void FAudio_sleep(uint32_t ms) +{ + Sleep(ms); +} + +uint32_t FAudio_timems() +{ + return GetTickCount(); +} + +/* FAudio I/O */ + +static size_t FAUDIOCALL FAudio_FILE_read( + void *data, + void *dst, + size_t size, + size_t count +) { + if (!data) return 0; + return fread(dst, size, count, data); +} + +static int64_t FAUDIOCALL FAudio_FILE_seek( + void *data, + int64_t offset, + int whence +) { + if (!data) return -1; + fseek(data, offset, whence); + return ftell(data); +} + +static int FAUDIOCALL FAudio_FILE_close(void *data) +{ + if (!data) return 0; + fclose(data); + return 0; +} + +FAudioIOStream* FAudio_fopen(const char *path) +{ + FAudioIOStream *io; + + io = (FAudioIOStream*) FAudio_malloc(sizeof(FAudioIOStream)); + if (!io) return NULL; + + io->data = fopen(path, "rb"); + io->read = FAudio_FILE_read; + io->seek = FAudio_FILE_seek; + io->close = FAudio_FILE_close; + io->lock = FAudio_PlatformCreateMutex(); + + return io; +} + +struct FAudio_mem +{ + char *mem; + int64_t len; + int64_t pos; +}; + +static size_t FAUDIOCALL FAudio_mem_read( + void *data, + void *dst, + size_t size, + size_t count +) { + struct FAudio_mem *io = data; + size_t len = size * count; + + if (!data) return 0; + + while (len && len > (io->len - io->pos)) len -= size; + FAudio_memcpy(dst, io->mem + io->pos, len); + io->pos += len; + + return len; +} + +static int64_t FAUDIOCALL FAudio_mem_seek( + void *data, + int64_t offset, + int whence +) { + struct FAudio_mem *io = data; + if (!data) return -1; + + if (whence == SEEK_SET) + { + if (io->len > offset) io->pos = offset; + else io->pos = io->len; + } + if (whence == SEEK_CUR) + { + if (io->len > io->pos + offset) io->pos += offset; + else io->pos = io->len; + } + if (whence == SEEK_END) + { + if (io->len > offset) io->pos = io->len - offset; + else io->pos = 0; + } + + return io->pos; +} + +static int FAUDIOCALL FAudio_mem_close(void *data) +{ + if (!data) return 0; + FAudio_free(data); +} + +FAudioIOStream* FAudio_memopen(void *mem, int len) +{ + struct FAudio_mem *data; + FAudioIOStream *io; + + io = (FAudioIOStream*) FAudio_malloc(sizeof(FAudioIOStream)); + if (!io) return NULL; + + data = FAudio_malloc(sizeof(struct FAudio_mem)); + if (!data) + { + FAudio_free(io); + return NULL; + } + + data->mem = mem; + data->len = len; + data->pos = 0; + + io->data = data; + io->read = FAudio_mem_read; + io->seek = FAudio_mem_seek; + io->close = FAudio_mem_close; + io->lock = FAudio_PlatformCreateMutex(); + return io; +} + +uint8_t* FAudio_memptr(FAudioIOStream *io, size_t offset) +{ + struct FAudio_mem *memio = io->data; + return memio->mem + offset; +} + +void FAudio_close(FAudioIOStream *io) +{ + io->close(io->data); + FAudio_PlatformDestroyMutex((FAudioMutex) io->lock); + FAudio_free(io); +} + +/* XNA Song implementation over Win32 MF */ + +static FAudioWaveFormatEx activeSongFormat; +IMFSourceReader *activeSong; +static uint8_t *songBuffer; +static SIZE_T songBufferSize; + +static float songVolume = 1.0f; +static FAudio *songAudio = NULL; +static FAudioMasteringVoice *songMaster = NULL; + +static FAudioSourceVoice *songVoice = NULL; +static FAudioVoiceCallback callbacks; + +/* Internal Functions */ + +static void XNA_SongSubmitBuffer(FAudioVoiceCallback *callback, void *pBufferContext) +{ + IMFMediaBuffer *media_buffer; + FAudioBuffer buffer; + IMFSample *sample; + HRESULT hr; + DWORD flags, buffer_size = 0; + BYTE *buffer_ptr; + + LOG_FUNC_ENTER(songAudio); + + FAudio_memset(&buffer, 0, sizeof(buffer)); + + hr = IMFSourceReader_ReadSample( + activeSong, + MF_SOURCE_READER_FIRST_AUDIO_STREAM, + 0, + NULL, + &flags, + NULL, + &sample + ); + FAudio_assert(!FAILED(hr) && "Failed to read audio sample!"); + + if (flags & MF_SOURCE_READERF_ENDOFSTREAM) + { + buffer.Flags = FAUDIO_END_OF_STREAM; + } + else + { + hr = IMFSample_ConvertToContiguousBuffer( + sample, + &media_buffer + ); + FAudio_assert(!FAILED(hr) && "Failed to get sample buffer!"); + + hr = IMFMediaBuffer_Lock( + media_buffer, + &buffer_ptr, + NULL, + &buffer_size + ); + FAudio_assert(!FAILED(hr) && "Failed to lock buffer bytes!"); + + if (songBufferSize < buffer_size) + { + songBufferSize = buffer_size; + songBuffer = FAudio_realloc(songBuffer, songBufferSize); + FAudio_assert(songBuffer != NULL && "Failed to allocate song buffer!"); + } + FAudio_memcpy(songBuffer, buffer_ptr, buffer_size); + + hr = IMFMediaBuffer_Unlock(media_buffer); + FAudio_assert(!FAILED(hr) && "Failed to unlock buffer bytes!"); + + IMFMediaBuffer_Release(media_buffer); + IMFSample_Release(sample); + } + + if (buffer_size > 0) + { + buffer.AudioBytes = buffer_size; + buffer.pAudioData = songBuffer; + buffer.PlayBegin = 0; + buffer.PlayLength = buffer_size / activeSongFormat.nBlockAlign; + buffer.LoopBegin = 0; + buffer.LoopLength = 0; + buffer.LoopCount = 0; + buffer.pContext = NULL; + FAudioSourceVoice_SubmitSourceBuffer( + songVoice, + &buffer, + NULL + ); + } + + LOG_FUNC_EXIT(songAudio); +} + +static void XNA_SongKill() +{ + if (songVoice != NULL) + { + FAudioSourceVoice_Stop(songVoice, 0, 0); + FAudioVoice_DestroyVoice(songVoice); + songVoice = NULL; + } + if (activeSong) + { + IMFSourceReader_Release(activeSong); + activeSong = NULL; + } + FAudio_free(songBuffer); + songBuffer = NULL; + songBufferSize = 0; +} + +/* "Public" API */ + +FAUDIOAPI void XNA_SongInit() +{ + HRESULT hr; + + hr = MFStartup(MF_VERSION, MFSTARTUP_FULL); + FAudio_assert(!FAILED(hr) && "Failed to initialize Media Foundation!"); + + FAudioCreate(&songAudio, 0, FAUDIO_DEFAULT_PROCESSOR); + FAudio_CreateMasteringVoice( + songAudio, + &songMaster, + FAUDIO_DEFAULT_CHANNELS, + FAUDIO_DEFAULT_SAMPLERATE, + 0, + 0, + NULL + ); +} + +FAUDIOAPI void XNA_SongQuit() +{ + XNA_SongKill(); + FAudioVoice_DestroyVoice(songMaster); + FAudio_Release(songAudio); + MFShutdown(); +} + +FAUDIOAPI float XNA_PlaySong(const char *name) +{ + IMFAttributes *attributes = NULL; + IMFMediaType *media_type = NULL; + UINT32 channels, samplerate; + UINT64 duration; + PROPVARIANT var; + HRESULT hr; + WCHAR filename_w[MAX_PATH]; + + LOG_FUNC_ENTER(songAudio); + LOG_INFO(songAudio, "name %s\n", name); + XNA_SongKill(); + + MultiByteToWideChar(CP_UTF8, 0, name, -1, filename_w, MAX_PATH); + + hr = MFCreateAttributes(&attributes, 1); + FAudio_assert(!FAILED(hr) && "Failed to create attributes!"); + hr = MFCreateSourceReaderFromURL( + filename_w, + attributes, + &activeSong + ); + FAudio_assert(!FAILED(hr) && "Failed to create source reader!"); + IMFAttributes_Release(attributes); + + hr = MFCreateMediaType(&media_type); + FAudio_assert(!FAILED(hr) && "Failed to create media type!"); + hr = IMFMediaType_SetGUID( + media_type, + &MF_MT_MAJOR_TYPE, + &MFMediaType_Audio + ); + FAudio_assert(!FAILED(hr) && "Failed to set major type!"); + hr = IMFMediaType_SetGUID( + media_type, + &MF_MT_SUBTYPE, + &MFAudioFormat_Float + ); + FAudio_assert(!FAILED(hr) && "Failed to set sub type!"); + hr = IMFSourceReader_SetCurrentMediaType( + activeSong, + MF_SOURCE_READER_FIRST_AUDIO_STREAM, + NULL, + media_type + ); + FAudio_assert(!FAILED(hr) && "Failed to set source media type!"); + hr = IMFSourceReader_SetStreamSelection( + activeSong, + MF_SOURCE_READER_FIRST_AUDIO_STREAM, + TRUE + ); + FAudio_assert(!FAILED(hr) && "Failed to select source stream!"); + IMFMediaType_Release(media_type); + + hr = IMFSourceReader_GetCurrentMediaType( + activeSong, + MF_SOURCE_READER_FIRST_AUDIO_STREAM, + &media_type + ); + FAudio_assert(!FAILED(hr) && "Failed to get current media type!"); + hr = IMFMediaType_GetUINT32( + media_type, + &MF_MT_AUDIO_NUM_CHANNELS, + &channels + ); + FAudio_assert(!FAILED(hr) && "Failed to get channel count!"); + hr = IMFMediaType_GetUINT32( + media_type, + &MF_MT_AUDIO_SAMPLES_PER_SECOND, + &samplerate + ); + FAudio_assert(!FAILED(hr) && "Failed to get sample rate!"); + IMFMediaType_Release(media_type); + + hr = IMFSourceReader_GetPresentationAttribute( + activeSong, + MF_SOURCE_READER_MEDIASOURCE, + &MF_PD_DURATION, + &var + ); + FAudio_assert(!FAILED(hr) && "Failed to get song duration!"); + hr = PropVariantToInt64(&var, &duration); + FAudio_assert(!FAILED(hr) && "Failed to get song duration!"); + PropVariantClear(&var); + + activeSongFormat.wFormatTag = FAUDIO_FORMAT_IEEE_FLOAT; + activeSongFormat.nChannels = channels; + activeSongFormat.nSamplesPerSec = samplerate; + activeSongFormat.wBitsPerSample = sizeof(float) * 8; + activeSongFormat.nBlockAlign = activeSongFormat.nChannels * activeSongFormat.wBitsPerSample / 8; + activeSongFormat.nAvgBytesPerSec = activeSongFormat.nSamplesPerSec * activeSongFormat.nBlockAlign; + activeSongFormat.cbSize = 0; + + /* Init voice */ + FAudio_zero(&callbacks, sizeof(FAudioVoiceCallback)); + callbacks.OnBufferEnd = XNA_SongSubmitBuffer; + FAudio_CreateSourceVoice( + songAudio, + &songVoice, + &activeSongFormat, + 0, + 1.0f, /* No pitch shifting here! */ + &callbacks, + NULL, + NULL + ); + FAudioVoice_SetVolume(songVoice, songVolume, 0); + XNA_SongSubmitBuffer(NULL, NULL); + + /* Finally. */ + FAudioSourceVoice_Start(songVoice, 0, 0); + LOG_FUNC_EXIT(songAudio); + return duration / 10000000.; +} + +FAUDIOAPI void XNA_PauseSong() +{ + if (songVoice == NULL) + { + return; + } + FAudioSourceVoice_Stop(songVoice, 0, 0); +} + +FAUDIOAPI void XNA_ResumeSong() +{ + if (songVoice == NULL) + { + return; + } + FAudioSourceVoice_Start(songVoice, 0, 0); +} + +FAUDIOAPI void XNA_StopSong() +{ + XNA_SongKill(); +} + +FAUDIOAPI void XNA_SetSongVolume(float volume) +{ + songVolume = volume; + if (songVoice != NULL) + { + FAudioVoice_SetVolume(songVoice, songVolume, 0); + } +} + +FAUDIOAPI uint32_t XNA_GetSongEnded() +{ + FAudioVoiceState state; + if (songVoice == NULL || activeSong == NULL) + { + return 1; + } + FAudioSourceVoice_GetState(songVoice, &state, 0); + return state.BuffersQueued == 0; +} + +FAUDIOAPI void XNA_EnableVisualization(uint32_t enable) +{ + /* TODO: Enable/Disable FAPO effect */ +} + +FAUDIOAPI uint32_t XNA_VisualizationEnabled() +{ + /* TODO: Query FAPO effect enabled */ + return 0; +} + +FAUDIOAPI void XNA_GetSongVisualizationData( + float *frequencies, + float *samples, + uint32_t count +) { + /* TODO: Visualization FAPO that reads in Song samples, FFT analysis */ +} + +/* FAudio WMADEC implementation over Win32 MF */ + +struct FAudioWMADEC +{ + IMFTransform *decoder; + IMFSample *output_sample; + + char *output_buf; + size_t output_pos; + size_t output_size; + size_t input_pos; + size_t input_size; +}; + +static HRESULT FAudio_WMAMF_ProcessInput( + FAudioVoice *voice, + FAudioBuffer *buffer +) { + struct FAudioWMADEC *impl = voice->src.wmadec; + IMFMediaBuffer *media_buffer; + IMFSample *sample; + DWORD copy_size; + BYTE *copy_buf; + HRESULT hr; + + copy_size = min(buffer->AudioBytes - impl->input_pos, impl->input_size); + if (!copy_size) return S_FALSE; + LOG_INFO(voice->audio, "pushing %x bytes at %x", copy_size, impl->input_pos); + + hr = MFCreateSample(&sample); + FAudio_assert(!FAILED(hr) && "Failed to create sample!"); + hr = MFCreateMemoryBuffer(copy_size, &media_buffer); + FAudio_assert(!FAILED(hr) && "Failed to create buffer!"); + hr = IMFMediaBuffer_SetCurrentLength(media_buffer, copy_size); + FAudio_assert(!FAILED(hr) && "Failed to set buffer length!"); + hr = IMFMediaBuffer_Lock( + media_buffer, + ©_buf, + NULL, + ©_size + ); + FAudio_assert(!FAILED(hr) && "Failed to lock buffer bytes!"); + FAudio_memcpy(copy_buf, buffer->pAudioData + impl->input_pos, copy_size); + hr = IMFMediaBuffer_Unlock(media_buffer); + FAudio_assert(!FAILED(hr) && "Failed to unlock buffer bytes!"); + + hr = IMFSample_AddBuffer(sample, media_buffer); + FAudio_assert(!FAILED(hr) && "Failed to buffer to sample!"); + IMFMediaBuffer_Release(media_buffer); + + hr = IMFTransform_ProcessInput(impl->decoder, 0, sample, 0); + IMFSample_Release(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 S_OK; +}; + +static HRESULT FAudio_WMAMF_ProcessOutput( + FAudioVoice *voice, + FAudioBuffer *buffer +) { + struct FAudioWMADEC *impl = voice->src.wmadec; + MFT_OUTPUT_DATA_BUFFER output; + IMFMediaBuffer *media_buffer; + DWORD status, copy_size; + BYTE *copy_buf; + HRESULT hr; + + while (1) + { + 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 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; + + hr = IMFSample_ConvertToContiguousBuffer( + output.pSample, + &media_buffer + ); + FAudio_assert(!FAILED(hr) && "Failed to get sample buffer!"); + hr = IMFMediaBuffer_Lock( + media_buffer, + ©_buf, + NULL, + ©_size + ); + FAudio_assert(!FAILED(hr) && "Failed to lock buffer bytes!"); + if (impl->output_pos + copy_size > impl->output_size) + { + impl->output_size = max( + impl->output_pos + copy_size, + impl->output_size * 3 / 2 + ); + impl->output_buf = voice->audio->pRealloc( + impl->output_buf, + impl->output_size + ); + FAudio_assert(impl->output_buf && "Failed to resize output buffer!"); + } + FAudio_memcpy(impl->output_buf + impl->output_pos, copy_buf, copy_size); + impl->output_pos += copy_size; + LOG_INFO(voice->audio, "pulled %x bytes at %x", copy_size, impl->output_pos); + hr = IMFMediaBuffer_Unlock(media_buffer); + FAudio_assert(!FAILED(hr) && "Failed to unlock buffer bytes!"); + + IMFMediaBuffer_Release(media_buffer); + if (!impl->output_sample) IMFSample_Release(output.pSample); + } + + return S_OK; +}; + +static void FAudio_INTERNAL_DecodeWMAMF( + FAudioVoice *voice, + FAudioBuffer *buffer, + float *decodeCache, + uint32_t samples +) { + const FAudioWaveFormatExtensible *wfx = (FAudioWaveFormatExtensible *)voice->src.format; + struct FAudioWMADEC *impl = voice->src.wmadec; + size_t samples_pos, samples_size, copy_size; + HRESULT hr; + + LOG_FUNC_ENTER(voice->audio) + + if (!impl->output_pos) + { + if (wfx->Format.wFormatTag == FAUDIO_FORMAT_EXTENSIBLE) + { + const FAudioBufferWMA *wma = &voice->src.bufferList->bufferWMA; + const UINT32 *output_sizes = wma->pDecodedPacketCumulativeBytes; + + impl->input_size = wfx->Format.nBlockAlign; + impl->output_size = max( + impl->output_size, + output_sizes[wma->PacketCount - 1] + ); + } + else + { + const FAudioXMA2WaveFormat *xwf = (const FAudioXMA2WaveFormat *)wfx; + + impl->input_size = xwf->dwBytesPerBlock; + impl->output_size = max( + impl->output_size, + (size_t) xwf->dwSamplesEncoded * + voice->src.format->nChannels * + (voice->src.format->wBitsPerSample / 8) + ); + } + + impl->output_buf = voice->audio->pRealloc( + impl->output_buf, + impl->output_size + ); + FAudio_assert(impl->output_buf && "Failed to allocate output buffer!"); + + LOG_INFO(voice->audio, "sending BOS to %p", impl->decoder); + hr = IMFTransform_ProcessMessage( + impl->decoder, + MFT_MESSAGE_NOTIFY_START_OF_STREAM, + 0 + ); + FAudio_assert(!FAILED(hr) && "Failed to notify decoder stream start!"); + FAudio_WMAMF_ProcessInput(voice, buffer); + } + + samples_pos = voice->src.curBufferOffset * voice->src.format->nChannels * sizeof(float); + samples_size = samples * voice->src.format->nChannels * sizeof(float); + + while (impl->output_pos < samples_pos + samples_size) + { + 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); + hr = IMFTransform_ProcessMessage( + impl->decoder, + MFT_MESSAGE_NOTIFY_END_OF_STREAM, + 0 + ); + FAudio_assert(!FAILED(hr) && "Failed to send EOS!"); + 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); + LOG_INFO( + voice->audio, + "decoded %x / %x bytes, copied %x / %x bytes", + impl->output_pos, + impl->output_size, + copy_size, + samples_size + ); + + 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}; + const FAudioWaveFormatExtensible *wfx = (FAudioWaveFormatExtensible *)voice->src.format; + struct FAudioWMADEC *impl; + MFT_OUTPUT_STREAM_INFO info = {0}; + IMFMediaBuffer *media_buffer; + IMFMediaType *media_type; + IMFTransform *decoder; + HRESULT hr; + UINT32 i, value; + GUID guid; + + LOG_FUNC_ENTER(voice->audio) + + if (!(impl = voice->audio->pMalloc(sizeof(*impl)))) return -1; + FAudio_memset(impl, 0, sizeof(*impl)); + + hr = CoCreateInstance( + &CLSID_CWMADecMediaObject, + 0, + CLSCTX_INPROC_SERVER, + &IID_IMFTransform, + (void **)&decoder + ); + if (FAILED(hr)) + { + voice->audio->pFree(impl->output_buf); + return -2; + } + + hr = MFCreateMediaType(&media_type); + FAudio_assert(!FAILED(hr) && "Failed create media type!"); + hr = IMFMediaType_SetGUID( + media_type, + &MF_MT_MAJOR_TYPE, + &MFMediaType_Audio + ); + FAudio_assert(!FAILED(hr) && "Failed set media major type!"); + + switch (type) + { + case FAUDIO_FORMAT_WMAUDIO2: + hr = IMFMediaType_SetBlob( + media_type, + &MF_MT_USER_DATA, + (void *)fake_codec_data, + sizeof(fake_codec_data) + ); + FAudio_assert(!FAILED(hr) && "Failed set codec private data!"); + hr = IMFMediaType_SetGUID( + media_type, + &MF_MT_SUBTYPE, + &MFAudioFormat_WMAudioV8 + ); + FAudio_assert(!FAILED(hr) && "Failed set media sub type!"); + hr = IMFMediaType_SetUINT32( + media_type, + &MF_MT_AUDIO_BLOCK_ALIGNMENT, + wfx->Format.nBlockAlign + ); + FAudio_assert(!FAILED(hr) && "Failed set input block align!"); + break; + case FAUDIO_FORMAT_WMAUDIO3: + hr = IMFMediaType_SetBlob( + media_type, + &MF_MT_USER_DATA, + (void *)&wfx->Samples, + wfx->Format.cbSize + ); + FAudio_assert(!FAILED(hr) && "Failed set codec private data!"); + hr = IMFMediaType_SetGUID( + media_type, + &MF_MT_SUBTYPE, + &MFAudioFormat_WMAudioV9 + ); + FAudio_assert(!FAILED(hr) && "Failed set media sub type!"); + hr = IMFMediaType_SetUINT32( + media_type, + &MF_MT_AUDIO_BLOCK_ALIGNMENT, + wfx->Format.nBlockAlign + ); + FAudio_assert(!FAILED(hr) && "Failed set input block align!"); + break; + case FAUDIO_FORMAT_WMAUDIO_LOSSLESS: + hr = IMFMediaType_SetBlob( + media_type, + &MF_MT_USER_DATA, + (void *)&wfx->Samples, + wfx->Format.cbSize + ); + FAudio_assert(!FAILED(hr) && "Failed set codec private data!"); + hr = IMFMediaType_SetGUID( + media_type, + &MF_MT_SUBTYPE, + &MFAudioFormat_WMAudio_Lossless + ); + FAudio_assert(!FAILED(hr) && "Failed set media sub type!"); + hr = IMFMediaType_SetUINT32( + media_type, + &MF_MT_AUDIO_BLOCK_ALIGNMENT, + wfx->Format.nBlockAlign + ); + FAudio_assert(!FAILED(hr) && "Failed set input block align!"); + break; + case FAUDIO_FORMAT_XMAUDIO2: + { + const FAudioXMA2WaveFormat *xwf = (const FAudioXMA2WaveFormat *)wfx; + hr = IMFMediaType_SetBlob( + media_type, + &MF_MT_USER_DATA, + (void *)&wfx->Samples, + wfx->Format.cbSize + ); + FAudio_assert(!FAILED(hr) && "Failed set codec private data!"); + hr = IMFMediaType_SetGUID( + media_type, + &MF_MT_SUBTYPE, + &MFAudioFormat_XMAudio2 + ); + FAudio_assert(!FAILED(hr) && "Failed set media sub type!"); + hr = IMFMediaType_SetUINT32( + media_type, + &MF_MT_AUDIO_BLOCK_ALIGNMENT, + xwf->dwBytesPerBlock + ); + FAudio_assert(!FAILED(hr) && "Failed set input block align!"); + break; + } + default: + FAudio_assert(0 && "Unsupported type!"); + break; + } + + hr = IMFMediaType_SetUINT32( + media_type, + &MF_MT_AUDIO_BITS_PER_SAMPLE, + wfx->Format.wBitsPerSample + ); + FAudio_assert(!FAILED(hr) && "Failed set input bits per sample!"); + hr = IMFMediaType_SetUINT32( + media_type, + &MF_MT_AUDIO_AVG_BYTES_PER_SECOND, + wfx->Format.nAvgBytesPerSec + ); + FAudio_assert(!FAILED(hr) && "Failed set input bytes per sample!"); + hr = IMFMediaType_SetUINT32( + media_type, + &MF_MT_AUDIO_NUM_CHANNELS, + wfx->Format.nChannels + ); + FAudio_assert(!FAILED(hr) && "Failed set input channel count!"); + hr = IMFMediaType_SetUINT32( + media_type, + &MF_MT_AUDIO_SAMPLES_PER_SECOND, + wfx->Format.nSamplesPerSec + ); + FAudio_assert(!FAILED(hr) && "Failed set input sample rate!"); + + hr = IMFTransform_SetInputType( + decoder, + 0, + media_type, + 0 + ); + FAudio_assert(!FAILED(hr) && "Failed set decoder input type!"); + IMFMediaType_Release(media_type); + + i = 0; + while (SUCCEEDED(hr)) + { + hr = IMFTransform_GetOutputAvailableType( + decoder, + 0, + i++, + &media_type + ); + FAudio_assert(!FAILED(hr) && "Failed get output media type!"); + + hr = IMFMediaType_GetGUID( + media_type, + &MF_MT_MAJOR_TYPE, + &guid + ); + FAudio_assert(!FAILED(hr) && "Failed get media major type!"); + if (!IsEqualGUID(&MFMediaType_Audio, &guid)) goto next; + + hr = IMFMediaType_GetGUID( + media_type, + &MF_MT_SUBTYPE, + &guid + ); + FAudio_assert(!FAILED(hr) && "Failed get media major type!"); + if (!IsEqualGUID(&MFAudioFormat_Float, &guid)) goto next; + + hr = IMFMediaType_GetUINT32( + media_type, + &MF_MT_AUDIO_BITS_PER_SAMPLE, + &value + ); + if (FAILED(hr)) + { + value = 32; + hr = IMFMediaType_SetUINT32( + media_type, + &MF_MT_AUDIO_BITS_PER_SAMPLE, + value + ); + } + FAudio_assert(!FAILED(hr) && "Failed get bits per sample!"); + if (value != 32) goto next; + + hr = IMFMediaType_GetUINT32( + media_type, + &MF_MT_AUDIO_NUM_CHANNELS, + &value + ); + if (FAILED(hr)) + { + value = wfx->Format.nChannels; + hr = IMFMediaType_SetUINT32( + media_type, + &MF_MT_AUDIO_NUM_CHANNELS, + value + ); + } + FAudio_assert(!FAILED(hr) && "Failed get channel count!"); + if (value != wfx->Format.nChannels) goto next; + + hr = IMFMediaType_GetUINT32( + media_type, + &MF_MT_AUDIO_SAMPLES_PER_SECOND, + &value + ); + if (FAILED(hr)) + { + value = wfx->Format.nSamplesPerSec; + hr = IMFMediaType_SetUINT32( + media_type, + &MF_MT_AUDIO_SAMPLES_PER_SECOND, + value + ); + } + FAudio_assert(!FAILED(hr) && "Failed get sample rate!"); + if (value != wfx->Format.nSamplesPerSec) goto next; + + hr = IMFMediaType_GetUINT32( + media_type, + &MF_MT_AUDIO_BLOCK_ALIGNMENT, + &value + ); + if (FAILED(hr)) + { + value = wfx->Format.nChannels * sizeof(float); + hr = IMFMediaType_SetUINT32( + media_type, + &MF_MT_AUDIO_BLOCK_ALIGNMENT, + value + ); + } + FAudio_assert(!FAILED(hr) && "Failed get block align!"); + if (value == wfx->Format.nChannels * sizeof(float)) break; + +next: + IMFMediaType_Release(media_type); + } + FAudio_assert(!FAILED(hr) && "Failed to find output media type!"); + hr = IMFTransform_SetOutputType(decoder, 0, media_type, 0); + FAudio_assert(!FAILED(hr) && "Failed set decoder output type!"); + IMFMediaType_Release(media_type); + + hr = IMFTransform_GetOutputStreamInfo(decoder, 0, &info); + FAudio_assert(!FAILED(hr) && "Failed to get output stream info!"); + + impl->decoder = decoder; + if (!(info.dwFlags & MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES)) + { + hr = MFCreateSample(&impl->output_sample); + FAudio_assert(!FAILED(hr) && "Failed to create sample!"); + hr = MFCreateMemoryBuffer(info.cbSize, &media_buffer); + FAudio_assert(!FAILED(hr) && "Failed to create buffer!"); + hr = IMFSample_AddBuffer(impl->output_sample, media_buffer); + FAudio_assert(!FAILED(hr) && "Failed to buffer to sample!"); + IMFMediaBuffer_Release(media_buffer); + } + + hr = IMFTransform_ProcessMessage( + decoder, + MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, + 0 + ); + FAudio_assert(!FAILED(hr) && "Failed to start decoder stream!"); + + voice->src.wmadec = impl; + voice->src.decode = FAudio_INTERNAL_DecodeWMAMF; + + LOG_FUNC_EXIT(voice->audio); + return 0; +} + +void FAudio_WMADEC_free(FAudioSourceVoice *voice) +{ + struct FAudioWMADEC *impl = voice->src.wmadec; + HRESULT hr; + + LOG_FUNC_ENTER(voice->audio) + FAudio_PlatformLockMutex(voice->audio->sourceLock); + LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock) + + if (impl->input_size) + { + LOG_INFO(voice->audio, "sending EOS to %p", impl->decoder); + hr = IMFTransform_ProcessMessage( + impl->decoder, + MFT_MESSAGE_NOTIFY_END_OF_STREAM, + 0 + ); + FAudio_assert(!FAILED(hr) && "Failed to send EOS!"); + impl->input_size = 0; + } + if (impl->output_pos) + { + LOG_INFO(voice->audio, "sending DRAIN to %p", impl->decoder); + hr = IMFTransform_ProcessMessage( + impl->decoder, + MFT_MESSAGE_COMMAND_DRAIN, + 0 + ); + FAudio_assert(!FAILED(hr) && "Failed to send DRAIN!"); + impl->output_pos = 0; + } + + if (impl->output_sample) IMFSample_Release(impl->output_sample); + IMFTransform_Release(impl->decoder); + voice->audio->pFree(impl->output_buf); + voice->audio->pFree(voice->src.wmadec); + voice->src.wmadec = NULL; + voice->src.decode = NULL; + + FAudio_PlatformUnlockMutex(voice->audio->sourceLock); + LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock) + LOG_FUNC_EXIT(voice->audio) +} + +void FAudio_WMADEC_end_buffer(FAudioSourceVoice *voice) +{ + struct FAudioWMADEC *impl = voice->src.wmadec; + HRESULT hr; + + LOG_FUNC_ENTER(voice->audio) + + if (impl->input_size) + { + LOG_INFO(voice->audio, "sending EOS to %p", impl->decoder); + hr = IMFTransform_ProcessMessage( + impl->decoder, + MFT_MESSAGE_NOTIFY_END_OF_STREAM, + 0 + ); + FAudio_assert(!FAILED(hr) && "Failed to send EOS!"); + impl->input_size = 0; + } + impl->output_pos = 0; + impl->input_pos = 0; + + LOG_FUNC_EXIT(voice->audio) +} + +#else + +extern int this_tu_is_empty; + +#endif /* FAUDIO_WIN32_PLATFORM */ diff --git a/libs/faudio/src/matrix_defaults.inl b/libs/faudio/src/matrix_defaults.inl new file mode 100644 index 00000000000..2262a6a0744 --- /dev/null +++ b/libs/faudio/src/matrix_defaults.inl @@ -0,0 +1,147 @@ +/* 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! + */ +{ + /* 1 x 1 */ + { 1.000000000f }, + /* 1 x 2 */ + { 1.000000000f, 1.000000000f }, + /* 1 x 3 */ + { 1.000000000f, 1.000000000f, 0.000000000f }, + /* 1 x 4 */ + { 1.000000000f, 1.000000000f, 0.000000000f, 0.000000000f }, + /* 1 x 5 */ + { 1.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f }, + /* 1 x 6 */ + { 1.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f }, + /* 1 x 7 */ + { 1.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f }, + /* 1 x 8 */ + { 1.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f } +}, +{ + /* 2 x 1 */ + { 0.500000000f, 0.500000000f }, + /* 2 x 2 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 1.000000000f }, + /* 2 x 3 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f }, + /* 2 x 4 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f }, + /* 2 x 5 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f }, + /* 2 x 6 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f }, + /* 2 x 7 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f }, + /* 2 x 8 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f } +}, +{ + /* 3 x 1 */ + { 0.333333343f, 0.333333343f, 0.333333343f }, + /* 3 x 2 */ + { 0.800000012f, 0.000000000f, 0.200000003f, 0.000000000f, 0.800000012f, 0.200000003f }, + /* 3 x 3 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f }, + /* 3 x 4 */ + { 0.888888896f, 0.000000000f, 0.111111112f, 0.000000000f, 0.888888896f, 0.111111112f, 0.000000000f, 0.000000000f, 0.111111112f, 0.000000000f, 0.000000000f, 0.111111112f }, + /* 3 x 5 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f }, + /* 3 x 6 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f }, + /* 3 x 7 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f }, + /* 3 x 8 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f } +}, +{ + /* 4 x 1 */ + { 0.250000000f, 0.250000000f, 0.250000000f, 0.250000000f }, + /* 4 x 2 */ + { 0.421000004f, 0.000000000f, 0.358999997f, 0.219999999f, 0.000000000f, 0.421000004f, 0.219999999f, 0.358999997f }, + /* 4 x 3 */ + { 0.421000004f, 0.000000000f, 0.358999997f, 0.219999999f, 0.000000000f, 0.421000004f, 0.219999999f, 0.358999997f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f }, + /* 4 x 4 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f }, + /* 4 x 5 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f }, + /* 4 x 6 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f }, + /* 4 x 7 */ + { 0.939999998f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.939999998f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.500000000f, 0.500000000f, 0.000000000f, 0.000000000f, 0.796000004f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.796000004f }, + /* 4 x 8 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f } +}, +{ + /* 5 x 1 */ + { 0.200000003f, 0.200000003f, 0.200000003f, 0.200000003f, 0.200000003f }, + /* 5 x 2 */ + { 0.374222219f, 0.000000000f, 0.111111112f, 0.319111109f, 0.195555553f, 0.000000000f, 0.374222219f, 0.111111112f, 0.195555553f, 0.319111109f }, + /* 5 x 3 */ + { 0.421000004f, 0.000000000f, 0.000000000f, 0.358999997f, 0.219999999f, 0.000000000f, 0.421000004f, 0.000000000f, 0.219999999f, 0.358999997f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f }, + /* 5 x 4 */ + { 0.941176474f, 0.000000000f, 0.058823530f, 0.000000000f, 0.000000000f, 0.000000000f, 0.941176474f, 0.058823530f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.058823530f, 0.941176474f, 0.000000000f, 0.000000000f, 0.000000000f, 0.058823530f, 0.000000000f, 0.941176474f }, + /* 5 x 5 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f }, + /* 5 x 6 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f }, + /* 5 x 7 */ + { 0.939999998f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.939999998f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.500000000f, 0.500000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.796000004f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.796000004f }, + /* 5 x 8 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f } +}, +{ + /* 6 x 1 */ + { 0.166666672f, 0.166666672f, 0.166666672f, 0.166666672f, 0.166666672f, 0.166666672f }, + /* 6 x 2 */ + { 0.294545442f, 0.000000000f, 0.208181813f, 0.090909094f, 0.251818180f, 0.154545456f, 0.000000000f, 0.294545442f, 0.208181813f, 0.090909094f, 0.154545456f, 0.251818180f }, + /* 6 x 3 */ + { 0.324000001f, 0.000000000f, 0.229000002f, 0.000000000f, 0.277000010f, 0.170000002f, 0.000000000f, 0.324000001f, 0.229000002f, 0.000000000f, 0.170000002f, 0.277000010f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f }, + /* 6 x 4 */ + { 0.558095276f, 0.000000000f, 0.394285709f, 0.047619049f, 0.000000000f, 0.000000000f, 0.000000000f, 0.558095276f, 0.394285709f, 0.047619049f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.047619049f, 0.558095276f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.047619049f, 0.000000000f, 0.558095276f }, + /* 6 x 5 */ + { 0.586000025f, 0.000000000f, 0.414000005f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.586000025f, 0.414000005f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.586000025f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.586000025f }, + /* 6 x 6 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f }, + /* 6 x 7 */ + { 0.939999998f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.939999998f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.939999998f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.500000000f, 0.500000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.796000004f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.796000004f }, + /* 6 x 8 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f } +}, +{ + /* 7 x 1 */ + { 0.143142849f, 0.143142849f, 0.143142849f, 0.142857149f, 0.143142849f, 0.143142849f, 0.143142849f }, + /* 7 x 2 */ + { 0.247384623f, 0.000000000f, 0.174461529f, 0.076923080f, 0.174461529f, 0.226153851f, 0.100615382f, 0.000000000f, 0.247384623f, 0.174461529f, 0.076923080f, 0.174461529f, 0.100615382f, 0.226153851f }, + /* 7 x 3 */ + { 0.268000007f, 0.000000000f, 0.188999996f, 0.000000000f, 0.188999996f, 0.245000005f, 0.108999997f, 0.000000000f, 0.268000007f, 0.188999996f, 0.000000000f, 0.188999996f, 0.108999997f, 0.245000005f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f }, + /* 7 x 4 */ + { 0.463679999f, 0.000000000f, 0.327360004f, 0.040000003f, 0.000000000f, 0.168960005f, 0.000000000f, 0.000000000f, 0.463679999f, 0.327360004f, 0.040000003f, 0.000000000f, 0.000000000f, 0.168960005f, 0.000000000f, 0.000000000f, 0.000000000f, 0.040000003f, 0.327360004f, 0.431039989f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.040000003f, 0.327360004f, 0.000000000f, 0.431039989f }, + /* 7 x 5 */ + { 0.483000010f, 0.000000000f, 0.340999991f, 0.000000000f, 0.000000000f, 0.175999999f, 0.000000000f, 0.000000000f, 0.483000010f, 0.340999991f, 0.000000000f, 0.000000000f, 0.000000000f, 0.175999999f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.340999991f, 0.449000001f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.340999991f, 0.000000000f, 0.449000001f }, + /* 7 x 6 */ + { 0.611000001f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.223000005f, 0.000000000f, 0.000000000f, 0.611000001f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.223000005f, 0.000000000f, 0.000000000f, 0.611000001f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.432000011f, 0.568000019f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.432000011f, 0.000000000f, 0.568000019f }, + /* 7 x 7 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f }, + /* 7 x 8 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.707000017f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.707000017f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f } +}, +{ + /* 8 x 1 */ + { 0.125125006f, 0.125125006f, 0.125125006f, 0.125000000f, 0.125125006f, 0.125125006f, 0.125125006f, 0.125125006f }, + /* 8 x 2 */ + { 0.211866662f, 0.000000000f, 0.150266662f, 0.066666670f, 0.181066677f, 0.111066669f, 0.194133341f, 0.085866667f, 0.000000000f, 0.211866662f, 0.150266662f, 0.066666670f, 0.111066669f, 0.181066677f, 0.085866667f, 0.194133341f }, + /* 8 x 3 */ + { 0.226999998f, 0.000000000f, 0.160999998f, 0.000000000f, 0.194000006f, 0.119000003f, 0.208000004f, 0.092000000f, 0.000000000f, 0.226999998f, 0.160999998f, 0.000000000f, 0.119000003f, 0.194000006f, 0.092000000f, 0.208000004f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f }, + /* 8 x 4 */ + { 0.466344833f, 0.000000000f, 0.329241365f, 0.034482758f, 0.000000000f, 0.000000000f, 0.169931039f, 0.000000000f, 0.000000000f, 0.466344833f, 0.329241365f, 0.034482758f, 0.000000000f, 0.000000000f, 0.000000000f, 0.169931039f, 0.000000000f, 0.000000000f, 0.000000000f, 0.034482758f, 0.466344833f, 0.000000000f, 0.433517247f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.034482758f, 0.000000000f, 0.466344833f, 0.000000000f, 0.433517247f }, + /* 8 x 5 */ + { 0.483000010f, 0.000000000f, 0.340999991f, 0.000000000f, 0.000000000f, 0.000000000f, 0.175999999f, 0.000000000f, 0.000000000f, 0.483000010f, 0.340999991f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.175999999f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.483000010f, 0.000000000f, 0.449000001f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.483000010f, 0.000000000f, 0.449000001f }, + /* 8 x 6 */ + { 0.518000007f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.188999996f, 0.000000000f, 0.000000000f, 0.518000007f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.188999996f, 0.000000000f, 0.000000000f, 0.518000007f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.518000007f, 0.000000000f, 0.481999993f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.518000007f, 0.000000000f, 0.481999993f }, + /* 8 x 7 */ + { 0.541000009f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.541000009f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.541000009f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.287999988f, 0.287999988f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.458999991f, 0.000000000f, 0.541000009f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.458999991f, 0.000000000f, 0.541000009f }, + /* 8 x 8 */ + { 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f } +} diff --git a/libs/faudio/src/stb.h b/libs/faudio/src/stb.h new file mode 100644 index 00000000000..5f1724a736d --- /dev/null +++ b/libs/faudio/src/stb.h @@ -0,0 +1,406 @@ +/* stb.h - v2.32 - Sean's Tool Box -- public domain -- http://nothings.org/stb.h + no warranty is offered or implied; use this code at your own risk + + This is a single header file with a bunch of useful utilities + for getting stuff done in C/C++. + + Documentation: http://nothings.org/stb/stb_h.html + Unit tests: http://nothings.org/stb/stb.c + + + ============================================================================ + You MUST + + #define STB_DEFINE + + in EXACTLY _one_ C or C++ file that includes this header, BEFORE the + include, like this: + + #define STB_DEFINE + #include "stb.h" + + All other files should just #include "stb.h" without the #define. + ============================================================================ + + +Version History + + 2.32 stb_intcmprev, stb_uidict, fix random numbers on Linux + 2.31 stb_ucharcmp + 2.30 MinGW fix + 2.29 attempt to fix use of swprintf() + 2.28 various new functionality + 2.27 test _WIN32 not WIN32 in STB_THREADS + 2.26 various warning & bugfixes + 2.25 various warning & bugfixes + 2.24 various warning & bugfixes + 2.23 fix 2.22 + 2.22 64-bit fixes from '!='; fix stb_sdict_copy() to have preferred name + 2.21 utf-8 decoder rejects "overlong" encodings; attempted 64-bit improvements + 2.20 fix to hash "copy" function--reported by someone with handle "!=" + 2.19 ??? + 2.18 stb_readdir_subdirs_mask + 2.17 stb_cfg_dir + 2.16 fix stb_bgio_, add stb_bgio_stat(); begin a streaming wrapper + 2.15 upgraded hash table template to allow: + - aggregate keys (explicit comparison func for EMPTY and DEL keys) + - "static" implementations (so they can be culled if unused) + 2.14 stb_mprintf + 2.13 reduce identifiable strings in STB_NO_STB_STRINGS + 2.12 fix STB_ONLY -- lots of uint32s, TRUE/FALSE things had crept in + 2.11 fix bug in stb_dirtree_get() which caused "c://path" sorts of stuff + 2.10 STB_F(), STB_I() inline constants (also KI,KU,KF,KD) + 2.09 stb_box_face_vertex_axis_side + 2.08 bugfix stb_trimwhite() + 2.07 colored printing in windows (why are we in 1985?) + 2.06 comparison functions are now functions-that-return-functions and + accept a struct-offset as a parameter (not thread-safe) + 2.05 compile and pass tests under Linux (but no threads); thread cleanup + 2.04 stb_cubic_bezier_1d, smoothstep, avoid dependency on registry + 2.03 ? + 2.02 remove integrated documentation + 2.01 integrate various fixes; stb_force_uniprocessor + 2.00 revised stb_dupe to use multiple hashes + 1.99 stb_charcmp + 1.98 stb_arr_deleten, stb_arr_insertn + 1.97 fix stb_newell_normal() + 1.96 stb_hash_number() + 1.95 hack stb__rec_max; clean up recursion code to use new functions + 1.94 stb_dirtree; rename stb_extra to stb_ptrmap + 1.93 stb_sem_new() API cleanup (no blockflag-starts blocked; use 'extra') + 1.92 stb_threadqueue--multi reader/writer queue, fixed size or resizeable + 1.91 stb_bgio_* for reading disk asynchronously + 1.90 stb_mutex uses CRITICAL_REGION; new stb_sync primitive for thread + joining; workqueue supports stb_sync instead of stb_semaphore + 1.89 support ';' in constant-string wildcards; stb_mutex wrapper (can + implement with EnterCriticalRegion eventually) + 1.88 portable threading API (only for win32 so far); worker thread queue + 1.87 fix wildcard handling in stb_readdir_recursive + 1.86 support ';' in wildcards + 1.85 make stb_regex work with non-constant strings; + beginnings of stb_introspect() + 1.84 (forgot to make notes) + 1.83 whoops, stb_keep_if_different wasn't deleting the temp file + 1.82 bring back stb_compress from stb_file.h for cmirror + 1.81 various bugfixes, STB_FASTMALLOC_INIT inits FASTMALLOC in release + 1.80 stb_readdir returns utf8; write own utf8-utf16 because lib was wrong + 1.79 stb_write + 1.78 calloc() support for malloc wrapper, STB_FASTMALLOC + 1.77 STB_FASTMALLOC + 1.76 STB_STUA - Lua-like language; (stb_image, stb_csample, stb_bilinear) + 1.75 alloc/free array of blocks; stb_hheap bug; a few stb_ps_ funcs; + hash*getkey, hash*copy; stb_bitset; stb_strnicmp; bugfix stb_bst + 1.74 stb_replaceinplace; use stdlib C function to convert utf8 to UTF-16 + 1.73 fix performance bug & leak in stb_ischar (C++ port lost a 'static') + 1.72 remove stb_block, stb_block_manager, stb_decompress (to stb_file.h) + 1.71 stb_trimwhite, stb_tokens_nested, etc. + 1.70 back out 1.69 because it might problemize mixed builds; stb_filec() + 1.69 (stb_file returns 'char *' in C++) + 1.68 add a special 'tree root' data type for stb_bst; stb_arr_end + 1.67 full C++ port. (stb_block_manager) + 1.66 stb_newell_normal + 1.65 stb_lex_item_wild -- allow wildcard items which MUST match entirely + 1.64 stb_data + 1.63 stb_log_name + 1.62 stb_define_sort; C++ cleanup + 1.61 stb_hash_fast -- Paul Hsieh's hash function (beats Bob Jenkins'?) + 1.60 stb_delete_directory_recursive + 1.59 stb_readdir_recursive + 1.58 stb_bst variant with parent pointer for O(1) iteration, not O(log N) + 1.57 replace LCG random with Mersenne Twister (found a public domain one) + 1.56 stb_perfect_hash, stb_ischar, stb_regex + 1.55 new stb_bst API allows multiple BSTs per node (e.g. secondary keys) + 1.54 bugfix: stb_define_hash, stb_wildmatch, regexp + 1.53 stb_define_hash; recoded stb_extra, stb_sdict use it + 1.52 stb_rand_define, stb_bst, stb_reverse + 1.51 fix 'stb_arr_setlen(NULL, 0)' + 1.50 stb_wordwrap + 1.49 minor improvements to enable the scripting language + 1.48 better approach for stb_arr using stb_malloc; more invasive, clearer + 1.47 stb_lex (lexes stb.h at 1.5ML/s on 3Ghz P4; 60/70% of optimal/flex) + 1.46 stb_wrapper_*, STB_MALLOC_WRAPPER + 1.45 lightly tested DFA acceleration of regexp searching + 1.44 wildcard matching & searching; regexp matching & searching + 1.43 stb_temp + 1.42 allow stb_arr to use stb_malloc/realloc; note this is global + 1.41 make it compile in C++; (disable stb_arr in C++) + 1.40 stb_dupe tweak; stb_swap; stb_substr + 1.39 stb_dupe; improve stb_file_max to be less stupid + 1.38 stb_sha1_file: generate sha1 for file, even > 4GB + 1.37 stb_file_max; partial support for utf8 filenames in Windows + 1.36 remove STB__NO_PREFIX - poor interaction with IDE, not worth it + streamline stb_arr to make it separately publishable + 1.35 bugfixes for stb_sdict, stb_malloc(0), stristr + 1.34 (streaming interfaces for stb_compress) + 1.33 stb_alloc; bug in stb_getopt; remove stb_overflow + 1.32 (stb_compress returns, smaller&faster; encode window & 64-bit len) + 1.31 stb_prefix_count + 1.30 (STB__NO_PREFIX - remove stb_ prefixes for personal projects) + 1.29 stb_fput_varlen64, etc. + 1.28 stb_sha1 + 1.27 ? + 1.26 stb_extra + 1.25 ? + 1.24 stb_copyfile + 1.23 stb_readdir + 1.22 ? + 1.21 ? + 1.20 ? + 1.19 ? + 1.18 ? + 1.17 ? + 1.16 ? + 1.15 stb_fixpath, stb_splitpath, stb_strchr2 + 1.14 stb_arr + 1.13 ?stb, stb_log, stb_fatal + 1.12 ?stb_hash2 + 1.11 miniML + 1.10 stb_crc32, stb_adler32 + 1.09 stb_sdict + 1.08 stb_bitreverse, stb_ispow2, stb_big32 + stb_fopen, stb_fput_varlen, stb_fput_ranged + stb_fcmp, stb_feq + 1.07 (stb_encompress) + 1.06 stb_compress + 1.05 stb_tokens, (stb_hheap) + 1.04 stb_rand + 1.03 ?(s-strings) + 1.02 ?stb_filelen, stb_tokens + 1.01 stb_tolower + 1.00 stb_hash, stb_intcmp + stb_file, stb_stringfile, stb_fgets + stb_prefix, stb_strlower, stb_strtok + stb_image + (stb_array), (stb_arena) + +Parenthesized items have since been removed. + +LICENSE + + See end of file for license information. + +CREDITS + + Written by Sean Barrett. + + Fixes: + Philipp Wiesemann + Robert Nix + r-lyeh + blackpawn + github:Mojofreem + Ryan Whitworth + Vincent Isambart + Mike Sartain + Eugene Opalev + Tim Sjostrand + github:infatum + Dave Butler (Croepha) +*/ + +#ifndef STB__INCLUDE_STB_H +#define STB__INCLUDE_STB_H + +#define STB_VERSION 1 + +/* In addition to trimming out all the stuff FAudio does not use, we are also + * binding various stdlib functions stb.h uses to FAudio's stdlib. + * -flibit + */ +#ifndef FAUDIO_WIN32_PLATFORM +#ifdef memcpy /* Thanks Apple! */ +#undef memcpy +#endif +#define memcpy FAudio_memcpy +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Miscellany +// + +STB_EXTERN void stb_swap(void *p, void *q, size_t sz); + +#ifdef STB_DEFINE +typedef struct { char d[4]; } stb__4; +typedef struct { char d[8]; } stb__8; + +// optimize the small cases, though you shouldn't be calling this for those! +void stb_swap(void *p, void *q, size_t sz) +{ + char buffer[256]; + if (p == q) return; + if (sz == 4) { + stb__4 temp = * ( stb__4 *) p; + * (stb__4 *) p = * ( stb__4 *) q; + * (stb__4 *) q = temp; + return; + } else if (sz == 8) { + stb__8 temp = * ( stb__8 *) p; + * (stb__8 *) p = * ( stb__8 *) q; + * (stb__8 *) q = temp; + return; + } + + while (sz > sizeof(buffer)) { + stb_swap(p, q, sizeof(buffer)); + p = (char *) p + sizeof(buffer); + q = (char *) q + sizeof(buffer); + sz -= sizeof(buffer); + } + + memcpy(buffer, p , sz); + memcpy(p , q , sz); + memcpy(q , buffer, sz); +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Random Numbers via Meresenne Twister or LCG +// + +STB_EXTERN unsigned int stb_srandLCG(unsigned int seed); +STB_EXTERN unsigned int stb_randLCG(void); + +STB_EXTERN void stb_srand(unsigned int seed); +STB_EXTERN unsigned int stb_rand(void); +STB_EXTERN double stb_frand(void); + +#define stb_rand_define(x,y) \ + \ + unsigned int x(void) \ + { \ + static unsigned int stb__rand = y; \ + stb__rand = stb__rand * 2147001325 + 715136305; /* BCPL */ \ + return 0x31415926 ^ ((stb__rand >> 16) + (stb__rand << 16)); \ + } + +#ifdef STB_DEFINE +static unsigned int stb__rand_seed=0; + +unsigned int stb_srandLCG(unsigned int seed) +{ + unsigned int previous = stb__rand_seed; + stb__rand_seed = seed; + return previous; +} + +unsigned int stb_randLCG(void) +{ + stb__rand_seed = stb__rand_seed * 2147001325 + 715136305; // BCPL generator + // shuffle non-random bits to the middle, and xor to decorrelate with seed + return 0x31415926 ^ ((stb__rand_seed >> 16) + (stb__rand_seed << 16)); +} + +// public domain Mersenne Twister by Michael Brundage +#define STB__MT_LEN 624 + +int stb__mt_index = STB__MT_LEN*sizeof(int)+1; +unsigned int stb__mt_buffer[STB__MT_LEN]; + +void stb_srand(unsigned int seed) +{ + int i; + unsigned int old = stb_srandLCG(seed); + for (i = 0; i < STB__MT_LEN; i++) + stb__mt_buffer[i] = stb_randLCG(); + stb_srandLCG(old); + stb__mt_index = STB__MT_LEN*sizeof(unsigned int); +} + +#define STB__MT_IA 397 +#define STB__MT_IB (STB__MT_LEN - STB__MT_IA) +#define STB__UPPER_MASK 0x80000000 +#define STB__LOWER_MASK 0x7FFFFFFF +#define STB__MATRIX_A 0x9908B0DF +#define STB__TWIST(b,i,j) ((b)[i] & STB__UPPER_MASK) | ((b)[j] & STB__LOWER_MASK) +#define STB__MAGIC(s) (((s)&1)*STB__MATRIX_A) + +unsigned int stb_rand() +{ + unsigned int * b = stb__mt_buffer; + int idx = stb__mt_index; + unsigned int s,r; + int i; + + if (idx >= STB__MT_LEN*sizeof(unsigned int)) { + if (idx > STB__MT_LEN*sizeof(unsigned int)) + stb_srand(0); + idx = 0; + i = 0; + for (; i < STB__MT_IB; i++) { + s = STB__TWIST(b, i, i+1); + b[i] = b[i + STB__MT_IA] ^ (s >> 1) ^ STB__MAGIC(s); + } + for (; i < STB__MT_LEN-1; i++) { + s = STB__TWIST(b, i, i+1); + b[i] = b[i - STB__MT_IB] ^ (s >> 1) ^ STB__MAGIC(s); + } + + s = STB__TWIST(b, STB__MT_LEN-1, 0); + b[STB__MT_LEN-1] = b[STB__MT_IA-1] ^ (s >> 1) ^ STB__MAGIC(s); + } + stb__mt_index = idx + sizeof(unsigned int); + + r = *(unsigned int *)((unsigned char *)b + idx); + + r ^= (r >> 11); + r ^= (r << 7) & 0x9D2C5680; + r ^= (r << 15) & 0xEFC60000; + r ^= (r >> 18); + + return r; +} + +double stb_frand(void) +{ + return stb_rand() / ((double) (1 << 16) * (1 << 16)); +} + +#endif + +#undef STB_EXTERN +#endif // STB_INCLUDE_STB_H + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/libs/faudio/src/stb_vorbis.h b/libs/faudio/src/stb_vorbis.h new file mode 100644 index 00000000000..3a6e009ba5a --- /dev/null +++ b/libs/faudio/src/stb_vorbis.h @@ -0,0 +1,5572 @@ +// Ogg Vorbis audio decoder - v1.20 - public domain +// http://nothings.org/stb_vorbis/ +// +// Original version written by Sean Barrett in 2007. +// +// Originally sponsored by RAD Game Tools. Seeking implementation +// sponsored by Phillip Bennefall, Marc Andersen, Aaron Baker, +// Elias Software, Aras Pranckevicius, and Sean Barrett. +// +// LICENSE +// +// See end of file for license information. +// +// Limitations: +// +// - floor 0 not supported (used in old ogg vorbis files pre-2004) +// - lossless sample-truncation at beginning ignored +// - cannot concatenate multiple vorbis streams +// - sample positions are 32-bit, limiting seekable 192Khz +// files to around 6 hours (Ogg supports 64-bit) +// +// Feature contributors: +// Dougall Johnson (sample-exact seeking) +// +// Bugfix/warning contributors: +// Terje Mathisen Niklas Frykholm Andy Hill +// Casey Muratori John Bolton Gargaj +// Laurent Gomila Marc LeBlanc Ronny Chevalier +// Bernhard Wodo Evan Balster github:alxprd +// Tom Beaumont Ingo Leitgeb Nicolas Guillemot +// Phillip Bennefall Rohit Thiago Goulart +// github:manxorist saga musix github:infatum +// Timur Gagiev Maxwell Koo Peter Waller +// github:audinowho Dougall Johnson David Reid +// github:Clownacy Pedro J. Estebanez Remi Verschelde +// +// Partial history: +// 1.20 - 2020-07-11 - several small fixes +// 1.19 - 2020-02-05 - warnings +// 1.18 - 2020-02-02 - fix seek bugs; parse header comments; misc warnings etc. +// 1.17 - 2019-07-08 - fix CVE-2019-13217..CVE-2019-13223 (by ForAllSecure) +// 1.16 - 2019-03-04 - fix warnings +// 1.15 - 2019-02-07 - explicit failure if Ogg Skeleton data is found +// 1.14 - 2018-02-11 - delete bogus dealloca usage +// 1.13 - 2018-01-29 - fix truncation of last frame (hopefully) +// 1.12 - 2017-11-21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files +// 1.11 - 2017-07-23 - fix MinGW compilation +// 1.10 - 2017-03-03 - more robust seeking; fix negative ilog(); clear error in open_memory +// 1.09 - 2016-04-04 - back out 'truncation of last frame' fix from previous version +// 1.08 - 2016-04-02 - warnings; setup memory leaks; truncation of last frame +// 1.07 - 2015-01-16 - fixes for crashes on invalid files; warning fixes; const +// 1.06 - 2015-08-31 - full, correct support for seeking API (Dougall Johnson) +// some crash fixes when out of memory or with corrupt files +// fix some inappropriately signed shifts +// 1.05 - 2015-04-19 - don't define __forceinline if it's redundant +// 1.04 - 2014-08-27 - fix missing const-correct case in API +// 1.03 - 2014-08-07 - warning fixes +// 1.02 - 2014-07-09 - declare qsort comparison as explicitly _cdecl in Windows +// 1.01 - 2014-06-18 - fix stb_vorbis_get_samples_float (interleaved was correct) +// 1.0 - 2014-05-26 - fix memory leaks; fix warnings; fix bugs in >2-channel; +// (API change) report sample rate for decode-full-file funcs +// +// See end of file for full version history. + + +////////////////////////////////////////////////////////////////////////////// +// +// HEADER BEGINS HERE +// + +#ifndef STB_VORBIS_INCLUDE_STB_VORBIS_H +#define STB_VORBIS_INCLUDE_STB_VORBIS_H + +#if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO) +#define STB_VORBIS_NO_STDIO 1 +#endif + +#if 0 /* FAudio change! */ +#ifndef STB_VORBIS_NO_STDIO +#include +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/////////// THREAD SAFETY + +// Individual stb_vorbis* handles are not thread-safe; you cannot decode from +// them from multiple threads at the same time. However, you can have multiple +// stb_vorbis* handles and decode from them independently in multiple thrads. + + +/////////// MEMORY ALLOCATION + +// normally stb_vorbis uses malloc() to allocate memory at startup, +// and alloca() to allocate temporary memory during a frame on the +// stack. (Memory consumption will depend on the amount of setup +// data in the file and how you set the compile flags for speed +// vs. size. In my test files the maximal-size usage is ~150KB.) +// +// You can modify the wrapper functions in the source (setup_malloc, +// setup_temp_malloc, temp_malloc) to change this behavior, or you +// can use a simpler allocation model: you pass in a buffer from +// which stb_vorbis will allocate _all_ its memory (including the +// temp memory). "open" may fail with a VORBIS_outofmem if you +// do not pass in enough data; there is no way to determine how +// much you do need except to succeed (at which point you can +// query get_info to find the exact amount required. yes I know +// this is lame). +// +// If you pass in a non-NULL buffer of the type below, allocation +// will occur from it as described above. Otherwise just pass NULL +// to use malloc()/alloca() + +typedef struct +{ + char *alloc_buffer; + int alloc_buffer_length_in_bytes; +} stb_vorbis_alloc; + + +/////////// FUNCTIONS USEABLE WITH ALL INPUT MODES + +typedef struct stb_vorbis stb_vorbis; + +typedef struct +{ + unsigned int sample_rate; + int channels; + + unsigned int setup_memory_required; + unsigned int setup_temp_memory_required; + unsigned int temp_memory_required; + + int max_frame_size; +} stb_vorbis_info; + +typedef struct +{ + char *vendor; + + int comment_list_length; + char **comment_list; +} stb_vorbis_comment; + +// get general information about the file +FAUDIOAPI stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f); + +// get ogg comments +FAUDIOAPI stb_vorbis_comment stb_vorbis_get_comment(stb_vorbis *f); + +// get the last error detected (clears it, too) +FAUDIOAPI int stb_vorbis_get_error(stb_vorbis *f); + +// close an ogg vorbis file and free all memory in use +FAUDIOAPI void stb_vorbis_close(stb_vorbis *f); + +// this function returns the offset (in samples) from the beginning of the +// file that will be returned by the next decode, if it is known, or -1 +// otherwise. after a flush_pushdata() call, this may take a while before +// it becomes valid again. +// NOT WORKING YET after a seek with PULLDATA API +FAUDIOAPI int stb_vorbis_get_sample_offset(stb_vorbis *f); + +// returns the current seek point within the file, or offset from the beginning +// of the memory buffer. In pushdata mode it returns 0. +FAUDIOAPI unsigned int stb_vorbis_get_file_offset(stb_vorbis *f); + +/////////// PUSHDATA API + +#ifndef STB_VORBIS_NO_PUSHDATA_API + +// this API allows you to get blocks of data from any source and hand +// them to stb_vorbis. you have to buffer them; stb_vorbis will tell +// you how much it used, and you have to give it the rest next time; +// and stb_vorbis may not have enough data to work with and you will +// need to give it the same data again PLUS more. Note that the Vorbis +// specification does not bound the size of an individual frame. + +extern stb_vorbis *stb_vorbis_open_pushdata( + const unsigned char * datablock, int datablock_length_in_bytes, + int *datablock_memory_consumed_in_bytes, + int *error, + const stb_vorbis_alloc *alloc_buffer); +// create a vorbis decoder by passing in the initial data block containing +// the ogg&vorbis headers (you don't need to do parse them, just provide +// the first N bytes of the file--you're told if it's not enough, see below) +// on success, returns an stb_vorbis *, does not set error, returns the amount of +// data parsed/consumed on this call in *datablock_memory_consumed_in_bytes; +// on failure, returns NULL on error and sets *error, does not change *datablock_memory_consumed +// if returns NULL and *error is VORBIS_need_more_data, then the input block was +// incomplete and you need to pass in a larger block from the start of the file + +extern int stb_vorbis_decode_frame_pushdata( + stb_vorbis *f, + const unsigned char *datablock, int datablock_length_in_bytes, + int *channels, // place to write number of float * buffers + float ***output, // place to write float ** array of float * buffers + int *samples // place to write number of output samples + ); +// decode a frame of audio sample data if possible from the passed-in data block +// +// return value: number of bytes we used from datablock +// +// possible cases: +// 0 bytes used, 0 samples output (need more data) +// N bytes used, 0 samples output (resynching the stream, keep going) +// N bytes used, M samples output (one frame of data) +// note that after opening a file, you will ALWAYS get one N-bytes,0-sample +// frame, because Vorbis always "discards" the first frame. +// +// Note that on resynch, stb_vorbis will rarely consume all of the buffer, +// instead only datablock_length_in_bytes-3 or less. This is because it wants +// to avoid missing parts of a page header if they cross a datablock boundary, +// without writing state-machiney code to record a partial detection. +// +// The number of channels returned are stored in *channels (which can be +// NULL--it is always the same as the number of channels reported by +// get_info). *output will contain an array of float* buffers, one per +// channel. In other words, (*output)[0][0] contains the first sample from +// the first channel, and (*output)[1][0] contains the first sample from +// the second channel. + +extern void stb_vorbis_flush_pushdata(stb_vorbis *f); +// inform stb_vorbis that your next datablock will not be contiguous with +// previous ones (e.g. you've seeked in the data); future attempts to decode +// frames will cause stb_vorbis to resynchronize (as noted above), and +// once it sees a valid Ogg page (typically 4-8KB, as large as 64KB), it +// will begin decoding the _next_ frame. +// +// if you want to seek using pushdata, you need to seek in your file, then +// call stb_vorbis_flush_pushdata(), then start calling decoding, then once +// decoding is returning you data, call stb_vorbis_get_sample_offset, and +// if you don't like the result, seek your file again and repeat. +#endif + + +////////// PULLING INPUT API + +#ifndef STB_VORBIS_NO_PULLDATA_API +// This API assumes stb_vorbis is allowed to pull data from a source-- +// either a block of memory containing the _entire_ vorbis stream, or a +// FILE * that you or it create, or possibly some other reading mechanism +// if you go modify the source to replace the FILE * case with some kind +// of callback to your code. (But if you don't support seeking, you may +// just want to go ahead and use pushdata.) + +#if !defined(STB_VORBIS_NO_STDIO) && !defined(STB_VORBIS_NO_INTEGER_CONVERSION) +extern int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output); +#endif +#if !defined(STB_VORBIS_NO_INTEGER_CONVERSION) +extern int stb_vorbis_decode_memory(const unsigned char *mem, int len, int *channels, int *sample_rate, short **output); +#endif +// decode an entire file and output the data interleaved into a malloc()ed +// buffer stored in *output. The return value is the number of samples +// decoded, or -1 if the file could not be opened or was not an ogg vorbis file. +// When you're done with it, just free() the pointer returned in *output. + +FAUDIOAPI stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, + int *error, const stb_vorbis_alloc *alloc_buffer); +// create an ogg vorbis decoder from an ogg vorbis stream in memory (note +// this must be the entire stream!). on failure, returns NULL and sets *error + +#ifndef STB_VORBIS_NO_STDIO +FAUDIOAPI stb_vorbis * stb_vorbis_open_filename(const char *filename, + int *error, const stb_vorbis_alloc *alloc_buffer); +// create an ogg vorbis decoder from a filename via fopen(). on failure, +// returns NULL and sets *error (possibly to VORBIS_file_open_failure). + +FAUDIOAPI stb_vorbis * stb_vorbis_open_file(FILE *f, int close_handle_on_close, + int *error, const stb_vorbis_alloc *alloc_buffer); +// create an ogg vorbis decoder from an open FILE *, looking for a stream at +// the _current_ seek point (ftell). on failure, returns NULL and sets *error. +// note that stb_vorbis must "own" this stream; if you seek it in between +// calls to stb_vorbis, it will become confused. Moreover, if you attempt to +// perform stb_vorbis_seek_*() operations on this file, it will assume it +// owns the _entire_ rest of the file after the start point. Use the next +// function, stb_vorbis_open_file_section(), to limit it. + +FAUDIOAPI stb_vorbis * stb_vorbis_open_file_section(FILE *f, int close_handle_on_close, + int *error, const stb_vorbis_alloc *alloc_buffer, unsigned int len); +// create an ogg vorbis decoder from an open FILE *, looking for a stream at +// the _current_ seek point (ftell); the stream will be of length 'len' bytes. +// on failure, returns NULL and sets *error. note that stb_vorbis must "own" +// this stream; if you seek it in between calls to stb_vorbis, it will become +// confused. +#endif + +FAUDIOAPI int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number); +FAUDIOAPI int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number); +// these functions seek in the Vorbis file to (approximately) 'sample_number'. +// after calling seek_frame(), the next call to get_frame_*() will include +// the specified sample. after calling stb_vorbis_seek(), the next call to +// stb_vorbis_get_samples_* will start with the specified sample. If you +// do not need to seek to EXACTLY the target sample when using get_samples_*, +// you can also use seek_frame(). + +FAUDIOAPI int stb_vorbis_seek_start(stb_vorbis *f); +// this function is equivalent to stb_vorbis_seek(f,0) + +FAUDIOAPI unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f); +FAUDIOAPI float stb_vorbis_stream_length_in_seconds(stb_vorbis *f); +// these functions return the total length of the vorbis stream + +FAUDIOAPI int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output); +// decode the next frame and return the number of samples. the number of +// channels returned are stored in *channels (which can be NULL--it is always +// the same as the number of channels reported by get_info). *output will +// contain an array of float* buffers, one per channel. These outputs will +// be overwritten on the next call to stb_vorbis_get_frame_*. +// +// You generally should not intermix calls to stb_vorbis_get_frame_*() +// and stb_vorbis_get_samples_*(), since the latter calls the former. + +#ifndef STB_VORBIS_NO_INTEGER_CONVERSION +extern int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts); +extern int stb_vorbis_get_frame_short (stb_vorbis *f, int num_c, short **buffer, int num_samples); +#endif +// decode the next frame and return the number of *samples* per channel. +// Note that for interleaved data, you pass in the number of shorts (the +// size of your array), but the return value is the number of samples per +// channel, not the total number of samples. +// +// The data is coerced to the number of channels you request according to the +// channel coercion rules (see below). You must pass in the size of your +// buffer(s) so that stb_vorbis will not overwrite the end of the buffer. +// The maximum buffer size needed can be gotten from get_info(); however, +// the Vorbis I specification implies an absolute maximum of 4096 samples +// per channel. + +// Channel coercion rules: +// Let M be the number of channels requested, and N the number of channels present, +// and Cn be the nth channel; let stereo L be the sum of all L and center channels, +// and stereo R be the sum of all R and center channels (channel assignment from the +// vorbis spec). +// M N output +// 1 k sum(Ck) for all k +// 2 * stereo L, stereo R +// k l k > l, the first l channels, then 0s +// k l k <= l, the first k channels +// Note that this is not _good_ surround etc. mixing at all! It's just so +// you get something useful. + +FAUDIOAPI int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats); +FAUDIOAPI int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples); +// gets num_samples samples, not necessarily on a frame boundary--this requires +// buffering so you have to supply the buffers. DOES NOT APPLY THE COERCION RULES. +// Returns the number of samples stored per channel; it may be less than requested +// at the end of the file. If there are no more samples in the file, returns 0. + +#ifndef STB_VORBIS_NO_INTEGER_CONVERSION +extern int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts); +extern int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int num_samples); +#endif +// gets num_samples samples, not necessarily on a frame boundary--this requires +// buffering so you have to supply the buffers. Applies the coercion rules above +// to produce 'channels' channels. Returns the number of samples stored per channel; +// it may be less than requested at the end of the file. If there are no more +// samples in the file, returns 0. + +#endif + +//////// ERROR CODES + +enum STBVorbisError +{ + VORBIS__no_error, + + VORBIS_need_more_data=1, // not a real error + + VORBIS_invalid_api_mixing, // can't mix API modes + VORBIS_outofmem, // not enough memory + VORBIS_feature_not_supported, // uses floor 0 + VORBIS_too_many_channels, // STB_VORBIS_MAX_CHANNELS is too small + VORBIS_file_open_failure, // fopen() failed + VORBIS_seek_without_length, // can't seek in unknown-length file + + VORBIS_unexpected_eof=10, // file is truncated? + VORBIS_seek_invalid, // seek past EOF + + // decoding errors (corrupt/invalid stream) -- you probably + // don't care about the exact details of these + + // vorbis errors: + VORBIS_invalid_setup=20, + VORBIS_invalid_stream, + + // ogg errors: + VORBIS_missing_capture_pattern=30, + VORBIS_invalid_stream_structure_version, + VORBIS_continued_packet_flag_invalid, + VORBIS_incorrect_stream_serial_number, + VORBIS_invalid_first_page, + VORBIS_bad_packet_type, + VORBIS_cant_find_last_page, + VORBIS_seek_failed, + VORBIS_ogg_skeleton_not_supported +}; + + +#ifdef __cplusplus +} +#endif + +#endif // STB_VORBIS_INCLUDE_STB_VORBIS_H +// +// HEADER ENDS HERE +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef STB_VORBIS_HEADER_ONLY + +// global configuration settings (e.g. set these in the project/makefile), +// or just set them in this file at the top (although ideally the first few +// should be visible when the header file is compiled too, although it's not +// crucial) + +// STB_VORBIS_NO_PUSHDATA_API +// does not compile the code for the various stb_vorbis_*_pushdata() +// functions +// #define STB_VORBIS_NO_PUSHDATA_API + +// STB_VORBIS_NO_PULLDATA_API +// does not compile the code for the non-pushdata APIs +// #define STB_VORBIS_NO_PULLDATA_API + +// STB_VORBIS_NO_STDIO +// does not compile the code for the APIs that use FILE *s internally +// or externally (implied by STB_VORBIS_NO_PULLDATA_API) +// #define STB_VORBIS_NO_STDIO + +// STB_VORBIS_NO_INTEGER_CONVERSION +// does not compile the code for converting audio sample data from +// float to integer (implied by STB_VORBIS_NO_PULLDATA_API) +// #define STB_VORBIS_NO_INTEGER_CONVERSION + +// STB_VORBIS_NO_FAST_SCALED_FLOAT +// does not use a fast float-to-int trick to accelerate float-to-int on +// most platforms which requires endianness be defined correctly. +//#define STB_VORBIS_NO_FAST_SCALED_FLOAT + + +// STB_VORBIS_MAX_CHANNELS [number] +// globally define this to the maximum number of channels you need. +// The spec does not put a restriction on channels except that +// the count is stored in a byte, so 255 is the hard limit. +// Reducing this saves about 16 bytes per value, so using 16 saves +// (255-16)*16 or around 4KB. Plus anything other memory usage +// I forgot to account for. Can probably go as low as 8 (7.1 audio), +// 6 (5.1 audio), or 2 (stereo only). +#ifndef STB_VORBIS_MAX_CHANNELS +#define STB_VORBIS_MAX_CHANNELS 16 // enough for anyone? +#endif + +// STB_VORBIS_PUSHDATA_CRC_COUNT [number] +// after a flush_pushdata(), stb_vorbis begins scanning for the +// next valid page, without backtracking. when it finds something +// that looks like a page, it streams through it and verifies its +// CRC32. Should that validation fail, it keeps scanning. But it's +// possible that _while_ streaming through to check the CRC32 of +// one candidate page, it sees another candidate page. This #define +// determines how many "overlapping" candidate pages it can search +// at once. Note that "real" pages are typically ~4KB to ~8KB, whereas +// garbage pages could be as big as 64KB, but probably average ~16KB. +// So don't hose ourselves by scanning an apparent 64KB page and +// missing a ton of real ones in the interim; so minimum of 2 +#ifndef STB_VORBIS_PUSHDATA_CRC_COUNT +#define STB_VORBIS_PUSHDATA_CRC_COUNT 4 +#endif + +// STB_VORBIS_FAST_HUFFMAN_LENGTH [number] +// sets the log size of the huffman-acceleration table. Maximum +// supported value is 24. with larger numbers, more decodings are O(1), +// but the table size is larger so worse cache missing, so you'll have +// to probe (and try multiple ogg vorbis files) to find the sweet spot. +#ifndef STB_VORBIS_FAST_HUFFMAN_LENGTH +#define STB_VORBIS_FAST_HUFFMAN_LENGTH 10 +#endif + +// STB_VORBIS_FAST_BINARY_LENGTH [number] +// sets the log size of the binary-search acceleration table. this +// is used in similar fashion to the fast-huffman size to set initial +// parameters for the binary search + +// STB_VORBIS_FAST_HUFFMAN_INT +// The fast huffman tables are much more efficient if they can be +// stored as 16-bit results instead of 32-bit results. This restricts +// the codebooks to having only 65535 possible outcomes, though. +// (At least, accelerated by the huffman table.) +#ifndef STB_VORBIS_FAST_HUFFMAN_INT +#define STB_VORBIS_FAST_HUFFMAN_SHORT +#endif + +// STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH +// If the 'fast huffman' search doesn't succeed, then stb_vorbis falls +// back on binary searching for the correct one. This requires storing +// extra tables with the huffman codes in sorted order. Defining this +// symbol trades off space for speed by forcing a linear search in the +// non-fast case, except for "sparse" codebooks. +// #define STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH + +// STB_VORBIS_DIVIDES_IN_RESIDUE +// stb_vorbis precomputes the result of the scalar residue decoding +// that would otherwise require a divide per chunk. you can trade off +// space for time by defining this symbol. +// #define STB_VORBIS_DIVIDES_IN_RESIDUE + +// STB_VORBIS_DIVIDES_IN_CODEBOOK +// vorbis VQ codebooks can be encoded two ways: with every case explicitly +// stored, or with all elements being chosen from a small range of values, +// and all values possible in all elements. By default, stb_vorbis expands +// this latter kind out to look like the former kind for ease of decoding, +// because otherwise an integer divide-per-vector-element is required to +// unpack the index. If you define STB_VORBIS_DIVIDES_IN_CODEBOOK, you can +// trade off storage for speed. +//#define STB_VORBIS_DIVIDES_IN_CODEBOOK + +#ifdef STB_VORBIS_CODEBOOK_SHORTS +#error "STB_VORBIS_CODEBOOK_SHORTS is no longer supported as it produced incorrect results for some input formats" +#endif + +// STB_VORBIS_DIVIDE_TABLE +// this replaces small integer divides in the floor decode loop with +// table lookups. made less than 1% difference, so disabled by default. + +// STB_VORBIS_NO_INLINE_DECODE +// disables the inlining of the scalar codebook fast-huffman decode. +// might save a little codespace; useful for debugging +// #define STB_VORBIS_NO_INLINE_DECODE + +// STB_VORBIS_NO_DEFER_FLOOR +// Normally we only decode the floor without synthesizing the actual +// full curve. We can instead synthesize the curve immediately. This +// requires more memory and is very likely slower, so I don't think +// you'd ever want to do it except for debugging. +// #define STB_VORBIS_NO_DEFER_FLOOR + + + + +////////////////////////////////////////////////////////////////////////////// + +#ifdef STB_VORBIS_NO_PULLDATA_API + #define STB_VORBIS_NO_INTEGER_CONVERSION + #define STB_VORBIS_NO_STDIO +#endif + +#if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO) + #define STB_VORBIS_NO_STDIO 1 +#endif + +#ifndef STB_VORBIS_NO_INTEGER_CONVERSION +#ifndef STB_VORBIS_NO_FAST_SCALED_FLOAT + + // only need endianness for fast-float-to-int, which we don't + // use for pushdata + + #ifndef STB_VORBIS_BIG_ENDIAN + #define STB_VORBIS_ENDIAN 0 + #else + #define STB_VORBIS_ENDIAN 1 + #endif + +#endif +#endif + + +#if 0 /* FAudio change! */ +#ifndef STB_VORBIS_NO_STDIO +#include +#endif + +#ifndef STB_VORBIS_NO_CRT + #include + #include + #include + #include + + // find definition of alloca if it's not in stdlib.h: + #if defined(_MSC_VER) || defined(__MINGW32__) + #include + #endif + #if defined(__linux__) || defined(__linux) || defined(__EMSCRIPTEN__) || defined(__NEWLIB__) + #include + #endif +#else // STB_VORBIS_NO_CRT + #define NULL 0 + #define malloc(s) 0 + #define free(s) ((void) 0) + #define realloc(s) 0 +#endif // STB_VORBIS_NO_CRT +#endif + +#include + +#ifdef __MINGW32__ + // eff you mingw: + // "fixed": + // http://sourceforge.net/p/mingw-w64/mailman/message/32882927/ + // "no that broke the build, reverted, who cares about C": + // http://sourceforge.net/p/mingw-w64/mailman/message/32890381/ + #ifdef __forceinline + #undef __forceinline + #endif + #define __forceinline + #ifndef alloca + #define alloca __builtin_alloca + #endif +#elif !defined(_MSC_VER) + #if __GNUC__ + #define __forceinline inline + #else + #define __forceinline + #endif +#endif + +#if STB_VORBIS_MAX_CHANNELS > 256 +#error "Value of STB_VORBIS_MAX_CHANNELS outside of allowed range" +#endif + +#if STB_VORBIS_FAST_HUFFMAN_LENGTH > 24 +#error "Value of STB_VORBIS_FAST_HUFFMAN_LENGTH outside of allowed range" +#endif + + +#if 0 +#include +#define CHECK(f) _CrtIsValidHeapPointer(f->channel_buffers[1]) +#else +#define CHECK(f) ((void) 0) +#endif + +#define MAX_BLOCKSIZE_LOG 13 // from specification +#define MAX_BLOCKSIZE (1 << MAX_BLOCKSIZE_LOG) + + +typedef unsigned char uint8; +typedef signed char int8; +typedef unsigned short uint16; +typedef signed short int16; +typedef unsigned int uint32; +typedef signed int int32; + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +typedef float codetype; + +// @NOTE +// +// Some arrays below are tagged "//varies", which means it's actually +// a variable-sized piece of data, but rather than malloc I assume it's +// small enough it's better to just allocate it all together with the +// main thing +// +// Most of the variables are specified with the smallest size I could pack +// them into. It might give better performance to make them all full-sized +// integers. It should be safe to freely rearrange the structures or change +// the sizes larger--nothing relies on silently truncating etc., nor the +// order of variables. + +#define FAST_HUFFMAN_TABLE_SIZE (1 << STB_VORBIS_FAST_HUFFMAN_LENGTH) +#define FAST_HUFFMAN_TABLE_MASK (FAST_HUFFMAN_TABLE_SIZE - 1) + +typedef struct +{ + int dimensions, entries; + uint8 *codeword_lengths; + float minimum_value; + float delta_value; + uint8 value_bits; + uint8 lookup_type; + uint8 sequence_p; + uint8 sparse; + uint32 lookup_values; + codetype *multiplicands; + uint32 *codewords; + #ifdef STB_VORBIS_FAST_HUFFMAN_SHORT + int16 fast_huffman[FAST_HUFFMAN_TABLE_SIZE]; + #else + int32 fast_huffman[FAST_HUFFMAN_TABLE_SIZE]; + #endif + uint32 *sorted_codewords; + int *sorted_values; + int sorted_entries; +} Codebook; + +typedef struct +{ + uint8 order; + uint16 rate; + uint16 bark_map_size; + uint8 amplitude_bits; + uint8 amplitude_offset; + uint8 number_of_books; + uint8 book_list[16]; // varies +} Floor0; + +typedef struct +{ + uint8 partitions; + uint8 partition_class_list[32]; // varies + uint8 class_dimensions[16]; // varies + uint8 class_subclasses[16]; // varies + uint8 class_masterbooks[16]; // varies + int16 subclass_books[16][8]; // varies + uint16 Xlist[31*8+2]; // varies + uint8 sorted_order[31*8+2]; + uint8 neighbors[31*8+2][2]; + uint8 floor1_multiplier; + uint8 rangebits; + int values; +} Floor1; + +typedef union +{ + Floor0 floor0; + Floor1 floor1; +} Floor; + +typedef struct +{ + uint32 begin, end; + uint32 part_size; + uint8 classifications; + uint8 classbook; + uint8 **classdata; + int16 (*residue_books)[8]; +} Residue; + +typedef struct +{ + uint8 magnitude; + uint8 angle; + uint8 mux; +} MappingChannel; + +typedef struct +{ + uint16 coupling_steps; + MappingChannel *chan; + uint8 submaps; + uint8 submap_floor[15]; // varies + uint8 submap_residue[15]; // varies +} Mapping; + +typedef struct +{ + uint8 blockflag; + uint8 mapping; + uint16 windowtype; + uint16 transformtype; +} Mode; + +typedef struct +{ + uint32 goal_crc; // expected crc if match + int bytes_left; // bytes left in packet + uint32 crc_so_far; // running crc + int bytes_done; // bytes processed in _current_ chunk + uint32 sample_loc; // granule pos encoded in page +} CRCscan; + +typedef struct +{ + uint32 page_start, page_end; + uint32 last_decoded_sample; +} ProbedPage; + +struct stb_vorbis +{ + // user-accessible info + unsigned int sample_rate; + int channels; + + unsigned int setup_memory_required; + unsigned int temp_memory_required; + unsigned int setup_temp_memory_required; + + char *vendor; + int comment_list_length; + char **comment_list; + + // input config +#ifndef STB_VORBIS_NO_STDIO + FILE *f; + uint32 f_start; + int close_on_free; +#endif + + uint8 *stream; + uint8 *stream_start; + uint8 *stream_end; + + uint32 stream_len; + + uint8 push_mode; + + // the page to seek to when seeking to start, may be zero + uint32 first_audio_page_offset; + + // p_first is the page on which the first audio packet ends + // (but not necessarily the page on which it starts) + ProbedPage p_first, p_last; + + // memory management + stb_vorbis_alloc alloc; + int setup_offset; + int temp_offset; + + // run-time results + int eof; + enum STBVorbisError error; + + // user-useful data + + // header info + int blocksize[2]; + int blocksize_0, blocksize_1; + int codebook_count; + Codebook *codebooks; + int floor_count; + uint16 floor_types[64]; // varies + Floor *floor_config; + int residue_count; + uint16 residue_types[64]; // varies + Residue *residue_config; + int mapping_count; + Mapping *mapping; + int mode_count; + Mode mode_config[64]; // varies + + uint32 total_samples; + + // decode buffer + float *channel_buffers[STB_VORBIS_MAX_CHANNELS]; + float *outputs [STB_VORBIS_MAX_CHANNELS]; + + float *previous_window[STB_VORBIS_MAX_CHANNELS]; + int previous_length; + + #ifndef STB_VORBIS_NO_DEFER_FLOOR + int16 *finalY[STB_VORBIS_MAX_CHANNELS]; + #else + float *floor_buffers[STB_VORBIS_MAX_CHANNELS]; + #endif + + uint32 current_loc; // sample location of next frame to decode + int current_loc_valid; + + // per-blocksize precomputed data + + // twiddle factors + float *A[2],*B[2],*C[2]; + float *window[2]; + uint16 *bit_reverse[2]; + + // current page/packet/segment streaming info + uint32 serial; // stream serial number for verification + int last_page; + int segment_count; + uint8 segments[255]; + uint8 page_flag; + uint8 bytes_in_seg; + uint8 first_decode; + int next_seg; + int last_seg; // flag that we're on the last segment + int last_seg_which; // what was the segment number of the last seg? + uint32 acc; + int valid_bits; + int packet_bytes; + int end_seg_with_known_loc; + uint32 known_loc_for_packet; + int discard_samples_deferred; + uint32 samples_output; + + // push mode scanning + int page_crc_tests; // only in push_mode: number of tests active; -1 if not searching +#ifndef STB_VORBIS_NO_PUSHDATA_API + CRCscan scan[STB_VORBIS_PUSHDATA_CRC_COUNT]; +#endif + + // sample-access + int channel_buffer_start; + int channel_buffer_end; +}; + +#if defined(STB_VORBIS_NO_PUSHDATA_API) + #define IS_PUSH_MODE(f) FALSE +#elif defined(STB_VORBIS_NO_PULLDATA_API) + #define IS_PUSH_MODE(f) TRUE +#else + #define IS_PUSH_MODE(f) ((f)->push_mode) +#endif + +typedef struct stb_vorbis vorb; + +static int error(vorb *f, enum STBVorbisError e) +{ + f->error = e; + if (!f->eof && e != VORBIS_need_more_data) { + f->error=e; // breakpoint for debugging + } + return 0; +} + + +// these functions are used for allocating temporary memory +// while decoding. if you can afford the stack space, use +// alloca(); otherwise, provide a temp buffer and it will +// allocate out of those. + +#define array_size_required(count,size) (count*(sizeof(void *)+(size))) + +#define temp_alloc(f,size) (f->alloc.alloc_buffer ? setup_temp_malloc(f,size) : FAudio_alloca(size)) +#define temp_free(f,p) FAudio_dealloca(p) +#define temp_alloc_save(f) ((f)->temp_offset) +#define temp_alloc_restore(f,p) ((f)->temp_offset = (p)) + +#define temp_block_array(f,count,size) make_block_array(temp_alloc(f,array_size_required(count,size)), count, size) + +// given a sufficiently large block of memory, make an array of pointers to subblocks of it +static void *make_block_array(void *mem, int count, int size) +{ + int i; + void ** p = (void **) mem; + char *q = (char *) (p + count); + for (i=0; i < count; ++i) { + p[i] = q; + q += size; + } + return p; +} + +// FIXME: https://github.com/nothings/stb/pull/1005 -flibit +static char zeromalloc = 0; + +static void *setup_malloc(vorb *f, int sz) +{ + sz = (sz+7) & ~7; // round up to nearest 8 for alignment of future allocs. + f->setup_memory_required += sz; + if (f->alloc.alloc_buffer) { + void *p = (char *) f->alloc.alloc_buffer + f->setup_offset; + if (f->setup_offset + sz > f->temp_offset) return NULL; + f->setup_offset += sz; + return p; + } + return sz ? malloc(sz) : &zeromalloc; +} + +static void setup_free(vorb *f, void *p) +{ + if (f->alloc.alloc_buffer) return; // do nothing; setup mem is a stack + if (p != &zeromalloc) + free(p); +} + +static void *setup_temp_malloc(vorb *f, int sz) +{ + sz = (sz+7) & ~7; // round up to nearest 8 for alignment of future allocs. + if (f->alloc.alloc_buffer) { + if (f->temp_offset - sz < f->setup_offset) return NULL; + f->temp_offset -= sz; + return (char *) f->alloc.alloc_buffer + f->temp_offset; + } + return malloc(sz); +} + +static void setup_temp_free(vorb *f, void *p, int sz) +{ + if (f->alloc.alloc_buffer) { + f->temp_offset += (sz+7)&~7; + return; + } + free(p); +} + +#define CRC32_POLY 0x04c11db7 // from spec + +static uint32 crc_table[256]; +static void crc32_init(void) +{ + int i,j; + uint32 s; + for(i=0; i < 256; i++) { + for (s=(uint32) i << 24, j=0; j < 8; ++j) + s = (s << 1) ^ (s >= (1U<<31) ? CRC32_POLY : 0); + crc_table[i] = s; + } +} + +static __forceinline uint32 crc32_update(uint32 crc, uint8 byte) +{ + return (crc << 8) ^ crc_table[byte ^ (crc >> 24)]; +} + + +// used in setup, and for huffman that doesn't go fast path +static unsigned int bit_reverse(unsigned int n) +{ + n = ((n & 0xAAAAAAAA) >> 1) | ((n & 0x55555555) << 1); + n = ((n & 0xCCCCCCCC) >> 2) | ((n & 0x33333333) << 2); + n = ((n & 0xF0F0F0F0) >> 4) | ((n & 0x0F0F0F0F) << 4); + n = ((n & 0xFF00FF00) >> 8) | ((n & 0x00FF00FF) << 8); + return (n >> 16) | (n << 16); +} + +static float square(float x) +{ + return x*x; +} + +// this is a weird definition of log2() for which log2(1) = 1, log2(2) = 2, log2(4) = 3 +// as required by the specification. fast(?) implementation from stb.h +// @OPTIMIZE: called multiple times per-packet with "constants"; move to setup +static int ilog(int32 n) +{ + static signed char log2_4[16] = { 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4 }; + + if (n < 0) return 0; // signed n returns 0 + + // 2 compares if n < 16, 3 compares otherwise (4 if signed or n > 1<<29) + if (n < (1 << 14)) + if (n < (1 << 4)) return 0 + log2_4[n ]; + else if (n < (1 << 9)) return 5 + log2_4[n >> 5]; + else return 10 + log2_4[n >> 10]; + else if (n < (1 << 24)) + if (n < (1 << 19)) return 15 + log2_4[n >> 15]; + else return 20 + log2_4[n >> 20]; + else if (n < (1 << 29)) return 25 + log2_4[n >> 25]; + else return 30 + log2_4[n >> 30]; +} + +#ifndef M_PI + #define M_PI 3.14159265358979323846264f // from CRC +#endif + +// code length assigned to a value with no huffman encoding +#define NO_CODE 255 + +/////////////////////// LEAF SETUP FUNCTIONS ////////////////////////// +// +// these functions are only called at setup, and only a few times +// per file + +static float float32_unpack(uint32 x) +{ + // from the specification + uint32 mantissa = x & 0x1fffff; + uint32 sign = x & 0x80000000; + uint32 exp = (x & 0x7fe00000) >> 21; + double res = sign ? -(double)mantissa : (double)mantissa; + return (float) ldexp((float)res, exp-788); +} + + +// zlib & jpeg huffman tables assume that the output symbols +// can either be arbitrarily arranged, or have monotonically +// increasing frequencies--they rely on the lengths being sorted; +// this makes for a very simple generation algorithm. +// vorbis allows a huffman table with non-sorted lengths. This +// requires a more sophisticated construction, since symbols in +// order do not map to huffman codes "in order". +static void add_entry(Codebook *c, uint32 huff_code, int symbol, int count, int len, uint32 *values) +{ + if (!c->sparse) { + c->codewords [symbol] = huff_code; + } else { + c->codewords [count] = huff_code; + c->codeword_lengths[count] = len; + values [count] = symbol; + } +} + +static int compute_codewords(Codebook *c, uint8 *len, int n, uint32 *values) +{ + int i,k,m=0; + uint32 available[32]; + + memset(available, 0, sizeof(available)); + // find the first entry + for (k=0; k < n; ++k) if (len[k] < NO_CODE) break; + if (k == n) { assert(c->sorted_entries == 0); return TRUE; } + // add to the list + add_entry(c, 0, k, m++, len[k], values); + // add all available leaves + for (i=1; i <= len[k]; ++i) + available[i] = 1U << (32-i); + // note that the above code treats the first case specially, + // but it's really the same as the following code, so they + // could probably be combined (except the initial code is 0, + // and I use 0 in available[] to mean 'empty') + for (i=k+1; i < n; ++i) { + uint32 res; + int z = len[i], y; + if (z == NO_CODE) continue; + // find lowest available leaf (should always be earliest, + // which is what the specification calls for) + // note that this property, and the fact we can never have + // more than one free leaf at a given level, isn't totally + // trivial to prove, but it seems true and the assert never + // fires, so! + while (z > 0 && !available[z]) --z; + if (z == 0) { return FALSE; } + res = available[z]; + assert(z >= 0 && z < 32); + available[z] = 0; + add_entry(c, bit_reverse(res), i, m++, len[i], values); + // propagate availability up the tree + if (z != len[i]) { + assert(len[i] >= 0 && len[i] < 32); + for (y=len[i]; y > z; --y) { + assert(available[y] == 0); + available[y] = res + (1 << (32-y)); + } + } + } + return TRUE; +} + +// accelerated huffman table allows fast O(1) match of all symbols +// of length <= STB_VORBIS_FAST_HUFFMAN_LENGTH +static void compute_accelerated_huffman(Codebook *c) +{ + int i, len; + for (i=0; i < FAST_HUFFMAN_TABLE_SIZE; ++i) + c->fast_huffman[i] = -1; + + len = c->sparse ? c->sorted_entries : c->entries; + #ifdef STB_VORBIS_FAST_HUFFMAN_SHORT + if (len > 32767) len = 32767; // largest possible value we can encode! + #endif + for (i=0; i < len; ++i) { + if (c->codeword_lengths[i] <= STB_VORBIS_FAST_HUFFMAN_LENGTH) { + uint32 z = c->sparse ? bit_reverse(c->sorted_codewords[i]) : c->codewords[i]; + // set table entries for all bit combinations in the higher bits + while (z < FAST_HUFFMAN_TABLE_SIZE) { + c->fast_huffman[z] = i; + z += 1 << c->codeword_lengths[i]; + } + } + } +} + +#ifdef _MSC_VER +#define STBV_CDECL __cdecl +#else +#define STBV_CDECL +#endif + +static int STBV_CDECL uint32_compare(const void *p, const void *q) +{ + uint32 x = * (uint32 *) p; + uint32 y = * (uint32 *) q; + return x < y ? -1 : x > y; +} + +static int include_in_sort(Codebook *c, uint8 len) +{ + if (c->sparse) { assert(len != NO_CODE); return TRUE; } + if (len == NO_CODE) return FALSE; + if (len > STB_VORBIS_FAST_HUFFMAN_LENGTH) return TRUE; + return FALSE; +} + +// if the fast table above doesn't work, we want to binary +// search them... need to reverse the bits +static void compute_sorted_huffman(Codebook *c, uint8 *lengths, uint32 *values) +{ + int i, len; + // build a list of all the entries + // OPTIMIZATION: don't include the short ones, since they'll be caught by FAST_HUFFMAN. + // this is kind of a frivolous optimization--I don't see any performance improvement, + // but it's like 4 extra lines of code, so. + if (!c->sparse) { + int k = 0; + for (i=0; i < c->entries; ++i) + if (include_in_sort(c, lengths[i])) + c->sorted_codewords[k++] = bit_reverse(c->codewords[i]); + assert(k == c->sorted_entries); + } else { + for (i=0; i < c->sorted_entries; ++i) + c->sorted_codewords[i] = bit_reverse(c->codewords[i]); + } + + qsort(c->sorted_codewords, c->sorted_entries, sizeof(c->sorted_codewords[0]), uint32_compare); + c->sorted_codewords[c->sorted_entries] = 0xffffffff; + + len = c->sparse ? c->sorted_entries : c->entries; + // now we need to indicate how they correspond; we could either + // #1: sort a different data structure that says who they correspond to + // #2: for each sorted entry, search the original list to find who corresponds + // #3: for each original entry, find the sorted entry + // #1 requires extra storage, #2 is slow, #3 can use binary search! + for (i=0; i < len; ++i) { + int huff_len = c->sparse ? lengths[values[i]] : lengths[i]; + if (include_in_sort(c,huff_len)) { + uint32 code = bit_reverse(c->codewords[i]); + int x=0, n=c->sorted_entries; + while (n > 1) { + // invariant: sc[x] <= code < sc[x+n] + int m = x + (n >> 1); + if (c->sorted_codewords[m] <= code) { + x = m; + n -= (n>>1); + } else { + n >>= 1; + } + } + assert(c->sorted_codewords[x] == code); + if (c->sparse) { + c->sorted_values[x] = values[i]; + c->codeword_lengths[x] = huff_len; + } else { + c->sorted_values[x] = i; + } + } + } +} + +// only run while parsing the header (3 times) +static int vorbis_validate(uint8 *data) +{ + static uint8 vorbis[6] = { 'v', 'o', 'r', 'b', 'i', 's' }; + return memcmp(data, vorbis, 6) == 0; +} + +// called from setup only, once per code book +// (formula implied by specification) +static int lookup1_values(int entries, int dim) +{ + int r = (int) floor(exp((float) log((float) entries) / dim)); + if ((int) floor(pow((float) r+1, dim)) <= entries) // (int) cast for MinGW warning; + ++r; // floor() to avoid _ftol() when non-CRT + if (pow((float) r+1, dim) <= entries) + return -1; + if ((int) floor(pow((float) r, dim)) > entries) + return -1; + return r; +} + +// called twice per file +static void compute_twiddle_factors(int n, float *A, float *B, float *C) +{ + int n4 = n >> 2, n8 = n >> 3; + int k,k2; + + for (k=k2=0; k < n4; ++k,k2+=2) { + A[k2 ] = (float) cos(4*k*M_PI/n); + A[k2+1] = (float) -sin(4*k*M_PI/n); + B[k2 ] = (float) cos((k2+1)*M_PI/n/2) * 0.5f; + B[k2+1] = (float) sin((k2+1)*M_PI/n/2) * 0.5f; + } + for (k=k2=0; k < n8; ++k,k2+=2) { + C[k2 ] = (float) cos(2*(k2+1)*M_PI/n); + C[k2+1] = (float) -sin(2*(k2+1)*M_PI/n); + } +} + +static void compute_window(int n, float *window) +{ + int n2 = n >> 1, i; + for (i=0; i < n2; ++i) + window[i] = (float) sin(0.5 * M_PI * square((float) sin((i - 0 + 0.5) / n2 * 0.5 * M_PI))); +} + +static void compute_bitreverse(int n, uint16 *rev) +{ + int ld = ilog(n) - 1; // ilog is off-by-one from normal definitions + int i, n8 = n >> 3; + for (i=0; i < n8; ++i) + rev[i] = (bit_reverse(i) >> (32-ld+3)) << 2; +} + +static int init_blocksize(vorb *f, int b, int n) +{ + int n2 = n >> 1, n4 = n >> 2, n8 = n >> 3; + f->A[b] = (float *) setup_malloc(f, sizeof(float) * n2); + f->B[b] = (float *) setup_malloc(f, sizeof(float) * n2); + f->C[b] = (float *) setup_malloc(f, sizeof(float) * n4); + if (!f->A[b] || !f->B[b] || !f->C[b]) return error(f, VORBIS_outofmem); + compute_twiddle_factors(n, f->A[b], f->B[b], f->C[b]); + f->window[b] = (float *) setup_malloc(f, sizeof(float) * n2); + if (!f->window[b]) return error(f, VORBIS_outofmem); + compute_window(n, f->window[b]); + f->bit_reverse[b] = (uint16 *) setup_malloc(f, sizeof(uint16) * n8); + if (!f->bit_reverse[b]) return error(f, VORBIS_outofmem); + compute_bitreverse(n, f->bit_reverse[b]); + return TRUE; +} + +static void neighbors(uint16 *x, int n, int *plow, int *phigh) +{ + int low = -1; + int high = 65536; + int i; + for (i=0; i < n; ++i) { + if (x[i] > low && x[i] < x[n]) { *plow = i; low = x[i]; } + if (x[i] < high && x[i] > x[n]) { *phigh = i; high = x[i]; } + } +} + +// this has been repurposed so y is now the original index instead of y +typedef struct +{ + uint16 x,id; +} stbv__floor_ordering; + +static int STBV_CDECL point_compare(const void *p, const void *q) +{ + stbv__floor_ordering *a = (stbv__floor_ordering *) p; + stbv__floor_ordering *b = (stbv__floor_ordering *) q; + return a->x < b->x ? -1 : a->x > b->x; +} + +// +/////////////////////// END LEAF SETUP FUNCTIONS ////////////////////////// + + +#if defined(STB_VORBIS_NO_STDIO) + #define USE_MEMORY(z) TRUE +#else + #define USE_MEMORY(z) ((z)->stream) +#endif + +static uint8 get8(vorb *z) +{ + if (USE_MEMORY(z)) { + if (z->stream >= z->stream_end) { z->eof = TRUE; return 0; } + return *z->stream++; + } + + #ifndef STB_VORBIS_NO_STDIO + { + /* FAudio change! */ + int8 c; + if (fread(&c, 1, 1, z->f) != 1) { z->eof = TRUE; return 0; } + return c; + } + #endif +} + +static uint32 get32(vorb *f) +{ + uint32 x; + x = get8(f); + x += get8(f) << 8; + x += get8(f) << 16; + x += (uint32) get8(f) << 24; + return x; +} + +static int getn(vorb *z, uint8 *data, int n) +{ + if (USE_MEMORY(z)) { + if (z->stream+n > z->stream_end) { z->eof = 1; return 0; } + memcpy(data, z->stream, n); + z->stream += n; + return 1; + } + + #ifndef STB_VORBIS_NO_STDIO + if (fread(data, n, 1, z->f) == 1) + return 1; + else { + z->eof = 1; + return 0; + } + #endif +} + +static void skip(vorb *z, int n) +{ + if (USE_MEMORY(z)) { + z->stream += n; + if (z->stream >= z->stream_end) z->eof = 1; + return; + } + #ifndef STB_VORBIS_NO_STDIO + { + long x = ftell(z->f); + fseek(z->f, x+n, SEEK_SET); + } + #endif +} + +static int set_file_offset(stb_vorbis *f, unsigned int loc) +{ + #ifndef STB_VORBIS_NO_PUSHDATA_API + if (f->push_mode) return 0; + #endif + f->eof = 0; + if (USE_MEMORY(f)) { + if (f->stream_start + loc >= f->stream_end || f->stream_start + loc < f->stream_start) { + f->stream = f->stream_end; + f->eof = 1; + return 0; + } else { + f->stream = f->stream_start + loc; + return 1; + } + } + #ifndef STB_VORBIS_NO_STDIO + if (loc + f->f_start < loc || loc >= 0x80000000) { + loc = 0x7fffffff; + f->eof = 1; + } else { + loc += f->f_start; + } + if (fseek(f->f, loc, SEEK_SET) != -1) /* FAudio change! */ + return 1; + f->eof = 1; + fseek(f->f, f->f_start, SEEK_END); + return 0; + #endif +} + + +static uint8 ogg_page_header[4] = { 0x4f, 0x67, 0x67, 0x53 }; + +static int capture_pattern(vorb *f) +{ + if (0x4f != get8(f)) return FALSE; + if (0x67 != get8(f)) return FALSE; + if (0x67 != get8(f)) return FALSE; + if (0x53 != get8(f)) return FALSE; + return TRUE; +} + +#define PAGEFLAG_continued_packet 1 +#define PAGEFLAG_first_page 2 +#define PAGEFLAG_last_page 4 + +static int start_page_no_capturepattern(vorb *f) +{ + uint32 loc0,loc1,n; + if (f->first_decode && !IS_PUSH_MODE(f)) { + f->p_first.page_start = stb_vorbis_get_file_offset(f) - 4; + } + // stream structure version + if (0 != get8(f)) return error(f, VORBIS_invalid_stream_structure_version); + // header flag + f->page_flag = get8(f); + // absolute granule position + loc0 = get32(f); + loc1 = get32(f); + // @TODO: validate loc0,loc1 as valid positions? + // stream serial number -- vorbis doesn't interleave, so discard + get32(f); + //if (f->serial != get32(f)) return error(f, VORBIS_incorrect_stream_serial_number); + // page sequence number + n = get32(f); + f->last_page = n; + // CRC32 + get32(f); + // page_segments + f->segment_count = get8(f); + if (!getn(f, f->segments, f->segment_count)) + return error(f, VORBIS_unexpected_eof); + // assume we _don't_ know any the sample position of any segments + f->end_seg_with_known_loc = -2; + if (loc0 != ~0U || loc1 != ~0U) { + int i; + // determine which packet is the last one that will complete + for (i=f->segment_count-1; i >= 0; --i) + if (f->segments[i] < 255) + break; + // 'i' is now the index of the _last_ segment of a packet that ends + if (i >= 0) { + f->end_seg_with_known_loc = i; + f->known_loc_for_packet = loc0; + } + } + if (f->first_decode) { + int i,len; + len = 0; + for (i=0; i < f->segment_count; ++i) + len += f->segments[i]; + len += 27 + f->segment_count; + f->p_first.page_end = f->p_first.page_start + len; + f->p_first.last_decoded_sample = loc0; + } + f->next_seg = 0; + return TRUE; +} + +static int start_page(vorb *f) +{ + if (!capture_pattern(f)) return error(f, VORBIS_missing_capture_pattern); + return start_page_no_capturepattern(f); +} + +static int start_packet(vorb *f) +{ + while (f->next_seg == -1) { + if (!start_page(f)) return FALSE; + if (f->page_flag & PAGEFLAG_continued_packet) + return error(f, VORBIS_continued_packet_flag_invalid); + } + f->last_seg = FALSE; + f->valid_bits = 0; + f->packet_bytes = 0; + f->bytes_in_seg = 0; + // f->next_seg is now valid + return TRUE; +} + +static int maybe_start_packet(vorb *f) +{ + if (f->next_seg == -1) { + int x = get8(f); + if (f->eof) return FALSE; // EOF at page boundary is not an error! + if (0x4f != x ) return error(f, VORBIS_missing_capture_pattern); + if (0x67 != get8(f)) return error(f, VORBIS_missing_capture_pattern); + if (0x67 != get8(f)) return error(f, VORBIS_missing_capture_pattern); + if (0x53 != get8(f)) return error(f, VORBIS_missing_capture_pattern); + if (!start_page_no_capturepattern(f)) return FALSE; + if (f->page_flag & PAGEFLAG_continued_packet) { + // set up enough state that we can read this packet if we want, + // e.g. during recovery + f->last_seg = FALSE; + f->bytes_in_seg = 0; + return error(f, VORBIS_continued_packet_flag_invalid); + } + } + return start_packet(f); +} + +static int next_segment(vorb *f) +{ + int len; + if (f->last_seg) return 0; + if (f->next_seg == -1) { + f->last_seg_which = f->segment_count-1; // in case start_page fails + if (!start_page(f)) { f->last_seg = 1; return 0; } + if (!(f->page_flag & PAGEFLAG_continued_packet)) return error(f, VORBIS_continued_packet_flag_invalid); + } + len = f->segments[f->next_seg++]; + if (len < 255) { + f->last_seg = TRUE; + f->last_seg_which = f->next_seg-1; + } + if (f->next_seg >= f->segment_count) + f->next_seg = -1; + assert(f->bytes_in_seg == 0); + f->bytes_in_seg = len; + return len; +} + +#define EOP (-1) +#define INVALID_BITS (-1) + +static int get8_packet_raw(vorb *f) +{ + if (!f->bytes_in_seg) { // CLANG! + if (f->last_seg) return EOP; + else if (!next_segment(f)) return EOP; + } + assert(f->bytes_in_seg > 0); + --f->bytes_in_seg; + ++f->packet_bytes; + return get8(f); +} + +static int get8_packet(vorb *f) +{ + int x = get8_packet_raw(f); + f->valid_bits = 0; + return x; +} + +static int get32_packet(vorb *f) +{ + uint32 x; + x = get8_packet(f); + x += get8_packet(f) << 8; + x += get8_packet(f) << 16; + x += (uint32) get8_packet(f) << 24; + return x; +} + +static void flush_packet(vorb *f) +{ + while (get8_packet_raw(f) != EOP); +} + +// @OPTIMIZE: this is the secondary bit decoder, so it's probably not as important +// as the huffman decoder? +static uint32 get_bits(vorb *f, int n) +{ + uint32 z; + + if (f->valid_bits < 0) return 0; + if (f->valid_bits < n) { + if (n > 24) { + // the accumulator technique below would not work correctly in this case + z = get_bits(f, 24); + z += get_bits(f, n-24) << 24; + return z; + } + if (f->valid_bits == 0) f->acc = 0; + while (f->valid_bits < n) { + int z = get8_packet_raw(f); + if (z == EOP) { + f->valid_bits = INVALID_BITS; + return 0; + } + f->acc += z << f->valid_bits; + f->valid_bits += 8; + } + } + + assert(f->valid_bits >= n); + z = f->acc & ((1 << n)-1); + f->acc >>= n; + f->valid_bits -= n; + return z; +} + +// @OPTIMIZE: primary accumulator for huffman +// expand the buffer to as many bits as possible without reading off end of packet +// it might be nice to allow f->valid_bits and f->acc to be stored in registers, +// e.g. cache them locally and decode locally +static __forceinline void prep_huffman(vorb *f) +{ + if (f->valid_bits <= 24) { + if (f->valid_bits == 0) f->acc = 0; + do { + int z; + if (f->last_seg && !f->bytes_in_seg) return; + z = get8_packet_raw(f); + if (z == EOP) return; + f->acc += (unsigned) z << f->valid_bits; + f->valid_bits += 8; + } while (f->valid_bits <= 24); + } +} + +enum +{ + VORBIS_packet_id = 1, + VORBIS_packet_comment = 3, + VORBIS_packet_setup = 5 +}; + +static int codebook_decode_scalar_raw(vorb *f, Codebook *c) +{ + int i; + prep_huffman(f); + + if (c->codewords == NULL && c->sorted_codewords == NULL) + return -1; + + // cases to use binary search: sorted_codewords && !c->codewords + // sorted_codewords && c->entries > 8 + if (c->entries > 8 ? c->sorted_codewords!=NULL : !c->codewords) { + // binary search + uint32 code = bit_reverse(f->acc); + int x=0, n=c->sorted_entries, len; + + while (n > 1) { + // invariant: sc[x] <= code < sc[x+n] + int m = x + (n >> 1); + if (c->sorted_codewords[m] <= code) { + x = m; + n -= (n>>1); + } else { + n >>= 1; + } + } + // x is now the sorted index + if (!c->sparse) x = c->sorted_values[x]; + // x is now sorted index if sparse, or symbol otherwise + len = c->codeword_lengths[x]; + if (f->valid_bits >= len) { + f->acc >>= len; + f->valid_bits -= len; + return x; + } + + f->valid_bits = 0; + return -1; + } + + // if small, linear search + assert(!c->sparse); + for (i=0; i < c->entries; ++i) { + if (c->codeword_lengths[i] == NO_CODE) continue; + if (c->codewords[i] == (f->acc & ((1 << c->codeword_lengths[i])-1))) { + if (f->valid_bits >= c->codeword_lengths[i]) { + f->acc >>= c->codeword_lengths[i]; + f->valid_bits -= c->codeword_lengths[i]; + return i; + } + f->valid_bits = 0; + return -1; + } + } + + error(f, VORBIS_invalid_stream); + f->valid_bits = 0; + return -1; +} + +#ifndef STB_VORBIS_NO_INLINE_DECODE + +#define DECODE_RAW(var, f,c) \ + if (f->valid_bits < STB_VORBIS_FAST_HUFFMAN_LENGTH) \ + prep_huffman(f); \ + var = f->acc & FAST_HUFFMAN_TABLE_MASK; \ + var = c->fast_huffman[var]; \ + if (var >= 0) { \ + int n = c->codeword_lengths[var]; \ + f->acc >>= n; \ + f->valid_bits -= n; \ + if (f->valid_bits < 0) { f->valid_bits = 0; var = -1; } \ + } else { \ + var = codebook_decode_scalar_raw(f,c); \ + } + +#else + +static int codebook_decode_scalar(vorb *f, Codebook *c) +{ + int i; + if (f->valid_bits < STB_VORBIS_FAST_HUFFMAN_LENGTH) + prep_huffman(f); + // fast huffman table lookup + i = f->acc & FAST_HUFFMAN_TABLE_MASK; + i = c->fast_huffman[i]; + if (i >= 0) { + f->acc >>= c->codeword_lengths[i]; + f->valid_bits -= c->codeword_lengths[i]; + if (f->valid_bits < 0) { f->valid_bits = 0; return -1; } + return i; + } + return codebook_decode_scalar_raw(f,c); +} + +#define DECODE_RAW(var,f,c) var = codebook_decode_scalar(f,c); + +#endif + +#define DECODE(var,f,c) \ + DECODE_RAW(var,f,c) \ + if (c->sparse) var = c->sorted_values[var]; + +#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK + #define DECODE_VQ(var,f,c) DECODE_RAW(var,f,c) +#else + #define DECODE_VQ(var,f,c) DECODE(var,f,c) +#endif + + + + + + +// CODEBOOK_ELEMENT_FAST is an optimization for the CODEBOOK_FLOATS case +// where we avoid one addition +#define CODEBOOK_ELEMENT(c,off) (c->multiplicands[off]) +#define CODEBOOK_ELEMENT_FAST(c,off) (c->multiplicands[off]) +#define CODEBOOK_ELEMENT_BASE(c) (0) + +static int codebook_decode_start(vorb *f, Codebook *c) +{ + int z = -1; + + // type 0 is only legal in a scalar context + if (c->lookup_type == 0) + error(f, VORBIS_invalid_stream); + else { + DECODE_VQ(z,f,c); + if (c->sparse) assert(z < c->sorted_entries); + if (z < 0) { // check for EOP + if (!f->bytes_in_seg) + if (f->last_seg) + return z; + error(f, VORBIS_invalid_stream); + } + } + return z; +} + +static int codebook_decode(vorb *f, Codebook *c, float *output, int len) +{ + int i,z = codebook_decode_start(f,c); + if (z < 0) return FALSE; + if (len > c->dimensions) len = c->dimensions; + +#ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (c->lookup_type == 1) { + float last = CODEBOOK_ELEMENT_BASE(c); + int div = 1; + for (i=0; i < len; ++i) { + int off = (z / div) % c->lookup_values; + float val = CODEBOOK_ELEMENT_FAST(c,off) + last; + output[i] += val; + if (c->sequence_p) last = val + c->minimum_value; + div *= c->lookup_values; + } + return TRUE; + } +#endif + + z *= c->dimensions; + if (c->sequence_p) { + float last = CODEBOOK_ELEMENT_BASE(c); + for (i=0; i < len; ++i) { + float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; + output[i] += val; + last = val + c->minimum_value; + } + } else { + float last = CODEBOOK_ELEMENT_BASE(c); + for (i=0; i < len; ++i) { + output[i] += CODEBOOK_ELEMENT_FAST(c,z+i) + last; + } + } + + return TRUE; +} + +static int codebook_decode_step(vorb *f, Codebook *c, float *output, int len, int step) +{ + int i,z = codebook_decode_start(f,c); + float last = CODEBOOK_ELEMENT_BASE(c); + if (z < 0) return FALSE; + if (len > c->dimensions) len = c->dimensions; + +#ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (c->lookup_type == 1) { + int div = 1; + for (i=0; i < len; ++i) { + int off = (z / div) % c->lookup_values; + float val = CODEBOOK_ELEMENT_FAST(c,off) + last; + output[i*step] += val; + if (c->sequence_p) last = val; + div *= c->lookup_values; + } + return TRUE; + } +#endif + + z *= c->dimensions; + for (i=0; i < len; ++i) { + float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; + output[i*step] += val; + if (c->sequence_p) last = val; + } + + return TRUE; +} + +static int codebook_decode_deinterleave_repeat(vorb *f, Codebook *c, float **outputs, int ch, int *c_inter_p, int *p_inter_p, int len, int total_decode) +{ + int c_inter = *c_inter_p; + int p_inter = *p_inter_p; + int i,z, effective = c->dimensions; + + // type 0 is only legal in a scalar context + if (c->lookup_type == 0) return error(f, VORBIS_invalid_stream); + + while (total_decode > 0) { + float last = CODEBOOK_ELEMENT_BASE(c); + DECODE_VQ(z,f,c); + #ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK + assert(!c->sparse || z < c->sorted_entries); + #endif + if (z < 0) { + if (!f->bytes_in_seg) + if (f->last_seg) return FALSE; + return error(f, VORBIS_invalid_stream); + } + + // if this will take us off the end of the buffers, stop short! + // we check by computing the length of the virtual interleaved + // buffer (len*ch), our current offset within it (p_inter*ch)+(c_inter), + // and the length we'll be using (effective) + if (c_inter + p_inter*ch + effective > len * ch) { + effective = len*ch - (p_inter*ch - c_inter); + } + + #ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (c->lookup_type == 1) { + int div = 1; + for (i=0; i < effective; ++i) { + int off = (z / div) % c->lookup_values; + float val = CODEBOOK_ELEMENT_FAST(c,off) + last; + if (outputs[c_inter]) + outputs[c_inter][p_inter] += val; + if (++c_inter == ch) { c_inter = 0; ++p_inter; } + if (c->sequence_p) last = val; + div *= c->lookup_values; + } + } else + #endif + { + z *= c->dimensions; + if (c->sequence_p) { + for (i=0; i < effective; ++i) { + float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; + if (outputs[c_inter]) + outputs[c_inter][p_inter] += val; + if (++c_inter == ch) { c_inter = 0; ++p_inter; } + last = val; + } + } else { + for (i=0; i < effective; ++i) { + float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; + if (outputs[c_inter]) + outputs[c_inter][p_inter] += val; + if (++c_inter == ch) { c_inter = 0; ++p_inter; } + } + } + } + + total_decode -= effective; + } + *c_inter_p = c_inter; + *p_inter_p = p_inter; + return TRUE; +} + +static int predict_point(int x, int x0, int x1, int y0, int y1) +{ + int dy = y1 - y0; + int adx = x1 - x0; + // @OPTIMIZE: force int division to round in the right direction... is this necessary on x86? + int err = abs(dy) * (x - x0); + int off = err / adx; + return dy < 0 ? y0 - off : y0 + off; +} + +// the following table is block-copied from the specification +static float inverse_db_table[256] = +{ + 1.0649863e-07f, 1.1341951e-07f, 1.2079015e-07f, 1.2863978e-07f, + 1.3699951e-07f, 1.4590251e-07f, 1.5538408e-07f, 1.6548181e-07f, + 1.7623575e-07f, 1.8768855e-07f, 1.9988561e-07f, 2.1287530e-07f, + 2.2670913e-07f, 2.4144197e-07f, 2.5713223e-07f, 2.7384213e-07f, + 2.9163793e-07f, 3.1059021e-07f, 3.3077411e-07f, 3.5226968e-07f, + 3.7516214e-07f, 3.9954229e-07f, 4.2550680e-07f, 4.5315863e-07f, + 4.8260743e-07f, 5.1396998e-07f, 5.4737065e-07f, 5.8294187e-07f, + 6.2082472e-07f, 6.6116941e-07f, 7.0413592e-07f, 7.4989464e-07f, + 7.9862701e-07f, 8.5052630e-07f, 9.0579828e-07f, 9.6466216e-07f, + 1.0273513e-06f, 1.0941144e-06f, 1.1652161e-06f, 1.2409384e-06f, + 1.3215816e-06f, 1.4074654e-06f, 1.4989305e-06f, 1.5963394e-06f, + 1.7000785e-06f, 1.8105592e-06f, 1.9282195e-06f, 2.0535261e-06f, + 2.1869758e-06f, 2.3290978e-06f, 2.4804557e-06f, 2.6416497e-06f, + 2.8133190e-06f, 2.9961443e-06f, 3.1908506e-06f, 3.3982101e-06f, + 3.6190449e-06f, 3.8542308e-06f, 4.1047004e-06f, 4.3714470e-06f, + 4.6555282e-06f, 4.9580707e-06f, 5.2802740e-06f, 5.6234160e-06f, + 5.9888572e-06f, 6.3780469e-06f, 6.7925283e-06f, 7.2339451e-06f, + 7.7040476e-06f, 8.2047000e-06f, 8.7378876e-06f, 9.3057248e-06f, + 9.9104632e-06f, 1.0554501e-05f, 1.1240392e-05f, 1.1970856e-05f, + 1.2748789e-05f, 1.3577278e-05f, 1.4459606e-05f, 1.5399272e-05f, + 1.6400004e-05f, 1.7465768e-05f, 1.8600792e-05f, 1.9809576e-05f, + 2.1096914e-05f, 2.2467911e-05f, 2.3928002e-05f, 2.5482978e-05f, + 2.7139006e-05f, 2.8902651e-05f, 3.0780908e-05f, 3.2781225e-05f, + 3.4911534e-05f, 3.7180282e-05f, 3.9596466e-05f, 4.2169667e-05f, + 4.4910090e-05f, 4.7828601e-05f, 5.0936773e-05f, 5.4246931e-05f, + 5.7772202e-05f, 6.1526565e-05f, 6.5524908e-05f, 6.9783085e-05f, + 7.4317983e-05f, 7.9147585e-05f, 8.4291040e-05f, 8.9768747e-05f, + 9.5602426e-05f, 0.00010181521f, 0.00010843174f, 0.00011547824f, + 0.00012298267f, 0.00013097477f, 0.00013948625f, 0.00014855085f, + 0.00015820453f, 0.00016848555f, 0.00017943469f, 0.00019109536f, + 0.00020351382f, 0.00021673929f, 0.00023082423f, 0.00024582449f, + 0.00026179955f, 0.00027881276f, 0.00029693158f, 0.00031622787f, + 0.00033677814f, 0.00035866388f, 0.00038197188f, 0.00040679456f, + 0.00043323036f, 0.00046138411f, 0.00049136745f, 0.00052329927f, + 0.00055730621f, 0.00059352311f, 0.00063209358f, 0.00067317058f, + 0.00071691700f, 0.00076350630f, 0.00081312324f, 0.00086596457f, + 0.00092223983f, 0.00098217216f, 0.0010459992f, 0.0011139742f, + 0.0011863665f, 0.0012634633f, 0.0013455702f, 0.0014330129f, + 0.0015261382f, 0.0016253153f, 0.0017309374f, 0.0018434235f, + 0.0019632195f, 0.0020908006f, 0.0022266726f, 0.0023713743f, + 0.0025254795f, 0.0026895994f, 0.0028643847f, 0.0030505286f, + 0.0032487691f, 0.0034598925f, 0.0036847358f, 0.0039241906f, + 0.0041792066f, 0.0044507950f, 0.0047400328f, 0.0050480668f, + 0.0053761186f, 0.0057254891f, 0.0060975636f, 0.0064938176f, + 0.0069158225f, 0.0073652516f, 0.0078438871f, 0.0083536271f, + 0.0088964928f, 0.009474637f, 0.010090352f, 0.010746080f, + 0.011444421f, 0.012188144f, 0.012980198f, 0.013823725f, + 0.014722068f, 0.015678791f, 0.016697687f, 0.017782797f, + 0.018938423f, 0.020169149f, 0.021479854f, 0.022875735f, + 0.024362330f, 0.025945531f, 0.027631618f, 0.029427276f, + 0.031339626f, 0.033376252f, 0.035545228f, 0.037855157f, + 0.040315199f, 0.042935108f, 0.045725273f, 0.048696758f, + 0.051861348f, 0.055231591f, 0.058820850f, 0.062643361f, + 0.066714279f, 0.071049749f, 0.075666962f, 0.080584227f, + 0.085821044f, 0.091398179f, 0.097337747f, 0.10366330f, + 0.11039993f, 0.11757434f, 0.12521498f, 0.13335215f, + 0.14201813f, 0.15124727f, 0.16107617f, 0.17154380f, + 0.18269168f, 0.19456402f, 0.20720788f, 0.22067342f, + 0.23501402f, 0.25028656f, 0.26655159f, 0.28387361f, + 0.30232132f, 0.32196786f, 0.34289114f, 0.36517414f, + 0.38890521f, 0.41417847f, 0.44109412f, 0.46975890f, + 0.50028648f, 0.53279791f, 0.56742212f, 0.60429640f, + 0.64356699f, 0.68538959f, 0.72993007f, 0.77736504f, + 0.82788260f, 0.88168307f, 0.9389798f, 1.0f +}; + + +// @OPTIMIZE: if you want to replace this bresenham line-drawing routine, +// note that you must produce bit-identical output to decode correctly; +// this specific sequence of operations is specified in the spec (it's +// drawing integer-quantized frequency-space lines that the encoder +// expects to be exactly the same) +// ... also, isn't the whole point of Bresenham's algorithm to NOT +// have to divide in the setup? sigh. +#ifndef STB_VORBIS_NO_DEFER_FLOOR +#define LINE_OP(a,b) a *= b +#else +#define LINE_OP(a,b) a = b +#endif + +#ifdef STB_VORBIS_DIVIDE_TABLE +#define DIVTAB_NUMER 32 +#define DIVTAB_DENOM 64 +int8 integer_divide_table[DIVTAB_NUMER][DIVTAB_DENOM]; // 2KB +#endif + +static __forceinline void draw_line(float *output, int x0, int y0, int x1, int y1, int n) +{ + int dy = y1 - y0; + int adx = x1 - x0; + int ady = abs(dy); + int base; + int x=x0,y=y0; + int err = 0; + int sy; + +#ifdef STB_VORBIS_DIVIDE_TABLE + if (adx < DIVTAB_DENOM && ady < DIVTAB_NUMER) { + if (dy < 0) { + base = -integer_divide_table[ady][adx]; + sy = base-1; + } else { + base = integer_divide_table[ady][adx]; + sy = base+1; + } + } else { + base = dy / adx; + if (dy < 0) + sy = base - 1; + else + sy = base+1; + } +#else + base = dy / adx; + if (dy < 0) + sy = base - 1; + else + sy = base+1; +#endif + ady -= abs(base) * adx; + if (x1 > n) x1 = n; + if (x < x1) { + LINE_OP(output[x], inverse_db_table[y&255]); + for (++x; x < x1; ++x) { + err += ady; + if (err >= adx) { + err -= adx; + y += sy; + } else + y += base; + LINE_OP(output[x], inverse_db_table[y&255]); + } + } +} + +static int residue_decode(vorb *f, Codebook *book, float *target, int offset, int n, int rtype) +{ + int k; + if (rtype == 0) { + int step = n / book->dimensions; + for (k=0; k < step; ++k) + if (!codebook_decode_step(f, book, target+offset+k, n-offset-k, step)) + return FALSE; + } else { + for (k=0; k < n; ) { + if (!codebook_decode(f, book, target+offset, n-k)) + return FALSE; + k += book->dimensions; + offset += book->dimensions; + } + } + return TRUE; +} + +// n is 1/2 of the blocksize -- +// specification: "Correct per-vector decode length is [n]/2" +static void decode_residue(vorb *f, float *residue_buffers[], int ch, int n, int rn, uint8 *do_not_decode) +{ + int i,j,pass; + Residue *r = f->residue_config + rn; + int rtype = f->residue_types[rn]; + int c = r->classbook; + int classwords = f->codebooks[c].dimensions; + unsigned int actual_size = rtype == 2 ? n*2 : n; + unsigned int limit_r_begin = (r->begin < actual_size ? r->begin : actual_size); + unsigned int limit_r_end = (r->end < actual_size ? r->end : actual_size); + int n_read = limit_r_end - limit_r_begin; + int part_read = n_read / r->part_size; + int temp_alloc_point = temp_alloc_save(f); + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + uint8 ***part_classdata = (uint8 ***) temp_block_array(f,f->channels, part_read * sizeof(**part_classdata)); + #else + int **classifications = (int **) temp_block_array(f,f->channels, part_read * sizeof(**classifications)); + #endif + + CHECK(f); + + for (i=0; i < ch; ++i) + if (!do_not_decode[i]) + memset(residue_buffers[i], 0, sizeof(float) * n); + + if (rtype == 2 && ch != 1) { + for (j=0; j < ch; ++j) + if (!do_not_decode[j]) + break; + if (j == ch) + goto done; + + for (pass=0; pass < 8; ++pass) { + int pcount = 0, class_set = 0; + if (ch == 2) { + while (pcount < part_read) { + int z = r->begin + pcount*r->part_size; + int c_inter = (z & 1), p_inter = z>>1; + if (pass == 0) { + Codebook *c = f->codebooks+r->classbook; + int q; + DECODE(q,f,c); + if (q == EOP) goto done; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + part_classdata[0][class_set] = r->classdata[q]; + #else + for (i=classwords-1; i >= 0; --i) { + classifications[0][i+pcount] = q % r->classifications; + q /= r->classifications; + } + #endif + } + for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { + int z = r->begin + pcount*r->part_size; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + int c = part_classdata[0][class_set][i]; + #else + int c = classifications[0][pcount]; + #endif + int b = r->residue_books[c][pass]; + if (b >= 0) { + Codebook *book = f->codebooks + b; + #ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) + goto done; + #else + // saves 1% + if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) + goto done; + #endif + } else { + z += r->part_size; + c_inter = z & 1; + p_inter = z >> 1; + } + } + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + ++class_set; + #endif + } + } else if (ch > 2) { + while (pcount < part_read) { + int z = r->begin + pcount*r->part_size; + int c_inter = z % ch, p_inter = z/ch; + if (pass == 0) { + Codebook *c = f->codebooks+r->classbook; + int q; + DECODE(q,f,c); + if (q == EOP) goto done; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + part_classdata[0][class_set] = r->classdata[q]; + #else + for (i=classwords-1; i >= 0; --i) { + classifications[0][i+pcount] = q % r->classifications; + q /= r->classifications; + } + #endif + } + for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { + int z = r->begin + pcount*r->part_size; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + int c = part_classdata[0][class_set][i]; + #else + int c = classifications[0][pcount]; + #endif + int b = r->residue_books[c][pass]; + if (b >= 0) { + Codebook *book = f->codebooks + b; + if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) + goto done; + } else { + z += r->part_size; + c_inter = z % ch; + p_inter = z / ch; + } + } + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + ++class_set; + #endif + } + } + } + goto done; + } + CHECK(f); + + for (pass=0; pass < 8; ++pass) { + int pcount = 0, class_set=0; + while (pcount < part_read) { + if (pass == 0) { + for (j=0; j < ch; ++j) { + if (!do_not_decode[j]) { + Codebook *c = f->codebooks+r->classbook; + int temp; + DECODE(temp,f,c); + if (temp == EOP) goto done; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + part_classdata[j][class_set] = r->classdata[temp]; + #else + for (i=classwords-1; i >= 0; --i) { + classifications[j][i+pcount] = temp % r->classifications; + temp /= r->classifications; + } + #endif + } + } + } + for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { + for (j=0; j < ch; ++j) { + if (!do_not_decode[j]) { + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + int c = part_classdata[j][class_set][i]; + #else + int c = classifications[j][pcount]; + #endif + int b = r->residue_books[c][pass]; + if (b >= 0) { + float *target = residue_buffers[j]; + int offset = r->begin + pcount * r->part_size; + int n = r->part_size; + Codebook *book = f->codebooks + b; + if (!residue_decode(f, book, target, offset, n, rtype)) + goto done; + } + } + } + } + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + ++class_set; + #endif + } + } + done: + CHECK(f); + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + temp_free(f,part_classdata); + #else + temp_free(f,classifications); + #endif + temp_alloc_restore(f,temp_alloc_point); +} + + +#if 0 +// slow way for debugging +void inverse_mdct_slow(float *buffer, int n) +{ + int i,j; + int n2 = n >> 1; + float *x = (float *) malloc(sizeof(*x) * n2); + memcpy(x, buffer, sizeof(*x) * n2); + for (i=0; i < n; ++i) { + float acc = 0; + for (j=0; j < n2; ++j) + // formula from paper: + //acc += n/4.0f * x[j] * (float) cos(M_PI / 2 / n * (2 * i + 1 + n/2.0)*(2*j+1)); + // formula from wikipedia + //acc += 2.0f / n2 * x[j] * (float) cos(M_PI/n2 * (i + 0.5 + n2/2)*(j + 0.5)); + // these are equivalent, except the formula from the paper inverts the multiplier! + // however, what actually works is NO MULTIPLIER!?! + //acc += 64 * 2.0f / n2 * x[j] * (float) cos(M_PI/n2 * (i + 0.5 + n2/2)*(j + 0.5)); + acc += x[j] * (float) cos(M_PI / 2 / n * (2 * i + 1 + n/2.0)*(2*j+1)); + buffer[i] = acc; + } + free(x); +} +#elif 0 +// same as above, but just barely able to run in real time on modern machines +void inverse_mdct_slow(float *buffer, int n, vorb *f, int blocktype) +{ + float mcos[16384]; + int i,j; + int n2 = n >> 1, nmask = (n << 2) -1; + float *x = (float *) malloc(sizeof(*x) * n2); + memcpy(x, buffer, sizeof(*x) * n2); + for (i=0; i < 4*n; ++i) + mcos[i] = (float) cos(M_PI / 2 * i / n); + + for (i=0; i < n; ++i) { + float acc = 0; + for (j=0; j < n2; ++j) + acc += x[j] * mcos[(2 * i + 1 + n2)*(2*j+1) & nmask]; + buffer[i] = acc; + } + free(x); +} +#elif 0 +// transform to use a slow dct-iv; this is STILL basically trivial, +// but only requires half as many ops +void dct_iv_slow(float *buffer, int n) +{ + float mcos[16384]; + float x[2048]; + int i,j; + int n2 = n >> 1, nmask = (n << 3) - 1; + memcpy(x, buffer, sizeof(*x) * n); + for (i=0; i < 8*n; ++i) + mcos[i] = (float) cos(M_PI / 4 * i / n); + for (i=0; i < n; ++i) { + float acc = 0; + for (j=0; j < n; ++j) + acc += x[j] * mcos[((2 * i + 1)*(2*j+1)) & nmask]; + buffer[i] = acc; + } +} + +void inverse_mdct_slow(float *buffer, int n, vorb *f, int blocktype) +{ + int i, n4 = n >> 2, n2 = n >> 1, n3_4 = n - n4; + float temp[4096]; + + memcpy(temp, buffer, n2 * sizeof(float)); + dct_iv_slow(temp, n2); // returns -c'-d, a-b' + + for (i=0; i < n4 ; ++i) buffer[i] = temp[i+n4]; // a-b' + for ( ; i < n3_4; ++i) buffer[i] = -temp[n3_4 - i - 1]; // b-a', c+d' + for ( ; i < n ; ++i) buffer[i] = -temp[i - n3_4]; // c'+d +} +#endif + +#ifndef LIBVORBIS_MDCT +#define LIBVORBIS_MDCT 0 +#endif + +#if LIBVORBIS_MDCT +// directly call the vorbis MDCT using an interface documented +// by Jeff Roberts... useful for performance comparison +typedef struct +{ + int n; + int log2n; + + float *trig; + int *bitrev; + + float scale; +} mdct_lookup; + +extern void mdct_init(mdct_lookup *lookup, int n); +extern void mdct_clear(mdct_lookup *l); +extern void mdct_backward(mdct_lookup *init, float *in, float *out); + +mdct_lookup M1,M2; + +void inverse_mdct(float *buffer, int n, vorb *f, int blocktype) +{ + mdct_lookup *M; + if (M1.n == n) M = &M1; + else if (M2.n == n) M = &M2; + else if (M1.n == 0) { mdct_init(&M1, n); M = &M1; } + else { + if (M2.n) __asm int 3; + mdct_init(&M2, n); + M = &M2; + } + + mdct_backward(M, buffer, buffer); +} +#endif + + +// the following were split out into separate functions while optimizing; +// they could be pushed back up but eh. __forceinline showed no change; +// they're probably already being inlined. +static void imdct_step3_iter0_loop(int n, float *e, int i_off, int k_off, float *A) +{ + float *ee0 = e + i_off; + float *ee2 = ee0 + k_off; + int i; + + assert((n & 3) == 0); + for (i=(n>>2); i > 0; --i) { + float k00_20, k01_21; + k00_20 = ee0[ 0] - ee2[ 0]; + k01_21 = ee0[-1] - ee2[-1]; + ee0[ 0] += ee2[ 0];//ee0[ 0] = ee0[ 0] + ee2[ 0]; + ee0[-1] += ee2[-1];//ee0[-1] = ee0[-1] + ee2[-1]; + ee2[ 0] = k00_20 * A[0] - k01_21 * A[1]; + ee2[-1] = k01_21 * A[0] + k00_20 * A[1]; + A += 8; + + k00_20 = ee0[-2] - ee2[-2]; + k01_21 = ee0[-3] - ee2[-3]; + ee0[-2] += ee2[-2];//ee0[-2] = ee0[-2] + ee2[-2]; + ee0[-3] += ee2[-3];//ee0[-3] = ee0[-3] + ee2[-3]; + ee2[-2] = k00_20 * A[0] - k01_21 * A[1]; + ee2[-3] = k01_21 * A[0] + k00_20 * A[1]; + A += 8; + + k00_20 = ee0[-4] - ee2[-4]; + k01_21 = ee0[-5] - ee2[-5]; + ee0[-4] += ee2[-4];//ee0[-4] = ee0[-4] + ee2[-4]; + ee0[-5] += ee2[-5];//ee0[-5] = ee0[-5] + ee2[-5]; + ee2[-4] = k00_20 * A[0] - k01_21 * A[1]; + ee2[-5] = k01_21 * A[0] + k00_20 * A[1]; + A += 8; + + k00_20 = ee0[-6] - ee2[-6]; + k01_21 = ee0[-7] - ee2[-7]; + ee0[-6] += ee2[-6];//ee0[-6] = ee0[-6] + ee2[-6]; + ee0[-7] += ee2[-7];//ee0[-7] = ee0[-7] + ee2[-7]; + ee2[-6] = k00_20 * A[0] - k01_21 * A[1]; + ee2[-7] = k01_21 * A[0] + k00_20 * A[1]; + A += 8; + ee0 -= 8; + ee2 -= 8; + } +} + +static void imdct_step3_inner_r_loop(int lim, float *e, int d0, int k_off, float *A, int k1) +{ + int i; + float k00_20, k01_21; + + float *e0 = e + d0; + float *e2 = e0 + k_off; + + for (i=lim >> 2; i > 0; --i) { + k00_20 = e0[-0] - e2[-0]; + k01_21 = e0[-1] - e2[-1]; + e0[-0] += e2[-0];//e0[-0] = e0[-0] + e2[-0]; + e0[-1] += e2[-1];//e0[-1] = e0[-1] + e2[-1]; + e2[-0] = (k00_20)*A[0] - (k01_21) * A[1]; + e2[-1] = (k01_21)*A[0] + (k00_20) * A[1]; + + A += k1; + + k00_20 = e0[-2] - e2[-2]; + k01_21 = e0[-3] - e2[-3]; + e0[-2] += e2[-2];//e0[-2] = e0[-2] + e2[-2]; + e0[-3] += e2[-3];//e0[-3] = e0[-3] + e2[-3]; + e2[-2] = (k00_20)*A[0] - (k01_21) * A[1]; + e2[-3] = (k01_21)*A[0] + (k00_20) * A[1]; + + A += k1; + + k00_20 = e0[-4] - e2[-4]; + k01_21 = e0[-5] - e2[-5]; + e0[-4] += e2[-4];//e0[-4] = e0[-4] + e2[-4]; + e0[-5] += e2[-5];//e0[-5] = e0[-5] + e2[-5]; + e2[-4] = (k00_20)*A[0] - (k01_21) * A[1]; + e2[-5] = (k01_21)*A[0] + (k00_20) * A[1]; + + A += k1; + + k00_20 = e0[-6] - e2[-6]; + k01_21 = e0[-7] - e2[-7]; + e0[-6] += e2[-6];//e0[-6] = e0[-6] + e2[-6]; + e0[-7] += e2[-7];//e0[-7] = e0[-7] + e2[-7]; + e2[-6] = (k00_20)*A[0] - (k01_21) * A[1]; + e2[-7] = (k01_21)*A[0] + (k00_20) * A[1]; + + e0 -= 8; + e2 -= 8; + + A += k1; + } +} + +static void imdct_step3_inner_s_loop(int n, float *e, int i_off, int k_off, float *A, int a_off, int k0) +{ + int i; + float A0 = A[0]; + float A1 = A[0+1]; + float A2 = A[0+a_off]; + float A3 = A[0+a_off+1]; + float A4 = A[0+a_off*2+0]; + float A5 = A[0+a_off*2+1]; + float A6 = A[0+a_off*3+0]; + float A7 = A[0+a_off*3+1]; + + float k00,k11; + + float *ee0 = e +i_off; + float *ee2 = ee0+k_off; + + for (i=n; i > 0; --i) { + k00 = ee0[ 0] - ee2[ 0]; + k11 = ee0[-1] - ee2[-1]; + ee0[ 0] = ee0[ 0] + ee2[ 0]; + ee0[-1] = ee0[-1] + ee2[-1]; + ee2[ 0] = (k00) * A0 - (k11) * A1; + ee2[-1] = (k11) * A0 + (k00) * A1; + + k00 = ee0[-2] - ee2[-2]; + k11 = ee0[-3] - ee2[-3]; + ee0[-2] = ee0[-2] + ee2[-2]; + ee0[-3] = ee0[-3] + ee2[-3]; + ee2[-2] = (k00) * A2 - (k11) * A3; + ee2[-3] = (k11) * A2 + (k00) * A3; + + k00 = ee0[-4] - ee2[-4]; + k11 = ee0[-5] - ee2[-5]; + ee0[-4] = ee0[-4] + ee2[-4]; + ee0[-5] = ee0[-5] + ee2[-5]; + ee2[-4] = (k00) * A4 - (k11) * A5; + ee2[-5] = (k11) * A4 + (k00) * A5; + + k00 = ee0[-6] - ee2[-6]; + k11 = ee0[-7] - ee2[-7]; + ee0[-6] = ee0[-6] + ee2[-6]; + ee0[-7] = ee0[-7] + ee2[-7]; + ee2[-6] = (k00) * A6 - (k11) * A7; + ee2[-7] = (k11) * A6 + (k00) * A7; + + ee0 -= k0; + ee2 -= k0; + } +} + +static __forceinline void iter_54(float *z) +{ + float k00,k11,k22,k33; + float y0,y1,y2,y3; + + k00 = z[ 0] - z[-4]; + y0 = z[ 0] + z[-4]; + y2 = z[-2] + z[-6]; + k22 = z[-2] - z[-6]; + + z[-0] = y0 + y2; // z0 + z4 + z2 + z6 + z[-2] = y0 - y2; // z0 + z4 - z2 - z6 + + // done with y0,y2 + + k33 = z[-3] - z[-7]; + + z[-4] = k00 + k33; // z0 - z4 + z3 - z7 + z[-6] = k00 - k33; // z0 - z4 - z3 + z7 + + // done with k33 + + k11 = z[-1] - z[-5]; + y1 = z[-1] + z[-5]; + y3 = z[-3] + z[-7]; + + z[-1] = y1 + y3; // z1 + z5 + z3 + z7 + z[-3] = y1 - y3; // z1 + z5 - z3 - z7 + z[-5] = k11 - k22; // z1 - z5 + z2 - z6 + z[-7] = k11 + k22; // z1 - z5 - z2 + z6 +} + +static void imdct_step3_inner_s_loop_ld654(int n, float *e, int i_off, float *A, int base_n) +{ + int a_off = base_n >> 3; + float A2 = A[0+a_off]; + float *z = e + i_off; + float *base = z - 16 * n; + + while (z > base) { + float k00,k11; + + k00 = z[-0] - z[-8]; + k11 = z[-1] - z[-9]; + z[-0] = z[-0] + z[-8]; + z[-1] = z[-1] + z[-9]; + z[-8] = k00; + z[-9] = k11 ; + + k00 = z[ -2] - z[-10]; + k11 = z[ -3] - z[-11]; + z[ -2] = z[ -2] + z[-10]; + z[ -3] = z[ -3] + z[-11]; + z[-10] = (k00+k11) * A2; + z[-11] = (k11-k00) * A2; + + k00 = z[-12] - z[ -4]; // reverse to avoid a unary negation + k11 = z[ -5] - z[-13]; + z[ -4] = z[ -4] + z[-12]; + z[ -5] = z[ -5] + z[-13]; + z[-12] = k11; + z[-13] = k00; + + k00 = z[-14] - z[ -6]; // reverse to avoid a unary negation + k11 = z[ -7] - z[-15]; + z[ -6] = z[ -6] + z[-14]; + z[ -7] = z[ -7] + z[-15]; + z[-14] = (k00+k11) * A2; + z[-15] = (k00-k11) * A2; + + iter_54(z); + iter_54(z-8); + z -= 16; + } +} + +static void inverse_mdct(float *buffer, int n, vorb *f, int blocktype) +{ + int n2 = n >> 1, n4 = n >> 2, n8 = n >> 3, l; + int ld; + // @OPTIMIZE: reduce register pressure by using fewer variables? + int save_point = temp_alloc_save(f); + float *buf2 = (float *) temp_alloc(f, n2 * sizeof(*buf2)); + float *u=NULL,*v=NULL; + // twiddle factors + float *A = f->A[blocktype]; + + // IMDCT algorithm from "The use of multirate filter banks for coding of high quality digital audio" + // See notes about bugs in that paper in less-optimal implementation 'inverse_mdct_old' after this function. + + // kernel from paper + + + // merged: + // copy and reflect spectral data + // step 0 + + // note that it turns out that the items added together during + // this step are, in fact, being added to themselves (as reflected + // by step 0). inexplicable inefficiency! this became obvious + // once I combined the passes. + + // so there's a missing 'times 2' here (for adding X to itself). + // this propagates through linearly to the end, where the numbers + // are 1/2 too small, and need to be compensated for. + + { + float *d,*e, *AA, *e_stop; + d = &buf2[n2-2]; + AA = A; + e = &buffer[0]; + e_stop = &buffer[n2]; + while (e != e_stop) { + d[1] = (e[0] * AA[0] - e[2]*AA[1]); + d[0] = (e[0] * AA[1] + e[2]*AA[0]); + d -= 2; + AA += 2; + e += 4; + } + + e = &buffer[n2-3]; + while (d >= buf2) { + d[1] = (-e[2] * AA[0] - -e[0]*AA[1]); + d[0] = (-e[2] * AA[1] + -e[0]*AA[0]); + d -= 2; + AA += 2; + e -= 4; + } + } + + // now we use symbolic names for these, so that we can + // possibly swap their meaning as we change which operations + // are in place + + u = buffer; + v = buf2; + + // step 2 (paper output is w, now u) + // this could be in place, but the data ends up in the wrong + // place... _somebody_'s got to swap it, so this is nominated + { + float *AA = &A[n2-8]; + float *d0,*d1, *e0, *e1; + + e0 = &v[n4]; + e1 = &v[0]; + + d0 = &u[n4]; + d1 = &u[0]; + + while (AA >= A) { + float v40_20, v41_21; + + v41_21 = e0[1] - e1[1]; + v40_20 = e0[0] - e1[0]; + d0[1] = e0[1] + e1[1]; + d0[0] = e0[0] + e1[0]; + d1[1] = v41_21*AA[4] - v40_20*AA[5]; + d1[0] = v40_20*AA[4] + v41_21*AA[5]; + + v41_21 = e0[3] - e1[3]; + v40_20 = e0[2] - e1[2]; + d0[3] = e0[3] + e1[3]; + d0[2] = e0[2] + e1[2]; + d1[3] = v41_21*AA[0] - v40_20*AA[1]; + d1[2] = v40_20*AA[0] + v41_21*AA[1]; + + AA -= 8; + + d0 += 4; + d1 += 4; + e0 += 4; + e1 += 4; + } + } + + // step 3 + ld = ilog(n) - 1; // ilog is off-by-one from normal definitions + + // optimized step 3: + + // the original step3 loop can be nested r inside s or s inside r; + // it's written originally as s inside r, but this is dumb when r + // iterates many times, and s few. So I have two copies of it and + // switch between them halfway. + + // this is iteration 0 of step 3 + imdct_step3_iter0_loop(n >> 4, u, n2-1-n4*0, -(n >> 3), A); + imdct_step3_iter0_loop(n >> 4, u, n2-1-n4*1, -(n >> 3), A); + + // this is iteration 1 of step 3 + imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*0, -(n >> 4), A, 16); + imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*1, -(n >> 4), A, 16); + imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*2, -(n >> 4), A, 16); + imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*3, -(n >> 4), A, 16); + + l=2; + for (; l < (ld-3)>>1; ++l) { + int k0 = n >> (l+2), k0_2 = k0>>1; + int lim = 1 << (l+1); + int i; + for (i=0; i < lim; ++i) + imdct_step3_inner_r_loop(n >> (l+4), u, n2-1 - k0*i, -k0_2, A, 1 << (l+3)); + } + + for (; l < ld-6; ++l) { + int k0 = n >> (l+2), k1 = 1 << (l+3), k0_2 = k0>>1; + int rlim = n >> (l+6), r; + int lim = 1 << (l+1); + int i_off; + float *A0 = A; + i_off = n2-1; + for (r=rlim; r > 0; --r) { + imdct_step3_inner_s_loop(lim, u, i_off, -k0_2, A0, k1, k0); + A0 += k1*4; + i_off -= 8; + } + } + + // iterations with count: + // ld-6,-5,-4 all interleaved together + // the big win comes from getting rid of needless flops + // due to the constants on pass 5 & 4 being all 1 and 0; + // combining them to be simultaneous to improve cache made little difference + imdct_step3_inner_s_loop_ld654(n >> 5, u, n2-1, A, n); + + // output is u + + // step 4, 5, and 6 + // cannot be in-place because of step 5 + { + uint16 *bitrev = f->bit_reverse[blocktype]; + // weirdly, I'd have thought reading sequentially and writing + // erratically would have been better than vice-versa, but in + // fact that's not what my testing showed. (That is, with + // j = bitreverse(i), do you read i and write j, or read j and write i.) + + float *d0 = &v[n4-4]; + float *d1 = &v[n2-4]; + while (d0 >= v) { + int k4; + + k4 = bitrev[0]; + d1[3] = u[k4+0]; + d1[2] = u[k4+1]; + d0[3] = u[k4+2]; + d0[2] = u[k4+3]; + + k4 = bitrev[1]; + d1[1] = u[k4+0]; + d1[0] = u[k4+1]; + d0[1] = u[k4+2]; + d0[0] = u[k4+3]; + + d0 -= 4; + d1 -= 4; + bitrev += 2; + } + } + // (paper output is u, now v) + + + // data must be in buf2 + assert(v == buf2); + + // step 7 (paper output is v, now v) + // this is now in place + { + float *C = f->C[blocktype]; + float *d, *e; + + d = v; + e = v + n2 - 4; + + while (d < e) { + float a02,a11,b0,b1,b2,b3; + + a02 = d[0] - e[2]; + a11 = d[1] + e[3]; + + b0 = C[1]*a02 + C[0]*a11; + b1 = C[1]*a11 - C[0]*a02; + + b2 = d[0] + e[ 2]; + b3 = d[1] - e[ 3]; + + d[0] = b2 + b0; + d[1] = b3 + b1; + e[2] = b2 - b0; + e[3] = b1 - b3; + + a02 = d[2] - e[0]; + a11 = d[3] + e[1]; + + b0 = C[3]*a02 + C[2]*a11; + b1 = C[3]*a11 - C[2]*a02; + + b2 = d[2] + e[ 0]; + b3 = d[3] - e[ 1]; + + d[2] = b2 + b0; + d[3] = b3 + b1; + e[0] = b2 - b0; + e[1] = b1 - b3; + + C += 4; + d += 4; + e -= 4; + } + } + + // data must be in buf2 + + + // step 8+decode (paper output is X, now buffer) + // this generates pairs of data a la 8 and pushes them directly through + // the decode kernel (pushing rather than pulling) to avoid having + // to make another pass later + + // this cannot POSSIBLY be in place, so we refer to the buffers directly + + { + float *d0,*d1,*d2,*d3; + + float *B = f->B[blocktype] + n2 - 8; + float *e = buf2 + n2 - 8; + d0 = &buffer[0]; + d1 = &buffer[n2-4]; + d2 = &buffer[n2]; + d3 = &buffer[n-4]; + while (e >= v) { + float p0,p1,p2,p3; + + p3 = e[6]*B[7] - e[7]*B[6]; + p2 = -e[6]*B[6] - e[7]*B[7]; + + d0[0] = p3; + d1[3] = - p3; + d2[0] = p2; + d3[3] = p2; + + p1 = e[4]*B[5] - e[5]*B[4]; + p0 = -e[4]*B[4] - e[5]*B[5]; + + d0[1] = p1; + d1[2] = - p1; + d2[1] = p0; + d3[2] = p0; + + p3 = e[2]*B[3] - e[3]*B[2]; + p2 = -e[2]*B[2] - e[3]*B[3]; + + d0[2] = p3; + d1[1] = - p3; + d2[2] = p2; + d3[1] = p2; + + p1 = e[0]*B[1] - e[1]*B[0]; + p0 = -e[0]*B[0] - e[1]*B[1]; + + d0[3] = p1; + d1[0] = - p1; + d2[3] = p0; + d3[0] = p0; + + B -= 8; + e -= 8; + d0 += 4; + d2 += 4; + d1 -= 4; + d3 -= 4; + } + } + + temp_free(f,buf2); + temp_alloc_restore(f,save_point); +} + +#if 0 +// this is the original version of the above code, if you want to optimize it from scratch +void inverse_mdct_naive(float *buffer, int n) +{ + float s; + float A[1 << 12], B[1 << 12], C[1 << 11]; + int i,k,k2,k4, n2 = n >> 1, n4 = n >> 2, n8 = n >> 3, l; + int n3_4 = n - n4, ld; + // how can they claim this only uses N words?! + // oh, because they're only used sparsely, whoops + float u[1 << 13], X[1 << 13], v[1 << 13], w[1 << 13]; + // set up twiddle factors + + for (k=k2=0; k < n4; ++k,k2+=2) { + A[k2 ] = (float) cos(4*k*M_PI/n); + A[k2+1] = (float) -sin(4*k*M_PI/n); + B[k2 ] = (float) cos((k2+1)*M_PI/n/2); + B[k2+1] = (float) sin((k2+1)*M_PI/n/2); + } + for (k=k2=0; k < n8; ++k,k2+=2) { + C[k2 ] = (float) cos(2*(k2+1)*M_PI/n); + C[k2+1] = (float) -sin(2*(k2+1)*M_PI/n); + } + + // IMDCT algorithm from "The use of multirate filter banks for coding of high quality digital audio" + // Note there are bugs in that pseudocode, presumably due to them attempting + // to rename the arrays nicely rather than representing the way their actual + // implementation bounces buffers back and forth. As a result, even in the + // "some formulars corrected" version, a direct implementation fails. These + // are noted below as "paper bug". + + // copy and reflect spectral data + for (k=0; k < n2; ++k) u[k] = buffer[k]; + for ( ; k < n ; ++k) u[k] = -buffer[n - k - 1]; + // kernel from paper + // step 1 + for (k=k2=k4=0; k < n4; k+=1, k2+=2, k4+=4) { + v[n-k4-1] = (u[k4] - u[n-k4-1]) * A[k2] - (u[k4+2] - u[n-k4-3])*A[k2+1]; + v[n-k4-3] = (u[k4] - u[n-k4-1]) * A[k2+1] + (u[k4+2] - u[n-k4-3])*A[k2]; + } + // step 2 + for (k=k4=0; k < n8; k+=1, k4+=4) { + w[n2+3+k4] = v[n2+3+k4] + v[k4+3]; + w[n2+1+k4] = v[n2+1+k4] + v[k4+1]; + w[k4+3] = (v[n2+3+k4] - v[k4+3])*A[n2-4-k4] - (v[n2+1+k4]-v[k4+1])*A[n2-3-k4]; + w[k4+1] = (v[n2+1+k4] - v[k4+1])*A[n2-4-k4] + (v[n2+3+k4]-v[k4+3])*A[n2-3-k4]; + } + // step 3 + ld = ilog(n) - 1; // ilog is off-by-one from normal definitions + for (l=0; l < ld-3; ++l) { + int k0 = n >> (l+2), k1 = 1 << (l+3); + int rlim = n >> (l+4), r4, r; + int s2lim = 1 << (l+2), s2; + for (r=r4=0; r < rlim; r4+=4,++r) { + for (s2=0; s2 < s2lim; s2+=2) { + u[n-1-k0*s2-r4] = w[n-1-k0*s2-r4] + w[n-1-k0*(s2+1)-r4]; + u[n-3-k0*s2-r4] = w[n-3-k0*s2-r4] + w[n-3-k0*(s2+1)-r4]; + u[n-1-k0*(s2+1)-r4] = (w[n-1-k0*s2-r4] - w[n-1-k0*(s2+1)-r4]) * A[r*k1] + - (w[n-3-k0*s2-r4] - w[n-3-k0*(s2+1)-r4]) * A[r*k1+1]; + u[n-3-k0*(s2+1)-r4] = (w[n-3-k0*s2-r4] - w[n-3-k0*(s2+1)-r4]) * A[r*k1] + + (w[n-1-k0*s2-r4] - w[n-1-k0*(s2+1)-r4]) * A[r*k1+1]; + } + } + if (l+1 < ld-3) { + // paper bug: ping-ponging of u&w here is omitted + memcpy(w, u, sizeof(u)); + } + } + + // step 4 + for (i=0; i < n8; ++i) { + int j = bit_reverse(i) >> (32-ld+3); + assert(j < n8); + if (i == j) { + // paper bug: original code probably swapped in place; if copying, + // need to directly copy in this case + int i8 = i << 3; + v[i8+1] = u[i8+1]; + v[i8+3] = u[i8+3]; + v[i8+5] = u[i8+5]; + v[i8+7] = u[i8+7]; + } else if (i < j) { + int i8 = i << 3, j8 = j << 3; + v[j8+1] = u[i8+1], v[i8+1] = u[j8 + 1]; + v[j8+3] = u[i8+3], v[i8+3] = u[j8 + 3]; + v[j8+5] = u[i8+5], v[i8+5] = u[j8 + 5]; + v[j8+7] = u[i8+7], v[i8+7] = u[j8 + 7]; + } + } + // step 5 + for (k=0; k < n2; ++k) { + w[k] = v[k*2+1]; + } + // step 6 + for (k=k2=k4=0; k < n8; ++k, k2 += 2, k4 += 4) { + u[n-1-k2] = w[k4]; + u[n-2-k2] = w[k4+1]; + u[n3_4 - 1 - k2] = w[k4+2]; + u[n3_4 - 2 - k2] = w[k4+3]; + } + // step 7 + for (k=k2=0; k < n8; ++k, k2 += 2) { + v[n2 + k2 ] = ( u[n2 + k2] + u[n-2-k2] + C[k2+1]*(u[n2+k2]-u[n-2-k2]) + C[k2]*(u[n2+k2+1]+u[n-2-k2+1]))/2; + v[n-2 - k2] = ( u[n2 + k2] + u[n-2-k2] - C[k2+1]*(u[n2+k2]-u[n-2-k2]) - C[k2]*(u[n2+k2+1]+u[n-2-k2+1]))/2; + v[n2+1+ k2] = ( u[n2+1+k2] - u[n-1-k2] + C[k2+1]*(u[n2+1+k2]+u[n-1-k2]) - C[k2]*(u[n2+k2]-u[n-2-k2]))/2; + v[n-1 - k2] = (-u[n2+1+k2] + u[n-1-k2] + C[k2+1]*(u[n2+1+k2]+u[n-1-k2]) - C[k2]*(u[n2+k2]-u[n-2-k2]))/2; + } + // step 8 + for (k=k2=0; k < n4; ++k,k2 += 2) { + X[k] = v[k2+n2]*B[k2 ] + v[k2+1+n2]*B[k2+1]; + X[n2-1-k] = v[k2+n2]*B[k2+1] - v[k2+1+n2]*B[k2 ]; + } + + // decode kernel to output + // determined the following value experimentally + // (by first figuring out what made inverse_mdct_slow work); then matching that here + // (probably vorbis encoder premultiplies by n or n/2, to save it on the decoder?) + s = 0.5; // theoretically would be n4 + + // [[[ note! the s value of 0.5 is compensated for by the B[] in the current code, + // so it needs to use the "old" B values to behave correctly, or else + // set s to 1.0 ]]] + for (i=0; i < n4 ; ++i) buffer[i] = s * X[i+n4]; + for ( ; i < n3_4; ++i) buffer[i] = -s * X[n3_4 - i - 1]; + for ( ; i < n ; ++i) buffer[i] = -s * X[i - n3_4]; +} +#endif + +static float *get_window(vorb *f, int len) +{ + len <<= 1; + if (len == f->blocksize_0) return f->window[0]; + if (len == f->blocksize_1) return f->window[1]; + return NULL; +} + +#ifndef STB_VORBIS_NO_DEFER_FLOOR +typedef int16 YTYPE; +#else +typedef int YTYPE; +#endif +static int do_floor(vorb *f, Mapping *map, int i, int n, float *target, YTYPE *finalY, uint8 *step2_flag) +{ + int n2 = n >> 1; + int s = map->chan[i].mux, floor; + floor = map->submap_floor[s]; + if (f->floor_types[floor] == 0) { + return error(f, VORBIS_invalid_stream); + } else { + Floor1 *g = &f->floor_config[floor].floor1; + int j,q; + int lx = 0, ly = finalY[0] * g->floor1_multiplier; + for (q=1; q < g->values; ++q) { + j = g->sorted_order[q]; + #ifndef STB_VORBIS_NO_DEFER_FLOOR + if (finalY[j] >= 0) + #else + if (step2_flag[j]) + #endif + { + int hy = finalY[j] * g->floor1_multiplier; + int hx = g->Xlist[j]; + if (lx != hx) + draw_line(target, lx,ly, hx,hy, n2); + CHECK(f); + lx = hx, ly = hy; + } + } + if (lx < n2) { + // optimization of: draw_line(target, lx,ly, n,ly, n2); + for (j=lx; j < n2; ++j) + LINE_OP(target[j], inverse_db_table[ly]); + CHECK(f); + } + } + return TRUE; +} + +// The meaning of "left" and "right" +// +// For a given frame: +// we compute samples from 0..n +// window_center is n/2 +// we'll window and mix the samples from left_start to left_end with data from the previous frame +// all of the samples from left_end to right_start can be output without mixing; however, +// this interval is 0-length except when transitioning between short and long frames +// all of the samples from right_start to right_end need to be mixed with the next frame, +// which we don't have, so those get saved in a buffer +// frame N's right_end-right_start, the number of samples to mix with the next frame, +// has to be the same as frame N+1's left_end-left_start (which they are by +// construction) + +static int vorbis_decode_initial(vorb *f, int *p_left_start, int *p_left_end, int *p_right_start, int *p_right_end, int *mode) +{ + Mode *m; + int i, n, prev, next, window_center; + f->channel_buffer_start = f->channel_buffer_end = 0; + + retry: + if (f->eof) return FALSE; + if (!maybe_start_packet(f)) + return FALSE; + // check packet type + if (get_bits(f,1) != 0) { + if (IS_PUSH_MODE(f)) + return error(f,VORBIS_bad_packet_type); + while (EOP != get8_packet(f)); + goto retry; + } + + if (f->alloc.alloc_buffer) + assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); + + i = get_bits(f, ilog(f->mode_count-1)); + if (i == EOP) return FALSE; + if (i >= f->mode_count) return FALSE; + *mode = i; + m = f->mode_config + i; + if (m->blockflag) { + n = f->blocksize_1; + prev = get_bits(f,1); + next = get_bits(f,1); + } else { + prev = next = 0; + n = f->blocksize_0; + } + +// WINDOWING + + window_center = n >> 1; + if (m->blockflag && !prev) { + *p_left_start = (n - f->blocksize_0) >> 2; + *p_left_end = (n + f->blocksize_0) >> 2; + } else { + *p_left_start = 0; + *p_left_end = window_center; + } + if (m->blockflag && !next) { + *p_right_start = (n*3 - f->blocksize_0) >> 2; + *p_right_end = (n*3 + f->blocksize_0) >> 2; + } else { + *p_right_start = window_center; + *p_right_end = n; + } + + return TRUE; +} + +static int vorbis_decode_packet_rest(vorb *f, int *len, Mode *m, int left_start, int left_end, int right_start, int right_end, int *p_left) +{ + Mapping *map; + int i,j,k,n,n2; + int zero_channel[256]; + int really_zero_channel[256]; + +// WINDOWING + + n = f->blocksize[m->blockflag]; + map = &f->mapping[m->mapping]; + +// FLOORS + n2 = n >> 1; + + CHECK(f); + + for (i=0; i < f->channels; ++i) { + int s = map->chan[i].mux, floor; + zero_channel[i] = FALSE; + floor = map->submap_floor[s]; + if (f->floor_types[floor] == 0) { + return error(f, VORBIS_invalid_stream); + } else { + Floor1 *g = &f->floor_config[floor].floor1; + if (get_bits(f, 1)) { + short *finalY; + uint8 step2_flag[256]; + static int range_list[4] = { 256, 128, 86, 64 }; + int range = range_list[g->floor1_multiplier-1]; + int offset = 2; + finalY = f->finalY[i]; + finalY[0] = get_bits(f, ilog(range)-1); + finalY[1] = get_bits(f, ilog(range)-1); + for (j=0; j < g->partitions; ++j) { + int pclass = g->partition_class_list[j]; + int cdim = g->class_dimensions[pclass]; + int cbits = g->class_subclasses[pclass]; + int csub = (1 << cbits)-1; + int cval = 0; + if (cbits) { + Codebook *c = f->codebooks + g->class_masterbooks[pclass]; + DECODE(cval,f,c); + } + for (k=0; k < cdim; ++k) { + int book = g->subclass_books[pclass][cval & csub]; + cval = cval >> cbits; + if (book >= 0) { + int temp; + Codebook *c = f->codebooks + book; + DECODE(temp,f,c); + finalY[offset++] = temp; + } else + finalY[offset++] = 0; + } + } + if (f->valid_bits == INVALID_BITS) goto error; // behavior according to spec + step2_flag[0] = step2_flag[1] = 1; + for (j=2; j < g->values; ++j) { + int low, high, pred, highroom, lowroom, room, val; + low = g->neighbors[j][0]; + high = g->neighbors[j][1]; + //neighbors(g->Xlist, j, &low, &high); + pred = predict_point(g->Xlist[j], g->Xlist[low], g->Xlist[high], finalY[low], finalY[high]); + val = finalY[j]; + highroom = range - pred; + lowroom = pred; + if (highroom < lowroom) + room = highroom * 2; + else + room = lowroom * 2; + if (val) { + step2_flag[low] = step2_flag[high] = 1; + step2_flag[j] = 1; + if (val >= room) + if (highroom > lowroom) + finalY[j] = val - lowroom + pred; + else + finalY[j] = pred - val + highroom - 1; + else + if (val & 1) + finalY[j] = pred - ((val+1)>>1); + else + finalY[j] = pred + (val>>1); + } else { + step2_flag[j] = 0; + finalY[j] = pred; + } + } + +#ifdef STB_VORBIS_NO_DEFER_FLOOR + do_floor(f, map, i, n, f->floor_buffers[i], finalY, step2_flag); +#else + // defer final floor computation until _after_ residue + for (j=0; j < g->values; ++j) { + if (!step2_flag[j]) + finalY[j] = -1; + } +#endif + } else { + error: + zero_channel[i] = TRUE; + } + // So we just defer everything else to later + + // at this point we've decoded the floor into buffer + } + } + CHECK(f); + // at this point we've decoded all floors + + if (f->alloc.alloc_buffer) + assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); + + // re-enable coupled channels if necessary + memcpy(really_zero_channel, zero_channel, sizeof(really_zero_channel[0]) * f->channels); + for (i=0; i < map->coupling_steps; ++i) + if (!zero_channel[map->chan[i].magnitude] || !zero_channel[map->chan[i].angle]) { + zero_channel[map->chan[i].magnitude] = zero_channel[map->chan[i].angle] = FALSE; + } + + CHECK(f); +// RESIDUE DECODE + for (i=0; i < map->submaps; ++i) { + float *residue_buffers[STB_VORBIS_MAX_CHANNELS]; + int r; + uint8 do_not_decode[256]; + int ch = 0; + for (j=0; j < f->channels; ++j) { + if (map->chan[j].mux == i) { + if (zero_channel[j]) { + do_not_decode[ch] = TRUE; + residue_buffers[ch] = NULL; + } else { + do_not_decode[ch] = FALSE; + residue_buffers[ch] = f->channel_buffers[j]; + } + ++ch; + } + } + r = map->submap_residue[i]; + decode_residue(f, residue_buffers, ch, n2, r, do_not_decode); + } + + if (f->alloc.alloc_buffer) + assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); + CHECK(f); + +// INVERSE COUPLING + for (i = map->coupling_steps-1; i >= 0; --i) { + int n2 = n >> 1; + float *m = f->channel_buffers[map->chan[i].magnitude]; + float *a = f->channel_buffers[map->chan[i].angle ]; + for (j=0; j < n2; ++j) { + float a2,m2; + if (m[j] > 0) + if (a[j] > 0) + m2 = m[j], a2 = m[j] - a[j]; + else + a2 = m[j], m2 = m[j] + a[j]; + else + if (a[j] > 0) + m2 = m[j], a2 = m[j] + a[j]; + else + a2 = m[j], m2 = m[j] - a[j]; + m[j] = m2; + a[j] = a2; + } + } + CHECK(f); + + // finish decoding the floors +#ifndef STB_VORBIS_NO_DEFER_FLOOR + for (i=0; i < f->channels; ++i) { + if (really_zero_channel[i]) { + memset(f->channel_buffers[i], 0, sizeof(*f->channel_buffers[i]) * n2); + } else { + do_floor(f, map, i, n, f->channel_buffers[i], f->finalY[i], NULL); + } + } +#else + for (i=0; i < f->channels; ++i) { + if (really_zero_channel[i]) { + memset(f->channel_buffers[i], 0, sizeof(*f->channel_buffers[i]) * n2); + } else { + for (j=0; j < n2; ++j) + f->channel_buffers[i][j] *= f->floor_buffers[i][j]; + } + } +#endif + +// INVERSE MDCT + CHECK(f); + for (i=0; i < f->channels; ++i) + inverse_mdct(f->channel_buffers[i], n, f, m->blockflag); + CHECK(f); + + // this shouldn't be necessary, unless we exited on an error + // and want to flush to get to the next packet + flush_packet(f); + + if (f->first_decode) { + // assume we start so first non-discarded sample is sample 0 + // this isn't to spec, but spec would require us to read ahead + // and decode the size of all current frames--could be done, + // but presumably it's not a commonly used feature + f->current_loc = -n2; // start of first frame is positioned for discard + // we might have to discard samples "from" the next frame too, + // if we're lapping a large block then a small at the start? + f->discard_samples_deferred = n - right_end; + f->current_loc_valid = TRUE; + f->first_decode = FALSE; + } else if (f->discard_samples_deferred) { + if (f->discard_samples_deferred >= right_start - left_start) { + f->discard_samples_deferred -= (right_start - left_start); + left_start = right_start; + *p_left = left_start; + } else { + left_start += f->discard_samples_deferred; + *p_left = left_start; + f->discard_samples_deferred = 0; + } + } else if (f->previous_length == 0 && f->current_loc_valid) { + // we're recovering from a seek... that means we're going to discard + // the samples from this packet even though we know our position from + // the last page header, so we need to update the position based on + // the discarded samples here + // but wait, the code below is going to add this in itself even + // on a discard, so we don't need to do it here... + } + + // check if we have ogg information about the sample # for this packet + if (f->last_seg_which == f->end_seg_with_known_loc) { + // if we have a valid current loc, and this is final: + if (f->current_loc_valid && (f->page_flag & PAGEFLAG_last_page)) { + uint32 current_end = f->known_loc_for_packet; + // then let's infer the size of the (probably) short final frame + if (current_end < f->current_loc + (right_end-left_start)) { + if (current_end < f->current_loc) { + // negative truncation, that's impossible! + *len = 0; + } else { + *len = current_end - f->current_loc; + } + *len += left_start; // this doesn't seem right, but has no ill effect on my test files + if (*len > right_end) *len = right_end; // this should never happen + f->current_loc += *len; + return TRUE; + } + } + // otherwise, just set our sample loc + // guess that the ogg granule pos refers to the _middle_ of the + // last frame? + // set f->current_loc to the position of left_start + f->current_loc = f->known_loc_for_packet - (n2-left_start); + f->current_loc_valid = TRUE; + } + if (f->current_loc_valid) + f->current_loc += (right_start - left_start); + + if (f->alloc.alloc_buffer) + assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); + *len = right_end; // ignore samples after the window goes to 0 + CHECK(f); + + return TRUE; +} + +static int vorbis_decode_packet(vorb *f, int *len, int *p_left, int *p_right) +{ + int mode, left_end, right_end; + if (!vorbis_decode_initial(f, p_left, &left_end, p_right, &right_end, &mode)) return 0; + return vorbis_decode_packet_rest(f, len, f->mode_config + mode, *p_left, left_end, *p_right, right_end, p_left); +} + +static int vorbis_finish_frame(stb_vorbis *f, int len, int left, int right) +{ + int prev,i,j; + // we use right&left (the start of the right- and left-window sin()-regions) + // to determine how much to return, rather than inferring from the rules + // (same result, clearer code); 'left' indicates where our sin() window + // starts, therefore where the previous window's right edge starts, and + // therefore where to start mixing from the previous buffer. 'right' + // indicates where our sin() ending-window starts, therefore that's where + // we start saving, and where our returned-data ends. + + // mixin from previous window + if (f->previous_length) { + int i,j, n = f->previous_length; + float *w = get_window(f, n); + if (w == NULL) return 0; + for (i=0; i < f->channels; ++i) { + for (j=0; j < n; ++j) + f->channel_buffers[i][left+j] = + f->channel_buffers[i][left+j]*w[ j] + + f->previous_window[i][ j]*w[n-1-j]; + } + } + + prev = f->previous_length; + + // last half of this data becomes previous window + f->previous_length = len - right; + + // @OPTIMIZE: could avoid this copy by double-buffering the + // output (flipping previous_window with channel_buffers), but + // then previous_window would have to be 2x as large, and + // channel_buffers couldn't be temp mem (although they're NOT + // currently temp mem, they could be (unless we want to level + // performance by spreading out the computation)) + for (i=0; i < f->channels; ++i) + for (j=0; right+j < len; ++j) + f->previous_window[i][j] = f->channel_buffers[i][right+j]; + + if (!prev) + // there was no previous packet, so this data isn't valid... + // this isn't entirely true, only the would-have-overlapped data + // isn't valid, but this seems to be what the spec requires + return 0; + + // truncate a short frame + if (len < right) right = len; + + f->samples_output += right-left; + + return right - left; +} + +static int vorbis_pump_first_frame(stb_vorbis *f) +{ + int len, right, left, res; + res = vorbis_decode_packet(f, &len, &left, &right); + if (res) + vorbis_finish_frame(f, len, left, right); + return res; +} + +#ifndef STB_VORBIS_NO_PUSHDATA_API +static int is_whole_packet_present(stb_vorbis *f) +{ + // make sure that we have the packet available before continuing... + // this requires a full ogg parse, but we know we can fetch from f->stream + + // instead of coding this out explicitly, we could save the current read state, + // read the next packet with get8() until end-of-packet, check f->eof, then + // reset the state? but that would be slower, esp. since we'd have over 256 bytes + // of state to restore (primarily the page segment table) + + int s = f->next_seg, first = TRUE; + uint8 *p = f->stream; + + if (s != -1) { // if we're not starting the packet with a 'continue on next page' flag + for (; s < f->segment_count; ++s) { + p += f->segments[s]; + if (f->segments[s] < 255) // stop at first short segment + break; + } + // either this continues, or it ends it... + if (s == f->segment_count) + s = -1; // set 'crosses page' flag + if (p > f->stream_end) return error(f, VORBIS_need_more_data); + first = FALSE; + } + for (; s == -1;) { + uint8 *q; + int n; + + // check that we have the page header ready + if (p + 26 >= f->stream_end) return error(f, VORBIS_need_more_data); + // validate the page + if (memcmp(p, ogg_page_header, 4)) return error(f, VORBIS_invalid_stream); + if (p[4] != 0) return error(f, VORBIS_invalid_stream); + if (first) { // the first segment must NOT have 'continued_packet', later ones MUST + if (f->previous_length) + if ((p[5] & PAGEFLAG_continued_packet)) return error(f, VORBIS_invalid_stream); + // if no previous length, we're resynching, so we can come in on a continued-packet, + // which we'll just drop + } else { + if (!(p[5] & PAGEFLAG_continued_packet)) return error(f, VORBIS_invalid_stream); + } + n = p[26]; // segment counts + q = p+27; // q points to segment table + p = q + n; // advance past header + // make sure we've read the segment table + if (p > f->stream_end) return error(f, VORBIS_need_more_data); + for (s=0; s < n; ++s) { + p += q[s]; + if (q[s] < 255) + break; + } + if (s == n) + s = -1; // set 'crosses page' flag + if (p > f->stream_end) return error(f, VORBIS_need_more_data); + first = FALSE; + } + return TRUE; +} +#endif // !STB_VORBIS_NO_PUSHDATA_API + +static int start_decoder(vorb *f) +{ + uint8 header[6], x,y; + int len,i,j,k, max_submaps = 0; + int longest_floorlist=0; + + // first page, first packet + f->first_decode = TRUE; + + if (!start_page(f)) return FALSE; + // validate page flag + if (!(f->page_flag & PAGEFLAG_first_page)) return error(f, VORBIS_invalid_first_page); + if (f->page_flag & PAGEFLAG_last_page) return error(f, VORBIS_invalid_first_page); + if (f->page_flag & PAGEFLAG_continued_packet) return error(f, VORBIS_invalid_first_page); + // check for expected packet length + if (f->segment_count != 1) return error(f, VORBIS_invalid_first_page); + if (f->segments[0] != 30) { + // check for the Ogg skeleton fishead identifying header to refine our error + if (f->segments[0] == 64 && + getn(f, header, 6) && + header[0] == 'f' && + header[1] == 'i' && + header[2] == 's' && + header[3] == 'h' && + header[4] == 'e' && + header[5] == 'a' && + get8(f) == 'd' && + get8(f) == '\0') return error(f, VORBIS_ogg_skeleton_not_supported); + else + return error(f, VORBIS_invalid_first_page); + } + + // read packet + // check packet header + if (get8(f) != VORBIS_packet_id) return error(f, VORBIS_invalid_first_page); + if (!getn(f, header, 6)) return error(f, VORBIS_unexpected_eof); + if (!vorbis_validate(header)) return error(f, VORBIS_invalid_first_page); + // vorbis_version + if (get32(f) != 0) return error(f, VORBIS_invalid_first_page); + f->channels = get8(f); if (!f->channels) return error(f, VORBIS_invalid_first_page); + if (f->channels > STB_VORBIS_MAX_CHANNELS) return error(f, VORBIS_too_many_channels); + f->sample_rate = get32(f); if (!f->sample_rate) return error(f, VORBIS_invalid_first_page); + get32(f); // bitrate_maximum + get32(f); // bitrate_nominal + get32(f); // bitrate_minimum + x = get8(f); + { + int log0,log1; + log0 = x & 15; + log1 = x >> 4; + f->blocksize_0 = 1 << log0; + f->blocksize_1 = 1 << log1; + if (log0 < 6 || log0 > 13) return error(f, VORBIS_invalid_setup); + if (log1 < 6 || log1 > 13) return error(f, VORBIS_invalid_setup); + if (log0 > log1) return error(f, VORBIS_invalid_setup); + } + + // framing_flag + x = get8(f); + if (!(x & 1)) return error(f, VORBIS_invalid_first_page); + + // second packet! + if (!start_page(f)) return FALSE; + + if (!start_packet(f)) return FALSE; + + if (!next_segment(f)) return FALSE; + + if (get8_packet(f) != VORBIS_packet_comment) return error(f, VORBIS_invalid_setup); + for (i=0; i < 6; ++i) header[i] = get8_packet(f); + if (!vorbis_validate(header)) return error(f, VORBIS_invalid_setup); + //file vendor + len = get32_packet(f); + f->vendor = (char*)setup_malloc(f, sizeof(char) * (len+1)); + if (f->vendor == NULL) return error(f, VORBIS_outofmem); + for(i=0; i < len; ++i) { + f->vendor[i] = get8_packet(f); + } + f->vendor[len] = (char)'\0'; + //user comments + f->comment_list_length = get32_packet(f); + f->comment_list = (char**)setup_malloc(f, sizeof(char*) * (f->comment_list_length)); + if (f->comment_list == NULL) return error(f, VORBIS_outofmem); + + for(i=0; i < f->comment_list_length; ++i) { + len = get32_packet(f); + f->comment_list[i] = (char*)setup_malloc(f, sizeof(char) * (len+1)); + if (f->comment_list[i] == NULL) return error(f, VORBIS_outofmem); + + for(j=0; j < len; ++j) { + f->comment_list[i][j] = get8_packet(f); + } + f->comment_list[i][len] = (char)'\0'; + } + + // framing_flag + x = get8_packet(f); + if (!(x & 1)) return error(f, VORBIS_invalid_setup); + + + skip(f, f->bytes_in_seg); + f->bytes_in_seg = 0; + + do { + len = next_segment(f); + skip(f, len); + f->bytes_in_seg = 0; + } while (len); + + // third packet! + if (!start_packet(f)) return FALSE; + + #ifndef STB_VORBIS_NO_PUSHDATA_API + if (IS_PUSH_MODE(f)) { + if (!is_whole_packet_present(f)) { + // convert error in ogg header to write type + if (f->error == VORBIS_invalid_stream) + f->error = VORBIS_invalid_setup; + return FALSE; + } + } + #endif + + crc32_init(); // always init it, to avoid multithread race conditions + + if (get8_packet(f) != VORBIS_packet_setup) return error(f, VORBIS_invalid_setup); + for (i=0; i < 6; ++i) header[i] = get8_packet(f); + if (!vorbis_validate(header)) return error(f, VORBIS_invalid_setup); + + // codebooks + + f->codebook_count = get_bits(f,8) + 1; + f->codebooks = (Codebook *) setup_malloc(f, sizeof(*f->codebooks) * f->codebook_count); + if (f->codebooks == NULL) return error(f, VORBIS_outofmem); + memset(f->codebooks, 0, sizeof(*f->codebooks) * f->codebook_count); + for (i=0; i < f->codebook_count; ++i) { + uint32 *values; + int ordered, sorted_count; + int total=0; + uint8 *lengths; + Codebook *c = f->codebooks+i; + CHECK(f); + x = get_bits(f, 8); if (x != 0x42) return error(f, VORBIS_invalid_setup); + x = get_bits(f, 8); if (x != 0x43) return error(f, VORBIS_invalid_setup); + x = get_bits(f, 8); if (x != 0x56) return error(f, VORBIS_invalid_setup); + x = get_bits(f, 8); + c->dimensions = (get_bits(f, 8)<<8) + x; + x = get_bits(f, 8); + y = get_bits(f, 8); + c->entries = (get_bits(f, 8)<<16) + (y<<8) + x; + ordered = get_bits(f,1); + c->sparse = ordered ? 0 : get_bits(f,1); + + if (c->dimensions == 0 && c->entries != 0) return error(f, VORBIS_invalid_setup); + + if (c->sparse) + lengths = (uint8 *) setup_temp_malloc(f, c->entries); + else + lengths = c->codeword_lengths = (uint8 *) setup_malloc(f, c->entries); + + if (!lengths) return error(f, VORBIS_outofmem); + + if (ordered) { + int current_entry = 0; + int current_length = get_bits(f,5) + 1; + while (current_entry < c->entries) { + int limit = c->entries - current_entry; + int n = get_bits(f, ilog(limit)); + if (current_length >= 32) return error(f, VORBIS_invalid_setup); + if (current_entry + n > (int) c->entries) { return error(f, VORBIS_invalid_setup); } + memset(lengths + current_entry, current_length, n); + current_entry += n; + ++current_length; + } + } else { + for (j=0; j < c->entries; ++j) { + int present = c->sparse ? get_bits(f,1) : 1; + if (present) { + lengths[j] = get_bits(f, 5) + 1; + ++total; + if (lengths[j] == 32) + return error(f, VORBIS_invalid_setup); + } else { + lengths[j] = NO_CODE; + } + } + } + + if (c->sparse && total >= c->entries >> 2) { + // convert sparse items to non-sparse! + if (c->entries > (int) f->setup_temp_memory_required) + f->setup_temp_memory_required = c->entries; + + c->codeword_lengths = (uint8 *) setup_malloc(f, c->entries); + if (c->codeword_lengths == NULL) return error(f, VORBIS_outofmem); + memcpy(c->codeword_lengths, lengths, c->entries); + setup_temp_free(f, lengths, c->entries); // note this is only safe if there have been no intervening temp mallocs! + lengths = c->codeword_lengths; + c->sparse = 0; + } + + // compute the size of the sorted tables + if (c->sparse) { + sorted_count = total; + } else { + sorted_count = 0; + #ifndef STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH + for (j=0; j < c->entries; ++j) + if (lengths[j] > STB_VORBIS_FAST_HUFFMAN_LENGTH && lengths[j] != NO_CODE) + ++sorted_count; + #endif + } + + c->sorted_entries = sorted_count; + values = NULL; + + CHECK(f); + if (!c->sparse) { + c->codewords = (uint32 *) setup_malloc(f, sizeof(c->codewords[0]) * c->entries); + if (!c->codewords) return error(f, VORBIS_outofmem); + } else { + unsigned int size; + if (c->sorted_entries) { + c->codeword_lengths = (uint8 *) setup_malloc(f, c->sorted_entries); + if (!c->codeword_lengths) return error(f, VORBIS_outofmem); + c->codewords = (uint32 *) setup_temp_malloc(f, sizeof(*c->codewords) * c->sorted_entries); + if (!c->codewords) return error(f, VORBIS_outofmem); + values = (uint32 *) setup_temp_malloc(f, sizeof(*values) * c->sorted_entries); + if (!values) return error(f, VORBIS_outofmem); + } + size = c->entries + (sizeof(*c->codewords) + sizeof(*values)) * c->sorted_entries; + if (size > f->setup_temp_memory_required) + f->setup_temp_memory_required = size; + } + + if (!compute_codewords(c, lengths, c->entries, values)) { + if (c->sparse) setup_temp_free(f, values, 0); + return error(f, VORBIS_invalid_setup); + } + + if (c->sorted_entries) { + // allocate an extra slot for sentinels + c->sorted_codewords = (uint32 *) setup_malloc(f, sizeof(*c->sorted_codewords) * (c->sorted_entries+1)); + if (c->sorted_codewords == NULL) return error(f, VORBIS_outofmem); + // allocate an extra slot at the front so that c->sorted_values[-1] is defined + // so that we can catch that case without an extra if + c->sorted_values = ( int *) setup_malloc(f, sizeof(*c->sorted_values ) * (c->sorted_entries+1)); + if (c->sorted_values == NULL) return error(f, VORBIS_outofmem); + ++c->sorted_values; + c->sorted_values[-1] = -1; + compute_sorted_huffman(c, lengths, values); + } + + if (c->sparse) { + setup_temp_free(f, values, sizeof(*values)*c->sorted_entries); + setup_temp_free(f, c->codewords, sizeof(*c->codewords)*c->sorted_entries); + setup_temp_free(f, lengths, c->entries); + c->codewords = NULL; + } + + compute_accelerated_huffman(c); + + CHECK(f); + c->lookup_type = get_bits(f, 4); + if (c->lookup_type > 2) return error(f, VORBIS_invalid_setup); + if (c->lookup_type > 0) { + uint16 *mults; + c->minimum_value = float32_unpack(get_bits(f, 32)); + c->delta_value = float32_unpack(get_bits(f, 32)); + c->value_bits = get_bits(f, 4)+1; + c->sequence_p = get_bits(f,1); + if (c->lookup_type == 1) { + int values = lookup1_values(c->entries, c->dimensions); + if (values < 0) return error(f, VORBIS_invalid_setup); + c->lookup_values = (uint32) values; + } else { + c->lookup_values = c->entries * c->dimensions; + } + if (c->lookup_values == 0) return error(f, VORBIS_invalid_setup); + mults = (uint16 *) setup_temp_malloc(f, sizeof(mults[0]) * c->lookup_values); + if (mults == NULL) return error(f, VORBIS_outofmem); + for (j=0; j < (int) c->lookup_values; ++j) { + int q = get_bits(f, c->value_bits); + if (q == EOP) { setup_temp_free(f,mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_invalid_setup); } + mults[j] = q; + } + +#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (c->lookup_type == 1) { + int len, sparse = c->sparse; + float last=0; + // pre-expand the lookup1-style multiplicands, to avoid a divide in the inner loop + if (sparse) { + if (c->sorted_entries == 0) goto skip; + c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->sorted_entries * c->dimensions); + } else + c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->entries * c->dimensions); + if (c->multiplicands == NULL) { setup_temp_free(f,mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_outofmem); } + len = sparse ? c->sorted_entries : c->entries; + for (j=0; j < len; ++j) { + unsigned int z = sparse ? c->sorted_values[j] : j; + unsigned int div=1; + for (k=0; k < c->dimensions; ++k) { + int off = (z / div) % c->lookup_values; + float val = mults[off]; + val = mults[off]*c->delta_value + c->minimum_value + last; + c->multiplicands[j*c->dimensions + k] = val; + if (c->sequence_p) + last = val; + if (k+1 < c->dimensions) { + if (div > UINT_MAX / (unsigned int) c->lookup_values) { + setup_temp_free(f, mults,sizeof(mults[0])*c->lookup_values); + return error(f, VORBIS_invalid_setup); + } + div *= c->lookup_values; + } + } + } + c->lookup_type = 2; + } + else +#endif + { + float last=0; + CHECK(f); + c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->lookup_values); + if (c->multiplicands == NULL) { setup_temp_free(f, mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_outofmem); } + for (j=0; j < (int) c->lookup_values; ++j) { + float val = mults[j] * c->delta_value + c->minimum_value + last; + c->multiplicands[j] = val; + if (c->sequence_p) + last = val; + } + } +#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK + skip:; +#endif + setup_temp_free(f, mults, sizeof(mults[0])*c->lookup_values); + + CHECK(f); + } + CHECK(f); + } + + // time domain transfers (notused) + + x = get_bits(f, 6) + 1; + for (i=0; i < x; ++i) { + uint32 z = get_bits(f, 16); + if (z != 0) return error(f, VORBIS_invalid_setup); + } + + // Floors + f->floor_count = get_bits(f, 6)+1; + f->floor_config = (Floor *) setup_malloc(f, f->floor_count * sizeof(*f->floor_config)); + if (f->floor_config == NULL) return error(f, VORBIS_outofmem); + for (i=0; i < f->floor_count; ++i) { + f->floor_types[i] = get_bits(f, 16); + if (f->floor_types[i] > 1) return error(f, VORBIS_invalid_setup); + if (f->floor_types[i] == 0) { + Floor0 *g = &f->floor_config[i].floor0; + g->order = get_bits(f,8); + g->rate = get_bits(f,16); + g->bark_map_size = get_bits(f,16); + g->amplitude_bits = get_bits(f,6); + g->amplitude_offset = get_bits(f,8); + g->number_of_books = get_bits(f,4) + 1; + for (j=0; j < g->number_of_books; ++j) + g->book_list[j] = get_bits(f,8); + return error(f, VORBIS_feature_not_supported); + } else { + stbv__floor_ordering p[31*8+2]; + Floor1 *g = &f->floor_config[i].floor1; + int max_class = -1; + g->partitions = get_bits(f, 5); + for (j=0; j < g->partitions; ++j) { + g->partition_class_list[j] = get_bits(f, 4); + if (g->partition_class_list[j] > max_class) + max_class = g->partition_class_list[j]; + } + for (j=0; j <= max_class; ++j) { + g->class_dimensions[j] = get_bits(f, 3)+1; + g->class_subclasses[j] = get_bits(f, 2); + if (g->class_subclasses[j]) { + g->class_masterbooks[j] = get_bits(f, 8); + if (g->class_masterbooks[j] >= f->codebook_count) return error(f, VORBIS_invalid_setup); + } + for (k=0; k < 1 << g->class_subclasses[j]; ++k) { + g->subclass_books[j][k] = get_bits(f,8)-1; + if (g->subclass_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup); + } + } + g->floor1_multiplier = get_bits(f,2)+1; + g->rangebits = get_bits(f,4); + g->Xlist[0] = 0; + g->Xlist[1] = 1 << g->rangebits; + g->values = 2; + for (j=0; j < g->partitions; ++j) { + int c = g->partition_class_list[j]; + for (k=0; k < g->class_dimensions[c]; ++k) { + g->Xlist[g->values] = get_bits(f, g->rangebits); + ++g->values; + } + } + // precompute the sorting + for (j=0; j < g->values; ++j) { + p[j].x = g->Xlist[j]; + p[j].id = j; + } + qsort(p, g->values, sizeof(p[0]), point_compare); + for (j=0; j < g->values-1; ++j) + if (p[j].x == p[j+1].x) + return error(f, VORBIS_invalid_setup); + for (j=0; j < g->values; ++j) + g->sorted_order[j] = (uint8) p[j].id; + // precompute the neighbors + for (j=2; j < g->values; ++j) { + int low = 0,hi = 0; + neighbors(g->Xlist, j, &low,&hi); + g->neighbors[j][0] = low; + g->neighbors[j][1] = hi; + } + + if (g->values > longest_floorlist) + longest_floorlist = g->values; + } + } + + // Residue + f->residue_count = get_bits(f, 6)+1; + f->residue_config = (Residue *) setup_malloc(f, f->residue_count * sizeof(f->residue_config[0])); + if (f->residue_config == NULL) return error(f, VORBIS_outofmem); + memset(f->residue_config, 0, f->residue_count * sizeof(f->residue_config[0])); + for (i=0; i < f->residue_count; ++i) { + uint8 residue_cascade[64]; + Residue *r = f->residue_config+i; + f->residue_types[i] = get_bits(f, 16); + if (f->residue_types[i] > 2) return error(f, VORBIS_invalid_setup); + r->begin = get_bits(f, 24); + r->end = get_bits(f, 24); + if (r->end < r->begin) return error(f, VORBIS_invalid_setup); + r->part_size = get_bits(f,24)+1; + r->classifications = get_bits(f,6)+1; + r->classbook = get_bits(f,8); + if (r->classbook >= f->codebook_count) return error(f, VORBIS_invalid_setup); + for (j=0; j < r->classifications; ++j) { + uint8 high_bits=0; + uint8 low_bits=get_bits(f,3); + if (get_bits(f,1)) + high_bits = get_bits(f,5); + residue_cascade[j] = high_bits*8 + low_bits; + } + r->residue_books = (short (*)[8]) setup_malloc(f, sizeof(r->residue_books[0]) * r->classifications); + if (r->residue_books == NULL) return error(f, VORBIS_outofmem); + for (j=0; j < r->classifications; ++j) { + for (k=0; k < 8; ++k) { + if (residue_cascade[j] & (1 << k)) { + r->residue_books[j][k] = get_bits(f, 8); + if (r->residue_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup); + } else { + r->residue_books[j][k] = -1; + } + } + } + // precompute the classifications[] array to avoid inner-loop mod/divide + // call it 'classdata' since we already have r->classifications + r->classdata = (uint8 **) setup_malloc(f, sizeof(*r->classdata) * f->codebooks[r->classbook].entries); + if (!r->classdata) return error(f, VORBIS_outofmem); + memset(r->classdata, 0, sizeof(*r->classdata) * f->codebooks[r->classbook].entries); + for (j=0; j < f->codebooks[r->classbook].entries; ++j) { + int classwords = f->codebooks[r->classbook].dimensions; + int temp = j; + r->classdata[j] = (uint8 *) setup_malloc(f, sizeof(r->classdata[j][0]) * classwords); + if (r->classdata[j] == NULL) return error(f, VORBIS_outofmem); + for (k=classwords-1; k >= 0; --k) { + r->classdata[j][k] = temp % r->classifications; + temp /= r->classifications; + } + } + } + + f->mapping_count = get_bits(f,6)+1; + f->mapping = (Mapping *) setup_malloc(f, f->mapping_count * sizeof(*f->mapping)); + if (f->mapping == NULL) return error(f, VORBIS_outofmem); + memset(f->mapping, 0, f->mapping_count * sizeof(*f->mapping)); + for (i=0; i < f->mapping_count; ++i) { + Mapping *m = f->mapping + i; + int mapping_type = get_bits(f,16); + if (mapping_type != 0) return error(f, VORBIS_invalid_setup); + m->chan = (MappingChannel *) setup_malloc(f, f->channels * sizeof(*m->chan)); + if (m->chan == NULL) return error(f, VORBIS_outofmem); + if (get_bits(f,1)) + m->submaps = get_bits(f,4)+1; + else + m->submaps = 1; + if (m->submaps > max_submaps) + max_submaps = m->submaps; + if (get_bits(f,1)) { + m->coupling_steps = get_bits(f,8)+1; + if (m->coupling_steps > f->channels) return error(f, VORBIS_invalid_setup); + for (k=0; k < m->coupling_steps; ++k) { + m->chan[k].magnitude = get_bits(f, ilog(f->channels-1)); + m->chan[k].angle = get_bits(f, ilog(f->channels-1)); + if (m->chan[k].magnitude >= f->channels) return error(f, VORBIS_invalid_setup); + if (m->chan[k].angle >= f->channels) return error(f, VORBIS_invalid_setup); + if (m->chan[k].magnitude == m->chan[k].angle) return error(f, VORBIS_invalid_setup); + } + } else + m->coupling_steps = 0; + + // reserved field + if (get_bits(f,2)) return error(f, VORBIS_invalid_setup); + if (m->submaps > 1) { + for (j=0; j < f->channels; ++j) { + m->chan[j].mux = get_bits(f, 4); + if (m->chan[j].mux >= m->submaps) return error(f, VORBIS_invalid_setup); + } + } else + // @SPECIFICATION: this case is missing from the spec + for (j=0; j < f->channels; ++j) + m->chan[j].mux = 0; + + for (j=0; j < m->submaps; ++j) { + get_bits(f,8); // discard + m->submap_floor[j] = get_bits(f,8); + m->submap_residue[j] = get_bits(f,8); + if (m->submap_floor[j] >= f->floor_count) return error(f, VORBIS_invalid_setup); + if (m->submap_residue[j] >= f->residue_count) return error(f, VORBIS_invalid_setup); + } + } + + // Modes + f->mode_count = get_bits(f, 6)+1; + for (i=0; i < f->mode_count; ++i) { + Mode *m = f->mode_config+i; + m->blockflag = get_bits(f,1); + m->windowtype = get_bits(f,16); + m->transformtype = get_bits(f,16); + m->mapping = get_bits(f,8); + if (m->windowtype != 0) return error(f, VORBIS_invalid_setup); + if (m->transformtype != 0) return error(f, VORBIS_invalid_setup); + if (m->mapping >= f->mapping_count) return error(f, VORBIS_invalid_setup); + } + + flush_packet(f); + + f->previous_length = 0; + + for (i=0; i < f->channels; ++i) { + f->channel_buffers[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1); + f->previous_window[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1/2); + f->finalY[i] = (int16 *) setup_malloc(f, sizeof(int16) * longest_floorlist); + if (f->channel_buffers[i] == NULL || f->previous_window[i] == NULL || f->finalY[i] == NULL) return error(f, VORBIS_outofmem); + memset(f->channel_buffers[i], 0, sizeof(float) * f->blocksize_1); + #ifdef STB_VORBIS_NO_DEFER_FLOOR + f->floor_buffers[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1/2); + if (f->floor_buffers[i] == NULL) return error(f, VORBIS_outofmem); + #endif + } + + if (!init_blocksize(f, 0, f->blocksize_0)) return FALSE; + if (!init_blocksize(f, 1, f->blocksize_1)) return FALSE; + f->blocksize[0] = f->blocksize_0; + f->blocksize[1] = f->blocksize_1; + +#ifdef STB_VORBIS_DIVIDE_TABLE + if (integer_divide_table[1][1]==0) + for (i=0; i < DIVTAB_NUMER; ++i) + for (j=1; j < DIVTAB_DENOM; ++j) + integer_divide_table[i][j] = i / j; +#endif + + // compute how much temporary memory is needed + + // 1. + { + uint32 imdct_mem = (f->blocksize_1 * sizeof(float) >> 1); + uint32 classify_mem; + int i,max_part_read=0; + for (i=0; i < f->residue_count; ++i) { + Residue *r = f->residue_config + i; + unsigned int actual_size = f->blocksize_1 / 2; + unsigned int limit_r_begin = r->begin < actual_size ? r->begin : actual_size; + unsigned int limit_r_end = r->end < actual_size ? r->end : actual_size; + int n_read = limit_r_end - limit_r_begin; + int part_read = n_read / r->part_size; + if (part_read > max_part_read) + max_part_read = part_read; + } + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + classify_mem = f->channels * (sizeof(void*) + max_part_read * sizeof(uint8 *)); + #else + classify_mem = f->channels * (sizeof(void*) + max_part_read * sizeof(int *)); + #endif + + // maximum reasonable partition size is f->blocksize_1 + + f->temp_memory_required = classify_mem; + if (imdct_mem > f->temp_memory_required) + f->temp_memory_required = imdct_mem; + } + + + if (f->alloc.alloc_buffer) { + assert(f->temp_offset == f->alloc.alloc_buffer_length_in_bytes); + // check if there's enough temp memory so we don't error later + if (f->setup_offset + sizeof(*f) + f->temp_memory_required > (unsigned) f->temp_offset) + return error(f, VORBIS_outofmem); + } + + // @TODO: stb_vorbis_seek_start expects first_audio_page_offset to point to a page + // without PAGEFLAG_continued_packet, so this either points to the first page, or + // the page after the end of the headers. It might be cleaner to point to a page + // in the middle of the headers, when that's the page where the first audio packet + // starts, but we'd have to also correctly skip the end of any continued packet in + // stb_vorbis_seek_start. + if (f->next_seg == -1) { + f->first_audio_page_offset = stb_vorbis_get_file_offset(f); + } else { + f->first_audio_page_offset = 0; + } + + return TRUE; +} + +static void vorbis_deinit(stb_vorbis *p) +{ + int i,j; + + setup_free(p, p->vendor); + for (i=0; i < p->comment_list_length; ++i) { + setup_free(p, p->comment_list[i]); + } + setup_free(p, p->comment_list); + + if (p->residue_config) { + for (i=0; i < p->residue_count; ++i) { + Residue *r = p->residue_config+i; + if (r->classdata) { + for (j=0; j < p->codebooks[r->classbook].entries; ++j) + setup_free(p, r->classdata[j]); + setup_free(p, r->classdata); + } + setup_free(p, r->residue_books); + } + } + + if (p->codebooks) { + CHECK(p); + for (i=0; i < p->codebook_count; ++i) { + Codebook *c = p->codebooks + i; + setup_free(p, c->codeword_lengths); + setup_free(p, c->multiplicands); + setup_free(p, c->codewords); + setup_free(p, c->sorted_codewords); + // c->sorted_values[-1] is the first entry in the array + setup_free(p, c->sorted_values ? c->sorted_values-1 : NULL); + } + setup_free(p, p->codebooks); + } + setup_free(p, p->floor_config); + setup_free(p, p->residue_config); + if (p->mapping) { + for (i=0; i < p->mapping_count; ++i) + setup_free(p, p->mapping[i].chan); + setup_free(p, p->mapping); + } + CHECK(p); + for (i=0; i < p->channels && i < STB_VORBIS_MAX_CHANNELS; ++i) { + setup_free(p, p->channel_buffers[i]); + setup_free(p, p->previous_window[i]); + #ifdef STB_VORBIS_NO_DEFER_FLOOR + setup_free(p, p->floor_buffers[i]); + #endif + setup_free(p, p->finalY[i]); + } + for (i=0; i < 2; ++i) { + setup_free(p, p->A[i]); + setup_free(p, p->B[i]); + setup_free(p, p->C[i]); + setup_free(p, p->window[i]); + setup_free(p, p->bit_reverse[i]); + } + #ifndef STB_VORBIS_NO_STDIO + if (p->close_on_free) fclose(p->f); + #endif +} + +void stb_vorbis_close(stb_vorbis *p) +{ + if (p == NULL) return; + vorbis_deinit(p); + setup_free(p,p); +} + +static void vorbis_init(stb_vorbis *p, const stb_vorbis_alloc *z) +{ + memset(p, 0, sizeof(*p)); // NULL out all malloc'd pointers to start + if (z) { + p->alloc = *z; + p->alloc.alloc_buffer_length_in_bytes &= ~7; + p->temp_offset = p->alloc.alloc_buffer_length_in_bytes; + } + p->eof = 0; + p->error = VORBIS__no_error; + p->stream = NULL; + p->codebooks = NULL; + p->page_crc_tests = -1; + #ifndef STB_VORBIS_NO_STDIO + p->close_on_free = FALSE; + p->f = NULL; + #endif +} + +int stb_vorbis_get_sample_offset(stb_vorbis *f) +{ + if (f->current_loc_valid) + return f->current_loc; + else + return -1; +} + +stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f) +{ + stb_vorbis_info d; + d.channels = f->channels; + d.sample_rate = f->sample_rate; + d.setup_memory_required = f->setup_memory_required; + d.setup_temp_memory_required = f->setup_temp_memory_required; + d.temp_memory_required = f->temp_memory_required; + d.max_frame_size = f->blocksize_1 >> 1; + return d; +} + +stb_vorbis_comment stb_vorbis_get_comment(stb_vorbis *f) +{ + stb_vorbis_comment d; + d.vendor = f->vendor; + d.comment_list_length = f->comment_list_length; + d.comment_list = f->comment_list; + return d; +} + +int stb_vorbis_get_error(stb_vorbis *f) +{ + int e = f->error; + f->error = VORBIS__no_error; + return e; +} + +static stb_vorbis * vorbis_alloc(stb_vorbis *f) +{ + stb_vorbis *p = (stb_vorbis *) setup_malloc(f, sizeof(*p)); + return p; +} + +#ifndef STB_VORBIS_NO_PUSHDATA_API + +void stb_vorbis_flush_pushdata(stb_vorbis *f) +{ + f->previous_length = 0; + f->page_crc_tests = 0; + f->discard_samples_deferred = 0; + f->current_loc_valid = FALSE; + f->first_decode = FALSE; + f->samples_output = 0; + f->channel_buffer_start = 0; + f->channel_buffer_end = 0; +} + +static int vorbis_search_for_page_pushdata(vorb *f, uint8 *data, int data_len) +{ + int i,n; + for (i=0; i < f->page_crc_tests; ++i) + f->scan[i].bytes_done = 0; + + // if we have room for more scans, search for them first, because + // they may cause us to stop early if their header is incomplete + if (f->page_crc_tests < STB_VORBIS_PUSHDATA_CRC_COUNT) { + if (data_len < 4) return 0; + data_len -= 3; // need to look for 4-byte sequence, so don't miss + // one that straddles a boundary + for (i=0; i < data_len; ++i) { + if (data[i] == 0x4f) { + if (0==memcmp(data+i, ogg_page_header, 4)) { + int j,len; + uint32 crc; + // make sure we have the whole page header + if (i+26 >= data_len || i+27+data[i+26] >= data_len) { + // only read up to this page start, so hopefully we'll + // have the whole page header start next time + data_len = i; + break; + } + // ok, we have it all; compute the length of the page + len = 27 + data[i+26]; + for (j=0; j < data[i+26]; ++j) + len += data[i+27+j]; + // scan everything up to the embedded crc (which we must 0) + crc = 0; + for (j=0; j < 22; ++j) + crc = crc32_update(crc, data[i+j]); + // now process 4 0-bytes + for ( ; j < 26; ++j) + crc = crc32_update(crc, 0); + // len is the total number of bytes we need to scan + n = f->page_crc_tests++; + f->scan[n].bytes_left = len-j; + f->scan[n].crc_so_far = crc; + f->scan[n].goal_crc = data[i+22] + (data[i+23] << 8) + (data[i+24]<<16) + (data[i+25]<<24); + // if the last frame on a page is continued to the next, then + // we can't recover the sample_loc immediately + if (data[i+27+data[i+26]-1] == 255) + f->scan[n].sample_loc = ~0; + else + f->scan[n].sample_loc = data[i+6] + (data[i+7] << 8) + (data[i+ 8]<<16) + (data[i+ 9]<<24); + f->scan[n].bytes_done = i+j; + if (f->page_crc_tests == STB_VORBIS_PUSHDATA_CRC_COUNT) + break; + // keep going if we still have room for more + } + } + } + } + + for (i=0; i < f->page_crc_tests;) { + uint32 crc; + int j; + int n = f->scan[i].bytes_done; + int m = f->scan[i].bytes_left; + if (m > data_len - n) m = data_len - n; + // m is the bytes to scan in the current chunk + crc = f->scan[i].crc_so_far; + for (j=0; j < m; ++j) + crc = crc32_update(crc, data[n+j]); + f->scan[i].bytes_left -= m; + f->scan[i].crc_so_far = crc; + if (f->scan[i].bytes_left == 0) { + // does it match? + if (f->scan[i].crc_so_far == f->scan[i].goal_crc) { + // Houston, we have page + data_len = n+m; // consumption amount is wherever that scan ended + f->page_crc_tests = -1; // drop out of page scan mode + f->previous_length = 0; // decode-but-don't-output one frame + f->next_seg = -1; // start a new page + f->current_loc = f->scan[i].sample_loc; // set the current sample location + // to the amount we'd have decoded had we decoded this page + f->current_loc_valid = f->current_loc != ~0U; + return data_len; + } + // delete entry + f->scan[i] = f->scan[--f->page_crc_tests]; + } else { + ++i; + } + } + + return data_len; +} + +// return value: number of bytes we used +int stb_vorbis_decode_frame_pushdata( + stb_vorbis *f, // the file we're decoding + const uint8 *data, int data_len, // the memory available for decoding + int *channels, // place to write number of float * buffers + float ***output, // place to write float ** array of float * buffers + int *samples // place to write number of output samples + ) +{ + int i; + int len,right,left; + + if (!IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); + + if (f->page_crc_tests >= 0) { + *samples = 0; + return vorbis_search_for_page_pushdata(f, (uint8 *) data, data_len); + } + + f->stream = (uint8 *) data; + f->stream_end = (uint8 *) data + data_len; + f->error = VORBIS__no_error; + + // check that we have the entire packet in memory + if (!is_whole_packet_present(f)) { + *samples = 0; + return 0; + } + + if (!vorbis_decode_packet(f, &len, &left, &right)) { + // save the actual error we encountered + enum STBVorbisError error = f->error; + if (error == VORBIS_bad_packet_type) { + // flush and resynch + f->error = VORBIS__no_error; + while (get8_packet(f) != EOP) + if (f->eof) break; + *samples = 0; + return (int) (f->stream - data); + } + if (error == VORBIS_continued_packet_flag_invalid) { + if (f->previous_length == 0) { + // we may be resynching, in which case it's ok to hit one + // of these; just discard the packet + f->error = VORBIS__no_error; + while (get8_packet(f) != EOP) + if (f->eof) break; + *samples = 0; + return (int) (f->stream - data); + } + } + // if we get an error while parsing, what to do? + // well, it DEFINITELY won't work to continue from where we are! + stb_vorbis_flush_pushdata(f); + // restore the error that actually made us bail + f->error = error; + *samples = 0; + return 1; + } + + // success! + len = vorbis_finish_frame(f, len, left, right); + for (i=0; i < f->channels; ++i) + f->outputs[i] = f->channel_buffers[i] + left; + + if (channels) *channels = f->channels; + *samples = len; + *output = f->outputs; + return (int) (f->stream - data); +} + +stb_vorbis *stb_vorbis_open_pushdata( + const unsigned char *data, int data_len, // the memory available for decoding + int *data_used, // only defined if result is not NULL + int *error, const stb_vorbis_alloc *alloc) +{ + stb_vorbis *f, p; + vorbis_init(&p, alloc); + p.stream = (uint8 *) data; + p.stream_end = (uint8 *) data + data_len; + p.push_mode = TRUE; + if (!start_decoder(&p)) { + if (p.eof) + *error = VORBIS_need_more_data; + else + *error = p.error; + return NULL; + } + f = vorbis_alloc(&p); + if (f) { + *f = p; + *data_used = (int) (f->stream - data); + *error = 0; + return f; + } else { + vorbis_deinit(&p); + return NULL; + } +} +#endif // STB_VORBIS_NO_PUSHDATA_API + +unsigned int stb_vorbis_get_file_offset(stb_vorbis *f) +{ + #ifndef STB_VORBIS_NO_PUSHDATA_API + if (f->push_mode) return 0; + #endif + if (USE_MEMORY(f)) return (unsigned int) (f->stream - f->stream_start); + #ifndef STB_VORBIS_NO_STDIO + return (unsigned int) (ftell(f->f) - f->f_start); + #endif +} + +#ifndef STB_VORBIS_NO_PULLDATA_API +// +// DATA-PULLING API +// + +static uint32 vorbis_find_page(stb_vorbis *f, uint32 *end, uint32 *last) +{ + for(;;) { + int n; + if (f->eof) return 0; + n = get8(f); + if (n == 0x4f) { // page header candidate + unsigned int retry_loc = stb_vorbis_get_file_offset(f); + int i; + // check if we're off the end of a file_section stream + if (retry_loc - 25 > f->stream_len) + return 0; + // check the rest of the header + for (i=1; i < 4; ++i) + if (get8(f) != ogg_page_header[i]) + break; + if (f->eof) return 0; + if (i == 4) { + uint8 header[27]; + uint32 i, crc, goal, len; + for (i=0; i < 4; ++i) + header[i] = ogg_page_header[i]; + for (; i < 27; ++i) + header[i] = get8(f); + if (f->eof) return 0; + if (header[4] != 0) goto invalid; + goal = header[22] + (header[23] << 8) + (header[24]<<16) + (header[25]<<24); + for (i=22; i < 26; ++i) + header[i] = 0; + crc = 0; + for (i=0; i < 27; ++i) + crc = crc32_update(crc, header[i]); + len = 0; + for (i=0; i < header[26]; ++i) { + int s = get8(f); + crc = crc32_update(crc, s); + len += s; + } + if (len && f->eof) return 0; + for (i=0; i < len; ++i) + crc = crc32_update(crc, get8(f)); + // finished parsing probable page + if (crc == goal) { + // we could now check that it's either got the last + // page flag set, OR it's followed by the capture + // pattern, but I guess TECHNICALLY you could have + // a file with garbage between each ogg page and recover + // from it automatically? So even though that paranoia + // might decrease the chance of an invalid decode by + // another 2^32, not worth it since it would hose those + // invalid-but-useful files? + if (end) + *end = stb_vorbis_get_file_offset(f); + if (last) { + if (header[5] & 0x04) + *last = 1; + else + *last = 0; + } + set_file_offset(f, retry_loc-1); + return 1; + } + } + invalid: + // not a valid page, so rewind and look for next one + set_file_offset(f, retry_loc); + } + } +} + + +#define SAMPLE_unknown 0xffffffff + +// seeking is implemented with a binary search, which narrows down the range to +// 64K, before using a linear search (because finding the synchronization +// pattern can be expensive, and the chance we'd find the end page again is +// relatively high for small ranges) +// +// two initial interpolation-style probes are used at the start of the search +// to try to bound either side of the binary search sensibly, while still +// working in O(log n) time if they fail. + +static int get_seek_page_info(stb_vorbis *f, ProbedPage *z) +{ + uint8 header[27], lacing[255]; + int i,len; + + // record where the page starts + z->page_start = stb_vorbis_get_file_offset(f); + + // parse the header + getn(f, header, 27); + if (header[0] != 'O' || header[1] != 'g' || header[2] != 'g' || header[3] != 'S') + return 0; + getn(f, lacing, header[26]); + + // determine the length of the payload + len = 0; + for (i=0; i < header[26]; ++i) + len += lacing[i]; + + // this implies where the page ends + z->page_end = z->page_start + 27 + header[26] + len; + + // read the last-decoded sample out of the data + z->last_decoded_sample = header[6] + (header[7] << 8) + (header[8] << 16) + (header[9] << 24); + + // restore file state to where we were + set_file_offset(f, z->page_start); + return 1; +} + +// rarely used function to seek back to the preceding page while finding the +// start of a packet +static int go_to_page_before(stb_vorbis *f, unsigned int limit_offset) +{ + unsigned int previous_safe, end; + + // now we want to seek back 64K from the limit + if (limit_offset >= 65536 && limit_offset-65536 >= f->first_audio_page_offset) + previous_safe = limit_offset - 65536; + else + previous_safe = f->first_audio_page_offset; + + set_file_offset(f, previous_safe); + + while (vorbis_find_page(f, &end, NULL)) { + if (end >= limit_offset && stb_vorbis_get_file_offset(f) < limit_offset) + return 1; + set_file_offset(f, end); + } + + return 0; +} + +// implements the search logic for finding a page and starting decoding. if +// the function succeeds, current_loc_valid will be true and current_loc will +// be less than or equal to the provided sample number (the closer the +// better). +static int seek_to_sample_coarse(stb_vorbis *f, uint32 sample_number) +{ + ProbedPage left, right, mid; + int i, start_seg_with_known_loc, end_pos, page_start; + uint32 delta, stream_length, padding, last_sample_limit; + double offset = 0.0, bytes_per_sample = 0.0; + int probe = 0; + + // find the last page and validate the target sample + stream_length = stb_vorbis_stream_length_in_samples(f); + if (stream_length == 0) return error(f, VORBIS_seek_without_length); + if (sample_number > stream_length) return error(f, VORBIS_seek_invalid); + + // this is the maximum difference between the window-center (which is the + // actual granule position value), and the right-start (which the spec + // indicates should be the granule position (give or take one)). + padding = ((f->blocksize_1 - f->blocksize_0) >> 2); + if (sample_number < padding) + last_sample_limit = 0; + else + last_sample_limit = sample_number - padding; + + left = f->p_first; + while (left.last_decoded_sample == ~0U) { + // (untested) the first page does not have a 'last_decoded_sample' + set_file_offset(f, left.page_end); + if (!get_seek_page_info(f, &left)) goto error; + } + + right = f->p_last; + assert(right.last_decoded_sample != ~0U); + + // starting from the start is handled differently + if (last_sample_limit <= left.last_decoded_sample) { + if (stb_vorbis_seek_start(f)) { + if (f->current_loc > sample_number) + return error(f, VORBIS_seek_failed); + return 1; + } + return 0; + } + + while (left.page_end != right.page_start) { + assert(left.page_end < right.page_start); + // search range in bytes + delta = right.page_start - left.page_end; + if (delta <= 65536) { + // there's only 64K left to search - handle it linearly + set_file_offset(f, left.page_end); + } else { + if (probe < 2) { + if (probe == 0) { + // first probe (interpolate) + double data_bytes = right.page_end - left.page_start; + bytes_per_sample = data_bytes / right.last_decoded_sample; + offset = left.page_start + bytes_per_sample * (last_sample_limit - left.last_decoded_sample); + } else { + // second probe (try to bound the other side) + double error = ((double) last_sample_limit - mid.last_decoded_sample) * bytes_per_sample; + if (error >= 0 && error < 8000) error = 8000; + if (error < 0 && error > -8000) error = -8000; + offset += error * 2; + } + + // ensure the offset is valid + if (offset < left.page_end) + offset = left.page_end; + if (offset > right.page_start - 65536) + offset = right.page_start - 65536; + + set_file_offset(f, (unsigned int) offset); + } else { + // binary search for large ranges (offset by 32K to ensure + // we don't hit the right page) + set_file_offset(f, left.page_end + (delta / 2) - 32768); + } + + if (!vorbis_find_page(f, NULL, NULL)) goto error; + } + + for (;;) { + if (!get_seek_page_info(f, &mid)) goto error; + if (mid.last_decoded_sample != ~0U) break; + // (untested) no frames end on this page + set_file_offset(f, mid.page_end); + assert(mid.page_start < right.page_start); + } + + // if we've just found the last page again then we're in a tricky file, + // and we're close enough (if it wasn't an interpolation probe). + if (mid.page_start == right.page_start) { + if (probe >= 2 || delta <= 65536) + break; + } else { + if (last_sample_limit < mid.last_decoded_sample) + right = mid; + else + left = mid; + } + + ++probe; + } + + // seek back to start of the last packet + page_start = left.page_start; + set_file_offset(f, page_start); + if (!start_page(f)) return error(f, VORBIS_seek_failed); + end_pos = f->end_seg_with_known_loc; + assert(end_pos >= 0); + + for (;;) { + for (i = end_pos; i > 0; --i) + if (f->segments[i-1] != 255) + break; + + start_seg_with_known_loc = i; + + if (start_seg_with_known_loc > 0 || !(f->page_flag & PAGEFLAG_continued_packet)) + break; + + // (untested) the final packet begins on an earlier page + if (!go_to_page_before(f, page_start)) + goto error; + + page_start = stb_vorbis_get_file_offset(f); + if (!start_page(f)) goto error; + end_pos = f->segment_count - 1; + } + + // prepare to start decoding + f->current_loc_valid = FALSE; + f->last_seg = FALSE; + f->valid_bits = 0; + f->packet_bytes = 0; + f->bytes_in_seg = 0; + f->previous_length = 0; + f->next_seg = start_seg_with_known_loc; + + for (i = 0; i < start_seg_with_known_loc; i++) + skip(f, f->segments[i]); + + // start decoding (optimizable - this frame is generally discarded) + if (!vorbis_pump_first_frame(f)) + return 0; + if (f->current_loc > sample_number) + return error(f, VORBIS_seek_failed); + return 1; + +error: + // try to restore the file to a valid state + stb_vorbis_seek_start(f); + return error(f, VORBIS_seek_failed); +} + +// the same as vorbis_decode_initial, but without advancing +static int peek_decode_initial(vorb *f, int *p_left_start, int *p_left_end, int *p_right_start, int *p_right_end, int *mode) +{ + int bits_read, bytes_read; + + if (!vorbis_decode_initial(f, p_left_start, p_left_end, p_right_start, p_right_end, mode)) + return 0; + + // either 1 or 2 bytes were read, figure out which so we can rewind + bits_read = 1 + ilog(f->mode_count-1); + if (f->mode_config[*mode].blockflag) + bits_read += 2; + bytes_read = (bits_read + 7) / 8; + + f->bytes_in_seg += bytes_read; + f->packet_bytes -= bytes_read; + skip(f, -bytes_read); + if (f->next_seg == -1) + f->next_seg = f->segment_count - 1; + else + f->next_seg--; + f->valid_bits = 0; + + return 1; +} + +int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number) +{ + uint32 max_frame_samples; + + if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); + + // fast page-level search + if (!seek_to_sample_coarse(f, sample_number)) + return 0; + + assert(f->current_loc_valid); + assert(f->current_loc <= sample_number); + + // linear search for the relevant packet + max_frame_samples = (f->blocksize_1*3 - f->blocksize_0) >> 2; + while (f->current_loc < sample_number) { + int left_start, left_end, right_start, right_end, mode, frame_samples; + if (!peek_decode_initial(f, &left_start, &left_end, &right_start, &right_end, &mode)) + return error(f, VORBIS_seek_failed); + // calculate the number of samples returned by the next frame + frame_samples = right_start - left_start; + if (f->current_loc + frame_samples > sample_number) { + return 1; // the next frame will contain the sample + } else if (f->current_loc + frame_samples + max_frame_samples > sample_number) { + // there's a chance the frame after this could contain the sample + vorbis_pump_first_frame(f); + } else { + // this frame is too early to be relevant + f->current_loc += frame_samples; + f->previous_length = 0; + maybe_start_packet(f); + flush_packet(f); + } + } + // the next frame should start with the sample + if (f->current_loc != sample_number) return error(f, VORBIS_seek_failed); + return 1; +} + +int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number) +{ + if (!stb_vorbis_seek_frame(f, sample_number)) + return 0; + + if (sample_number != f->current_loc) { + int n; + uint32 frame_start = f->current_loc; + stb_vorbis_get_frame_float(f, &n, NULL); + assert(sample_number > frame_start); + assert(f->channel_buffer_start + (int) (sample_number-frame_start) <= f->channel_buffer_end); + f->channel_buffer_start += (sample_number - frame_start); + } + + return 1; +} + +int stb_vorbis_seek_start(stb_vorbis *f) +{ + if (IS_PUSH_MODE(f)) { return error(f, VORBIS_invalid_api_mixing); } + set_file_offset(f, f->first_audio_page_offset); + f->previous_length = 0; + f->first_decode = TRUE; + f->next_seg = -1; + return vorbis_pump_first_frame(f); +} + +unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f) +{ + unsigned int restore_offset, previous_safe; + unsigned int end, last_page_loc; + + if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); + if (!f->total_samples) { + unsigned int last; + uint32 lo,hi; + char header[6]; + + // first, store the current decode position so we can restore it + restore_offset = stb_vorbis_get_file_offset(f); + + // now we want to seek back 64K from the end (the last page must + // be at most a little less than 64K, but let's allow a little slop) + if (f->stream_len >= 65536 && f->stream_len-65536 >= f->first_audio_page_offset) + previous_safe = f->stream_len - 65536; + else + previous_safe = f->first_audio_page_offset; + + set_file_offset(f, previous_safe); + // previous_safe is now our candidate 'earliest known place that seeking + // to will lead to the final page' + + if (!vorbis_find_page(f, &end, &last)) { + // if we can't find a page, we're hosed! + f->error = VORBIS_cant_find_last_page; + f->total_samples = 0xffffffff; + goto done; + } + + // check if there are more pages + last_page_loc = stb_vorbis_get_file_offset(f); + + // stop when the last_page flag is set, not when we reach eof; + // this allows us to stop short of a 'file_section' end without + // explicitly checking the length of the section + while (!last) { + set_file_offset(f, end); + if (!vorbis_find_page(f, &end, &last)) { + // the last page we found didn't have the 'last page' flag + // set. whoops! + break; + } + previous_safe = last_page_loc+1; + last_page_loc = stb_vorbis_get_file_offset(f); + } + + set_file_offset(f, last_page_loc); + + // parse the header + getn(f, (unsigned char *)header, 6); + // extract the absolute granule position + lo = get32(f); + hi = get32(f); + if (lo == 0xffffffff && hi == 0xffffffff) { + f->error = VORBIS_cant_find_last_page; + f->total_samples = SAMPLE_unknown; + goto done; + } + if (hi) + lo = 0xfffffffe; // saturate + f->total_samples = lo; + + f->p_last.page_start = last_page_loc; + f->p_last.page_end = end; + f->p_last.last_decoded_sample = lo; + + done: + set_file_offset(f, restore_offset); + } + return f->total_samples == SAMPLE_unknown ? 0 : f->total_samples; +} + +float stb_vorbis_stream_length_in_seconds(stb_vorbis *f) +{ + return stb_vorbis_stream_length_in_samples(f) / (float) f->sample_rate; +} + + + +int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output) +{ + int len, right,left,i; + if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); + + if (!vorbis_decode_packet(f, &len, &left, &right)) { + f->channel_buffer_start = f->channel_buffer_end = 0; + return 0; + } + + len = vorbis_finish_frame(f, len, left, right); + for (i=0; i < f->channels; ++i) + f->outputs[i] = f->channel_buffers[i] + left; + + f->channel_buffer_start = left; + f->channel_buffer_end = left+len; + + if (channels) *channels = f->channels; + if (output) *output = f->outputs; + return len; +} + +#ifndef STB_VORBIS_NO_STDIO + +stb_vorbis * stb_vorbis_open_file_section(FILE *file, int close_on_free, int *error, const stb_vorbis_alloc *alloc, unsigned int length) +{ + stb_vorbis *f, p; + vorbis_init(&p, alloc); + p.f = file; + p.f_start = (uint32) ftell(file); + p.stream_len = length; + p.close_on_free = close_on_free; + if (start_decoder(&p)) { + f = vorbis_alloc(&p); + if (f) { + *f = p; + vorbis_pump_first_frame(f); + return f; + } + } + if (error) *error = p.error; + vorbis_deinit(&p); + return NULL; +} + +stb_vorbis * stb_vorbis_open_file(FILE *file, int close_on_free, int *error, const stb_vorbis_alloc *alloc) +{ + 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); + return stb_vorbis_open_file_section(file, close_on_free, error, alloc, len); +} + +stb_vorbis * stb_vorbis_open_filename(const char *filename, int *error, const stb_vorbis_alloc *alloc) +{ + FILE *f; +#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__) + if (0 != fopen_s(&f, filename, "rb")) + f = NULL; +#else + f = fopen(filename, "rb"); +#endif + if (f) + return stb_vorbis_open_file(f, TRUE, error, alloc); + if (error) *error = VORBIS_file_open_failure; + return NULL; +} +#endif // STB_VORBIS_NO_STDIO + +stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, int *error, const stb_vorbis_alloc *alloc) +{ + stb_vorbis *f, p; + if (data == NULL) return NULL; + vorbis_init(&p, alloc); + p.stream = (uint8 *) data; + p.stream_end = (uint8 *) data + len; + p.stream_start = (uint8 *) p.stream; + p.stream_len = len; + p.push_mode = FALSE; + if (start_decoder(&p)) { + f = vorbis_alloc(&p); + if (f) { + *f = p; + vorbis_pump_first_frame(f); + if (error) *error = VORBIS__no_error; + return f; + } + } + if (error) *error = p.error; + vorbis_deinit(&p); + return NULL; +} + +#ifndef STB_VORBIS_NO_INTEGER_CONVERSION +#define PLAYBACK_MONO 1 +#define PLAYBACK_LEFT 2 +#define PLAYBACK_RIGHT 4 + +#define L (PLAYBACK_LEFT | PLAYBACK_MONO) +#define C (PLAYBACK_LEFT | PLAYBACK_RIGHT | PLAYBACK_MONO) +#define R (PLAYBACK_RIGHT | PLAYBACK_MONO) + +static int8 channel_position[7][6] = +{ + { 0 }, + { C }, + { L, R }, + { L, C, R }, + { L, R, L, R }, + { L, C, R, L, R }, + { L, C, R, L, R, C }, +}; + + +#ifndef STB_VORBIS_NO_FAST_SCALED_FLOAT + typedef union { + float f; + int i; + } float_conv; + typedef char stb_vorbis_float_size_test[sizeof(float)==4 && sizeof(int) == 4]; + #define FASTDEF(x) float_conv x + // add (1<<23) to convert to int, then divide by 2^SHIFT, then add 0.5/2^SHIFT to round + #define MAGIC(SHIFT) (1.5f * (1 << (23-SHIFT)) + 0.5f/(1 << SHIFT)) + #define ADDEND(SHIFT) (((150-SHIFT) << 23) + (1 << 22)) + #define FAST_SCALED_FLOAT_TO_INT(temp,x,s) (temp.f = (x) + MAGIC(s), temp.i - ADDEND(s)) + #define check_endianness() +#else + #define FAST_SCALED_FLOAT_TO_INT(temp,x,s) ((int) ((x) * (1 << (s)))) + #define check_endianness() + #define FASTDEF(x) +#endif + +static void copy_samples(short *dest, float *src, int len) +{ + int i; + check_endianness(); + for (i=0; i < len; ++i) { + FASTDEF(temp); + int v = FAST_SCALED_FLOAT_TO_INT(temp, src[i],15); + if ((unsigned int) (v + 32768) > 65535) + v = v < 0 ? -32768 : 32767; + dest[i] = v; + } +} + +static void compute_samples(int mask, short *output, int num_c, float **data, int d_offset, int len) +{ + #define BUFFER_SIZE 32 + float buffer[BUFFER_SIZE]; + int i,j,o,n = BUFFER_SIZE; + check_endianness(); + for (o = 0; o < len; o += BUFFER_SIZE) { + memset(buffer, 0, sizeof(buffer)); + if (o + n > len) n = len - o; + for (j=0; j < num_c; ++j) { + if (channel_position[num_c][j] & mask) { + for (i=0; i < n; ++i) + buffer[i] += data[j][d_offset+o+i]; + } + } + for (i=0; i < n; ++i) { + FASTDEF(temp); + int v = FAST_SCALED_FLOAT_TO_INT(temp,buffer[i],15); + if ((unsigned int) (v + 32768) > 65535) + v = v < 0 ? -32768 : 32767; + output[o+i] = v; + } + } +} + +static void compute_stereo_samples(short *output, int num_c, float **data, int d_offset, int len) +{ + #define BUFFER_SIZE 32 + float buffer[BUFFER_SIZE]; + int i,j,o,n = BUFFER_SIZE >> 1; + // o is the offset in the source data + check_endianness(); + for (o = 0; o < len; o += BUFFER_SIZE >> 1) { + // o2 is the offset in the output data + int o2 = o << 1; + memset(buffer, 0, sizeof(buffer)); + if (o + n > len) n = len - o; + for (j=0; j < num_c; ++j) { + int m = channel_position[num_c][j] & (PLAYBACK_LEFT | PLAYBACK_RIGHT); + if (m == (PLAYBACK_LEFT | PLAYBACK_RIGHT)) { + for (i=0; i < n; ++i) { + buffer[i*2+0] += data[j][d_offset+o+i]; + buffer[i*2+1] += data[j][d_offset+o+i]; + } + } else if (m == PLAYBACK_LEFT) { + for (i=0; i < n; ++i) { + buffer[i*2+0] += data[j][d_offset+o+i]; + } + } else if (m == PLAYBACK_RIGHT) { + for (i=0; i < n; ++i) { + buffer[i*2+1] += data[j][d_offset+o+i]; + } + } + } + for (i=0; i < (n<<1); ++i) { + FASTDEF(temp); + int v = FAST_SCALED_FLOAT_TO_INT(temp,buffer[i],15); + if ((unsigned int) (v + 32768) > 65535) + v = v < 0 ? -32768 : 32767; + output[o2+i] = v; + } + } +} + +static void convert_samples_short(int buf_c, short **buffer, int b_offset, int data_c, float **data, int d_offset, int samples) +{ + int i; + if (buf_c != data_c && buf_c <= 2 && data_c <= 6) { + static int channel_selector[3][2] = { {0}, {PLAYBACK_MONO}, {PLAYBACK_LEFT, PLAYBACK_RIGHT} }; + for (i=0; i < buf_c; ++i) + compute_samples(channel_selector[buf_c][i], buffer[i]+b_offset, data_c, data, d_offset, samples); + } else { + int limit = buf_c < data_c ? buf_c : data_c; + for (i=0; i < limit; ++i) + copy_samples(buffer[i]+b_offset, data[i]+d_offset, samples); + for ( ; i < buf_c; ++i) + memset(buffer[i]+b_offset, 0, sizeof(short) * samples); + } +} + +int stb_vorbis_get_frame_short(stb_vorbis *f, int num_c, short **buffer, int num_samples) +{ + float **output = NULL; + int len = stb_vorbis_get_frame_float(f, NULL, &output); + if (len > num_samples) len = num_samples; + if (len) + convert_samples_short(num_c, buffer, 0, f->channels, output, 0, len); + return len; +} + +static void convert_channels_short_interleaved(int buf_c, short *buffer, int data_c, float **data, int d_offset, int len) +{ + int i; + check_endianness(); + if (buf_c != data_c && buf_c <= 2 && data_c <= 6) { + assert(buf_c == 2); + for (i=0; i < buf_c; ++i) + compute_stereo_samples(buffer, data_c, data, d_offset, len); + } else { + int limit = buf_c < data_c ? buf_c : data_c; + int j; + for (j=0; j < len; ++j) { + for (i=0; i < limit; ++i) { + FASTDEF(temp); + float f = data[i][d_offset+j]; + int v = FAST_SCALED_FLOAT_TO_INT(temp, f,15);//data[i][d_offset+j],15); + if ((unsigned int) (v + 32768) > 65535) + v = v < 0 ? -32768 : 32767; + *buffer++ = v; + } + for ( ; i < buf_c; ++i) + *buffer++ = 0; + } + } +} + +int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts) +{ + float **output; + int len; + if (num_c == 1) return stb_vorbis_get_frame_short(f,num_c,&buffer, num_shorts); + len = stb_vorbis_get_frame_float(f, NULL, &output); + if (len) { + if (len*num_c > num_shorts) len = num_shorts / num_c; + convert_channels_short_interleaved(num_c, buffer, f->channels, output, 0, len); + } + return len; +} + +int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts) +{ + float **outputs; + int len = num_shorts / channels; + int n=0; + int z = f->channels; + if (z > channels) z = channels; + while (n < len) { + int k = f->channel_buffer_end - f->channel_buffer_start; + if (n+k >= len) k = len - n; + if (k) + convert_channels_short_interleaved(channels, buffer, f->channels, f->channel_buffers, f->channel_buffer_start, k); + buffer += k*channels; + n += k; + f->channel_buffer_start += k; + if (n == len) break; + if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) break; + } + return n; +} + +int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int len) +{ + float **outputs; + int n=0; + int z = f->channels; + if (z > channels) z = channels; + while (n < len) { + int k = f->channel_buffer_end - f->channel_buffer_start; + if (n+k >= len) k = len - n; + if (k) + convert_samples_short(channels, buffer, n, f->channels, f->channel_buffers, f->channel_buffer_start, k); + n += k; + f->channel_buffer_start += k; + if (n == len) break; + if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) break; + } + return n; +} + +#ifndef STB_VORBIS_NO_STDIO +int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output) +{ + int data_len, offset, total, limit, error; + short *data; + stb_vorbis *v = stb_vorbis_open_filename(filename, &error, NULL); + if (v == NULL) return -1; + limit = v->channels * 4096; + *channels = v->channels; + if (sample_rate) + *sample_rate = v->sample_rate; + offset = data_len = 0; + total = limit; + data = (short *) malloc(total * sizeof(*data)); + if (data == NULL) { + stb_vorbis_close(v); + return -2; + } + for (;;) { + int n = stb_vorbis_get_frame_short_interleaved(v, v->channels, data+offset, total-offset); + if (n == 0) break; + data_len += n; + offset += n * v->channels; + if (offset + limit > total) { + short *data2; + total *= 2; + data2 = (short *) realloc(data, total * sizeof(*data)); + if (data2 == NULL) { + free(data); + stb_vorbis_close(v); + return -2; + } + data = data2; + } + } + *output = data; + stb_vorbis_close(v); + return data_len; +} +#endif // NO_STDIO + +int stb_vorbis_decode_memory(const uint8 *mem, int len, int *channels, int *sample_rate, short **output) +{ + int data_len, offset, total, limit, error; + short *data; + stb_vorbis *v = stb_vorbis_open_memory(mem, len, &error, NULL); + if (v == NULL) return -1; + limit = v->channels * 4096; + *channels = v->channels; + if (sample_rate) + *sample_rate = v->sample_rate; + offset = data_len = 0; + total = limit; + data = (short *) malloc(total * sizeof(*data)); + if (data == NULL) { + stb_vorbis_close(v); + return -2; + } + for (;;) { + int n = stb_vorbis_get_frame_short_interleaved(v, v->channels, data+offset, total-offset); + if (n == 0) break; + data_len += n; + offset += n * v->channels; + if (offset + limit > total) { + short *data2; + total *= 2; + data2 = (short *) realloc(data, total * sizeof(*data)); + if (data2 == NULL) { + free(data); + stb_vorbis_close(v); + return -2; + } + data = data2; + } + } + *output = data; + stb_vorbis_close(v); + return data_len; +} +#endif // STB_VORBIS_NO_INTEGER_CONVERSION + +int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats) +{ + float **outputs; + int len = num_floats / channels; + int n=0; + int z = f->channels; + if (z > channels) z = channels; + while (n < len) { + int i,j; + int k = f->channel_buffer_end - f->channel_buffer_start; + if (n+k >= len) k = len - n; + for (j=0; j < k; ++j) { + for (i=0; i < z; ++i) + *buffer++ = f->channel_buffers[i][f->channel_buffer_start+j]; + for ( ; i < channels; ++i) + *buffer++ = 0; + } + n += k; + f->channel_buffer_start += k; + if (n == len) + break; + if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) + break; + } + return n; +} + +int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples) +{ + float **outputs; + int n=0; + int z = f->channels; + if (z > channels) z = channels; + while (n < num_samples) { + int i; + int k = f->channel_buffer_end - f->channel_buffer_start; + if (n+k >= num_samples) k = num_samples - n; + if (k) { + for (i=0; i < z; ++i) + memcpy(buffer[i]+n, f->channel_buffers[i]+f->channel_buffer_start, sizeof(float)*k); + for ( ; i < channels; ++i) + memset(buffer[i]+n, 0, sizeof(float) * k); + } + n += k; + f->channel_buffer_start += k; + if (n == num_samples) + break; + if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) + break; + } + return n; +} +#endif // STB_VORBIS_NO_PULLDATA_API + +/* Version history + 1.17 - 2019-07-08 - fix CVE-2019-13217, -13218, -13219, -13220, -13221, -13222, -13223 + found with Mayhem by ForAllSecure + 1.16 - 2019-03-04 - fix warnings + 1.15 - 2019-02-07 - explicit failure if Ogg Skeleton data is found + 1.14 - 2018-02-11 - delete bogus dealloca usage + 1.13 - 2018-01-29 - fix truncation of last frame (hopefully) + 1.12 - 2017-11-21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files + 1.11 - 2017-07-23 - fix MinGW compilation + 1.10 - 2017-03-03 - more robust seeking; fix negative ilog(); clear error in open_memory + 1.09 - 2016-04-04 - back out 'avoid discarding last frame' fix from previous version + 1.08 - 2016-04-02 - fixed multiple warnings; fix setup memory leaks; + avoid discarding last frame of audio data + 1.07 - 2015-01-16 - fixed some warnings, fix mingw, const-correct API + some more crash fixes when out of memory or with corrupt files + 1.06 - 2015-08-31 - full, correct support for seeking API (Dougall Johnson) + some crash fixes when out of memory or with corrupt files + 1.05 - 2015-04-19 - don't define __forceinline if it's redundant + 1.04 - 2014-08-27 - fix missing const-correct case in API + 1.03 - 2014-08-07 - Warning fixes + 1.02 - 2014-07-09 - Declare qsort compare function _cdecl on windows + 1.01 - 2014-06-18 - fix stb_vorbis_get_samples_float + 1.0 - 2014-05-26 - fix memory leaks; fix warnings; fix bugs in multichannel + (API change) report sample rate for decode-full-file funcs + 0.99996 - bracket #include for macintosh compilation by Laurent Gomila + 0.99995 - use union instead of pointer-cast for fast-float-to-int to avoid alias-optimization problem + 0.99994 - change fast-float-to-int to work in single-precision FPU mode, remove endian-dependence + 0.99993 - remove assert that fired on legal files with empty tables + 0.99992 - rewind-to-start + 0.99991 - bugfix to stb_vorbis_get_samples_short by Bernhard Wodo + 0.9999 - (should have been 0.99990) fix no-CRT support, compiling as C++ + 0.9998 - add a full-decode function with a memory source + 0.9997 - fix a bug in the read-from-FILE case in 0.9996 addition + 0.9996 - query length of vorbis stream in samples/seconds + 0.9995 - bugfix to another optimization that only happened in certain files + 0.9994 - bugfix to one of the optimizations that caused significant (but inaudible?) errors + 0.9993 - performance improvements; runs in 99% to 104% of time of reference implementation + 0.9992 - performance improvement of IMDCT; now performs close to reference implementation + 0.9991 - performance improvement of IMDCT + 0.999 - (should have been 0.9990) performance improvement of IMDCT + 0.998 - no-CRT support from Casey Muratori + 0.997 - bugfixes for bugs found by Terje Mathisen + 0.996 - bugfix: fast-huffman decode initialized incorrectly for sparse codebooks; fixing gives 10% speedup - found by Terje Mathisen + 0.995 - bugfix: fix to 'effective' overrun detection - found by Terje Mathisen + 0.994 - bugfix: garbage decode on final VQ symbol of a non-multiple - found by Terje Mathisen + 0.993 - bugfix: pushdata API required 1 extra byte for empty page (failed to consume final page if empty) - found by Terje Mathisen + 0.992 - fixes for MinGW warning + 0.991 - turn fast-float-conversion on by default + 0.990 - fix push-mode seek recovery if you seek into the headers + 0.98b - fix to bad release of 0.98 + 0.98 - fix push-mode seek recovery; robustify float-to-int and support non-fast mode + 0.97 - builds under c++ (typecasting, don't use 'class' keyword) + 0.96 - somehow MY 0.95 was right, but the web one was wrong, so here's my 0.95 rereleased as 0.96, fixes a typo in the clamping code + 0.95 - clamping code for 16-bit functions + 0.94 - not publically released + 0.93 - fixed all-zero-floor case (was decoding garbage) + 0.92 - fixed a memory leak + 0.91 - conditional compiles to omit parts of the API and the infrastructure to support them: STB_VORBIS_NO_PULLDATA_API, STB_VORBIS_NO_PUSHDATA_API, STB_VORBIS_NO_STDIO, STB_VORBIS_NO_INTEGER_CONVERSION + 0.90 - first public release +*/ + +#endif // STB_VORBIS_HEADER_ONLY + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/