diff --git a/extras/miniaudio_split/miniaudio.c b/extras/miniaudio_split/miniaudio.c index 8a4e7fb8..c0391159 100644 --- a/extras/miniaudio_split/miniaudio.c +++ b/extras/miniaudio_split/miniaudio.c @@ -1,6 +1,6 @@ /* Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file. -miniaudio - v0.10.43 - 2021-12-10 +miniaudio - v0.11.0 - 2021-12-18 David Reid - mackron@gmail.com @@ -23,6 +23,9 @@ GitHub: https://github.com/mackron/miniaudio #include /* For strcasecmp(). */ #include /* For wcslen(), wcsrtombs() */ #endif +#ifdef _MSC_VER + #include /* For _controlfp_s constants */ +#endif #ifdef MA_WIN32 #include @@ -73,15 +76,10 @@ GitHub: https://github.com/mackron/miniaudio #define MA_X64 #elif defined(__i386) || defined(_M_IX86) #define MA_X86 -#elif defined(__arm__) || defined(_M_ARM) +#elif defined(__arm__) || defined(_M_ARM) || defined(__arm64) || defined(__arm64__) || defined(__aarch64__) || defined(_M_ARM64) #define MA_ARM #endif -/* Cannot currently support AVX-512 if AVX is disabled. */ -#if !defined(MA_NO_AVX512) && defined(MA_NO_AVX2) -#define MA_NO_AVX512 -#endif - /* Intrinsics Support */ #if defined(MA_X64) || defined(MA_X86) #if defined(_MSC_VER) && !defined(__clang__) @@ -95,9 +93,6 @@ GitHub: https://github.com/mackron/miniaudio #if _MSC_VER >= 1700 && !defined(MA_NO_AVX2) /* 2012 */ #define MA_SUPPORT_AVX2 #endif - #if _MSC_VER >= 1910 && !defined(MA_NO_AVX512) /* 2017 */ - #define MA_SUPPORT_AVX512 - #endif #else /* Assume GNUC-style. */ #if defined(__SSE2__) && !defined(MA_NO_SSE2) @@ -109,9 +104,6 @@ GitHub: https://github.com/mackron/miniaudio #if defined(__AVX2__) && !defined(MA_NO_AVX2) #define MA_SUPPORT_AVX2 #endif - #if defined(__AVX512F__) && !defined(MA_NO_AVX512) - #define MA_SUPPORT_AVX512 - #endif #endif /* If at this point we still haven't determined compiler support for the intrinsics just fall back to __has_include. */ @@ -125,14 +117,9 @@ GitHub: https://github.com/mackron/miniaudio #if !defined(MA_SUPPORT_AVX2) && !defined(MA_NO_AVX2) && __has_include() #define MA_SUPPORT_AVX2 #endif - #if !defined(MA_SUPPORT_AVX512) && !defined(MA_NO_AVX512) && __has_include() - #define MA_SUPPORT_AVX512 - #endif #endif - #if defined(MA_SUPPORT_AVX512) - #include /* Not a mistake. Intentionally including instead of because otherwise the compiler will complain. */ - #elif defined(MA_SUPPORT_AVX2) || defined(MA_SUPPORT_AVX) + #if defined(MA_SUPPORT_AVX2) || defined(MA_SUPPORT_AVX) #include #elif defined(MA_SUPPORT_SSE2) #include @@ -160,6 +147,7 @@ GitHub: https://github.com/mackron/miniaudio #if defined(_MSC_VER) #pragma warning(push) #pragma warning(disable:4752) /* found Intel(R) Advanced Vector Extensions; consider using /arch:AVX */ + #pragma warning(disable:4049) /* compiler limit : terminating line number emission */ #endif #if defined(MA_X64) || defined(MA_X86) @@ -321,41 +309,6 @@ static MA_INLINE ma_bool32 ma_has_avx2(void) #endif } -static MA_INLINE ma_bool32 ma_has_avx512f(void) -{ -#if defined(MA_SUPPORT_AVX512) - #if (defined(MA_X64) || defined(MA_X86)) && !defined(MA_NO_AVX512) - #if defined(__AVX512F__) - return MA_TRUE; /* If the compiler is allowed to freely generate AVX-512F code we can assume support. */ - #else - /* AVX-512 requires both CPU and OS support. */ - #if defined(MA_NO_CPUID) || defined(MA_NO_XGETBV) - return MA_FALSE; - #else - int info1[4]; - int info7[4]; - ma_cpuid(info1, 1); - ma_cpuid(info7, 7); - if (((info1[2] & (1 << 27)) != 0) && ((info7[1] & (1 << 16)) != 0)) { - ma_uint64 xrc = ma_xgetbv(0); - if ((xrc & 0xE6) == 0xE6) { - return MA_TRUE; - } else { - return MA_FALSE; - } - } else { - return MA_FALSE; - } - #endif - #endif - #else - return MA_FALSE; /* AVX-512F is only supported on x86 and x64 architectures. */ - #endif -#else - return MA_FALSE; /* No compiler support. */ -#endif -} - static MA_INLINE ma_bool32 ma_has_neon(void) { #if defined(MA_SUPPORT_NEON) @@ -405,7 +358,7 @@ static MA_INLINE ma_bool32 ma_has_neon(void) #elif defined(_MSC_VER) #define MA_ASSUME(x) __assume(x) #else - #define MA_ASSUME(x) while(0) + #define MA_ASSUME(x) (void)(x) #endif #endif @@ -504,7 +457,7 @@ static void ma_sleep__posix(ma_uint32 milliseconds) (void)milliseconds; MA_ASSERT(MA_FALSE); /* The Emscripten build should never sleep. */ #else - #if _POSIX_C_SOURCE >= 199309L + #if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309L struct timespec ts; ts.tv_sec = milliseconds / 1000; ts.tv_nsec = milliseconds % 1000 * 1000000; @@ -519,7 +472,7 @@ static void ma_sleep__posix(ma_uint32 milliseconds) } #endif -static void ma_sleep(ma_uint32 milliseconds) +static MA_INLINE void ma_sleep(ma_uint32 milliseconds) { #ifdef MA_WIN32 ma_sleep__win32(milliseconds); @@ -548,7 +501,7 @@ static MA_INLINE void ma_yield() #else __asm__ __volatile__ ("pause"); #endif -#elif (defined(__arm__) && defined(__ARM_ARCH) && __ARM_ARCH >= 7) || (defined(_M_ARM) && _M_ARM >= 7) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6T2__) +#elif (defined(__arm__) && defined(__ARM_ARCH) && __ARM_ARCH >= 7) || defined(_M_ARM64) || (defined(_M_ARM) && _M_ARM >= 7) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6T2__) /* ARM */ #if defined(_MSC_VER) /* Apparently there is a __yield() intrinsic that's compatible with ARM, but I cannot find documentation for it nor can I find where it's declared. */ @@ -562,6 +515,96 @@ static MA_INLINE void ma_yield() } +#define MA_MM_DENORMALS_ZERO_MASK 0x0040 +#define MA_MM_FLUSH_ZERO_MASK 0x8000 + +static MA_INLINE unsigned int ma_disable_denormals() +{ + unsigned int prevState; + + #if defined(_MSC_VER) + { + /* + Older versions of Visual Studio don't support the "safe" versions of _controlfp_s(). I don't + know which version of Visual Studio first added support for _controlfp_s(), but I do know + that VC6 lacks support. _MSC_VER = 1200 is VC6, but if you get compilation errors on older + versions of Visual Studio, let me know and I'll make the necessary adjustment. + */ + #if _MSC_VER <= 1200 + { + prevState = _statusfp(); + _controlfp(prevState | _DN_FLUSH, _MCW_DN); + } + #else + { + unsigned int unused; + _controlfp_s(&prevState, 0, 0); + _controlfp_s(&unused, prevState | _DN_FLUSH, _MCW_DN); + } + #endif + } + #elif defined(MA_X86) || defined(MA_X64) + { + #if defined(__SSE2__) && !(defined(__TINYC__) || defined(__WATCOMC__)) /* <-- Add compilers that lack support for _mm_getcsr() and _mm_setcsr() to this list. */ + { + prevState = _mm_getcsr(); + _mm_setcsr(prevState | MA_MM_DENORMALS_ZERO_MASK | MA_MM_FLUSH_ZERO_MASK); + } + #else + { + /* x88/64, but no support for _mm_getcsr()/_mm_setcsr(). May need to fall back to inlined assembly here. */ + prevState = 0; + } + #endif + } + #else + { + /* Unknown or unsupported architecture. No-op. */ + prevState = 0; + } + #endif + + return prevState; +} + +static MA_INLINE void ma_restore_denormals(unsigned int prevState) +{ + #if defined(_MSC_VER) + { + /* Older versions of Visual Studio do not support _controlfp_s(). See ma_disable_denormals(). */ + #if _MSC_VER <= 1200 + { + _controlfp(prevState, _MCW_DN); + } + #else + { + unsigned int unused; + _controlfp_s(&unused, prevState, _MCW_DN); + } + #endif + } + #elif defined(MA_X86) || defined(MA_X64) + { + #if defined(__SSE2__) && !(defined(__TINYC__) || defined(__WATCOMC__)) /* <-- Add compilers that lack support for _mm_getcsr() and _mm_setcsr() to this list. */ + { + _mm_setcsr(prevState); + } + #else + { + /* x88/64, but no support for _mm_getcsr()/_mm_setcsr(). May need to fall back to inlined assembly here. */ + (void)prevState; + } + #endif + } + #else + { + /* Unknown or unsupported architecture. No-op. */ + (void)prevState; + } + #endif +} + + #ifndef MA_COINIT_VALUE #define MA_COINIT_VALUE 0 /* 0 = COINIT_MULTITHREADED */ @@ -1838,76 +1881,6 @@ static void ma__free_default(void* p, void* pUserData) MA_FREE(p); } - -static void* ma__malloc_from_callbacks(size_t sz, const ma_allocation_callbacks* pAllocationCallbacks) -{ - if (pAllocationCallbacks == NULL) { - return NULL; - } - - if (pAllocationCallbacks->onMalloc != NULL) { - return pAllocationCallbacks->onMalloc(sz, pAllocationCallbacks->pUserData); - } - - /* Try using realloc(). */ - if (pAllocationCallbacks->onRealloc != NULL) { - return pAllocationCallbacks->onRealloc(NULL, sz, pAllocationCallbacks->pUserData); - } - - return NULL; -} - -static void* ma__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, const ma_allocation_callbacks* pAllocationCallbacks) -{ - if (pAllocationCallbacks == NULL) { - return NULL; - } - - if (pAllocationCallbacks->onRealloc != NULL) { - return pAllocationCallbacks->onRealloc(p, szNew, pAllocationCallbacks->pUserData); - } - - /* Try emulating realloc() in terms of malloc()/free(). */ - if (pAllocationCallbacks->onMalloc != NULL && pAllocationCallbacks->onFree != NULL) { - void* p2; - - p2 = pAllocationCallbacks->onMalloc(szNew, pAllocationCallbacks->pUserData); - if (p2 == NULL) { - return NULL; - } - - if (p != NULL) { - MA_COPY_MEMORY(p2, p, szOld); - pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); - } - - return p2; - } - - return NULL; -} - -static MA_INLINE void* ma__calloc_from_callbacks(size_t sz, const ma_allocation_callbacks* pAllocationCallbacks) -{ - void* p = ma__malloc_from_callbacks(sz, pAllocationCallbacks); - if (p != NULL) { - MA_ZERO_MEMORY(p, sz); - } - - return p; -} - -static void ma__free_from_callbacks(void* p, const ma_allocation_callbacks* pAllocationCallbacks) -{ - if (p == NULL || pAllocationCallbacks == NULL) { - return; - } - - if (pAllocationCallbacks->onFree != NULL) { - pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); - } -} - static ma_allocation_callbacks ma_allocation_callbacks_init_default(void) { ma_allocation_callbacks callbacks; @@ -2018,7 +1991,7 @@ MA_API ma_result ma_log_init(const ma_allocation_callbacks* pAllocationCallbacks } } #endif - + /* If we're using debug output, enable it. */ #if defined(MA_DEBUG_OUTPUT) { @@ -2334,8 +2307,32 @@ MA_API ma_result ma_log_postf(ma_log* pLog, ma_uint32 level, const char* pFormat +static MA_INLINE ma_uint8 ma_clip_u8(ma_int32 x) +{ + return (ma_uint8)(ma_clamp(x, -128, 127) + 128); +} + +static MA_INLINE ma_int16 ma_clip_s16(ma_int32 x) +{ + return (ma_int16)ma_clamp(x, -32768, 32767); +} + +static MA_INLINE ma_int64 ma_clip_s24(ma_int64 x) +{ + return (ma_int64)ma_clamp(x, -8388608, 8388607); +} + +static MA_INLINE ma_int32 ma_clip_s32(ma_int64 x) +{ + /* This dance is to silence warnings with -std=c89. A good compiler should be able to optimize this away. */ + ma_int64 clipMin; + ma_int64 clipMax; + clipMin = -((ma_int64)2147483647 + 1); + clipMax = (ma_int64)2147483647; + + return (ma_int32)ma_clamp(x, clipMin, clipMax); +} -/* Clamps an f32 sample to -1..1 */ static MA_INLINE float ma_clip_f32(float x) { if (x < -1) return -1; @@ -2343,6 +2340,7 @@ static MA_INLINE float ma_clip_f32(float x) return x; } + static MA_INLINE float ma_mix_f32(float x, float y, float a) { return x*(1-a) + y*a; @@ -2367,12 +2365,6 @@ static MA_INLINE __m256 ma_mix_f32_fast__avx2(__m256 x, __m256 y, __m256 a) return _mm256_add_ps(x, _mm256_mul_ps(_mm256_sub_ps(y, x), a)); } #endif -#if defined(MA_SUPPORT_AVX512) -static MA_INLINE __m512 ma_mix_f32_fast__avx512(__m512 x, __m512 y, __m512 a) -{ - return _mm512_add_ps(x, _mm512_mul_ps(_mm512_sub_ps(y, x), a)); -} -#endif #if defined(MA_SUPPORT_NEON) static MA_INLINE float32x4_t ma_mix_f32_fast__neon(float32x4_t x, float32x4_t y, float32x4_t a) { @@ -2415,6 +2407,27 @@ static MA_INLINE ma_uint32 ma_gcf_u32(ma_uint32 a, ma_uint32 b) } +static ma_uint32 ma_ffs_32(ma_uint32 x) +{ + ma_uint32 i; + + /* Just a naive implementation just to get things working for now. Will optimize this later. */ + for (i = 0; i < 32; i += 1) { + if ((x & (1 << i)) != 0) { + return i; + } + } + + return i; +} + +static MA_INLINE ma_int16 ma_float_to_fixed_16(float x) +{ + return (ma_int16)(x * (1 << 8)); +} + + + /* Random Number Generation @@ -2575,7 +2588,7 @@ typedef signed short c89atomic_int16; typedef unsigned short c89atomic_uint16; typedef signed int c89atomic_int32; typedef unsigned int c89atomic_uint32; -#if defined(_MSC_VER) +#if defined(_MSC_VER) && !defined(__clang__) typedef signed __int64 c89atomic_int64; typedef unsigned __int64 c89atomic_uint64; #else @@ -2733,7 +2746,7 @@ typedef unsigned char c89atomic_bool; #define c89atomic_compare_and_swap_32(dst, expected, desired) (c89atomic_uint32)_InterlockedCompareExchange((volatile long*)dst, (long)desired, (long)expected) #endif #if defined(C89ATOMIC_HAS_64) - #define c89atomic_compare_and_swap_64(dst, expected, desired) (c89atomic_uint64)_InterlockedCompareExchange64((volatile long long*)dst, (long long)desired, (long long)expected) + #define c89atomic_compare_and_swap_64(dst, expected, desired) (c89atomic_uint64)_InterlockedCompareExchange64((volatile c89atomic_int64*)dst, (c89atomic_int64)desired, (c89atomic_int64)expected) #endif #endif #if defined(C89ATOMIC_MSVC_USE_INLINED_ASSEMBLY) @@ -3920,11 +3933,11 @@ typedef unsigned char c89atomic_bool; { return (void*)c89atomic_exchange_explicit_64((volatile c89atomic_uint64*)dst, (c89atomic_uint64)src, order); } - static C89ATOMIC_INLINE c89atomic_bool c89atomic_compare_exchange_strong_explicit_ptr(volatile void** dst, volatile void** expected, void* desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) + static C89ATOMIC_INLINE c89atomic_bool c89atomic_compare_exchange_strong_explicit_ptr(volatile void** dst, void** expected, void* desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) { return c89atomic_compare_exchange_strong_explicit_64((volatile c89atomic_uint64*)dst, (c89atomic_uint64*)expected, (c89atomic_uint64)desired, successOrder, failureOrder); } - static C89ATOMIC_INLINE c89atomic_bool c89atomic_compare_exchange_weak_explicit_ptr(volatile void** dst, volatile void** expected, void* desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) + static C89ATOMIC_INLINE c89atomic_bool c89atomic_compare_exchange_weak_explicit_ptr(volatile void** dst, void** expected, void* desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) { return c89atomic_compare_exchange_weak_explicit_64((volatile c89atomic_uint64*)dst, (c89atomic_uint64*)expected, (c89atomic_uint64)desired, successOrder, failureOrder); } @@ -3953,7 +3966,7 @@ typedef unsigned char c89atomic_bool; { return c89atomic_compare_exchange_strong_explicit_32((volatile c89atomic_uint32*)dst, (c89atomic_uint32*)expected, (c89atomic_uint32)desired, successOrder, failureOrder); } - static C89ATOMIC_INLINE c89atomic_bool c89atomic_compare_exchange_weak_explicit_ptr(volatile void** dst, volatile void** expected, void* desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) + static C89ATOMIC_INLINE c89atomic_bool c89atomic_compare_exchange_weak_explicit_ptr(volatile void** dst, void** expected, void* desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) { return c89atomic_compare_exchange_weak_explicit_32((volatile c89atomic_uint32*)dst, (c89atomic_uint32*)expected, (c89atomic_uint32)desired, successOrder, failureOrder); } @@ -3969,8 +3982,8 @@ typedef unsigned char c89atomic_bool; #define c89atomic_store_ptr(dst, src) c89atomic_store_explicit_ptr((volatile void**)dst, (void*)src, c89atomic_memory_order_seq_cst) #define c89atomic_load_ptr(ptr) c89atomic_load_explicit_ptr((volatile void**)ptr, c89atomic_memory_order_seq_cst) #define c89atomic_exchange_ptr(dst, src) c89atomic_exchange_explicit_ptr((volatile void**)dst, (void*)src, c89atomic_memory_order_seq_cst) -#define c89atomic_compare_exchange_strong_ptr(dst, expected, desired) c89atomic_compare_exchange_strong_explicit_ptr((volatile void**)dst, (volatile void**)expected, (void*)desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) -#define c89atomic_compare_exchange_weak_ptr(dst, expected, desired) c89atomic_compare_exchange_weak_explicit_ptr((volatile void**)dst, (volatile void**)expected, (void*)desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_strong_ptr(dst, expected, desired) c89atomic_compare_exchange_strong_explicit_ptr((volatile void**)dst, (void**)expected, (void*)desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_weak_ptr(dst, expected, desired) c89atomic_compare_exchange_weak_explicit_ptr((volatile void**)dst, (void**)expected, (void*)desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) #define c89atomic_test_and_set_8( ptr) c89atomic_test_and_set_explicit_8( ptr, c89atomic_memory_order_seq_cst) #define c89atomic_test_and_set_16(ptr) c89atomic_test_and_set_explicit_16(ptr, c89atomic_memory_order_seq_cst) #define c89atomic_test_and_set_32(ptr) c89atomic_test_and_set_explicit_32(ptr, c89atomic_memory_order_seq_cst) @@ -4143,16 +4156,16 @@ static C89ATOMIC_INLINE void c89atomic_store_explicit_f64(volatile double* dst, x.f = src; c89atomic_store_explicit_64((volatile c89atomic_uint64*)dst, x.i, order); } -static C89ATOMIC_INLINE float c89atomic_load_explicit_f32(volatile float* ptr, c89atomic_memory_order order) +static C89ATOMIC_INLINE float c89atomic_load_explicit_f32(volatile const float* ptr, c89atomic_memory_order order) { c89atomic_if32 r; - r.i = c89atomic_load_explicit_32((volatile c89atomic_uint32*)ptr, order); + r.i = c89atomic_load_explicit_32((volatile const c89atomic_uint32*)ptr, order); return r.f; } -static C89ATOMIC_INLINE double c89atomic_load_explicit_f64(volatile double* ptr, c89atomic_memory_order order) +static C89ATOMIC_INLINE double c89atomic_load_explicit_f64(volatile const double* ptr, c89atomic_memory_order order) { c89atomic_if64 r; - r.i = c89atomic_load_explicit_64((volatile c89atomic_uint64*)ptr, order); + r.i = c89atomic_load_explicit_64((volatile const c89atomic_uint64*)ptr, order); return r.f; } static C89ATOMIC_INLINE float c89atomic_exchange_explicit_f32(volatile float* dst, float src, c89atomic_memory_order order) @@ -4204,26 +4217,29 @@ static C89ATOMIC_INLINE void c89atomic_spinlock_unlock(volatile c89atomic_spinlo MA_API ma_uint64 ma_calculate_frame_count_after_resampling(ma_uint32 sampleRateOut, ma_uint32 sampleRateIn, ma_uint64 frameCountIn) { - /* For robustness we're going to use a resampler object to calculate this since that already has a way of calculating this. */ - ma_result result; - ma_uint64 frameCountOut; - ma_resampler_config config; - ma_resampler resampler; + /* This is based on the calculation in ma_linear_resampler_get_expected_output_frame_count(). */ + ma_uint64 outputFrameCount; + ma_uint64 preliminaryInputFrameCountFromFrac; + ma_uint64 preliminaryInputFrameCount; + + if (sampleRateIn == 0 || sampleRateOut == 0 || frameCountIn == 0) { + return 0; + } if (sampleRateOut == sampleRateIn) { return frameCountIn; } - config = ma_resampler_config_init(ma_format_s16, 1, sampleRateIn, sampleRateOut, ma_resample_algorithm_linear); - result = ma_resampler_init(&config, &resampler); - if (result != MA_SUCCESS) { - return 0; + outputFrameCount = (frameCountIn * sampleRateOut) / sampleRateIn; + + preliminaryInputFrameCountFromFrac = (outputFrameCount * (sampleRateIn / sampleRateOut)) / sampleRateOut; + preliminaryInputFrameCount = (outputFrameCount * (sampleRateIn % sampleRateOut)) + preliminaryInputFrameCountFromFrac; + + if (preliminaryInputFrameCount <= frameCountIn) { + outputFrameCount += 1; } - frameCountOut = ma_resampler_get_expected_output_frame_count(&resampler, frameCountIn); - - ma_resampler_uninit(&resampler); - return frameCountOut; + return outputFrameCount; } #ifndef MA_DATA_CONVERTER_STACK_BUFFER_SIZE @@ -4261,16 +4277,6 @@ static ma_result ma_result_from_GetLastError(DWORD error) Threading *******************************************************************************/ -#ifndef MA_NO_THREADING -#ifdef MA_WIN32 - #define MA_THREADCALL WINAPI - typedef unsigned long ma_thread_result; -#else - #define MA_THREADCALL - typedef void* ma_thread_result; -#endif -typedef ma_thread_result (MA_THREADCALL * ma_thread_entry_proc)(void* pData); - static MA_INLINE ma_result ma_spinlock_lock_ex(volatile ma_spinlock* pSpinlock, ma_bool32 yield) { if (pSpinlock == NULL) { @@ -4312,6 +4318,17 @@ MA_API ma_result ma_spinlock_unlock(volatile ma_spinlock* pSpinlock) return MA_SUCCESS; } + +#ifndef MA_NO_THREADING +#ifdef MA_WIN32 + #define MA_THREADCALL WINAPI + typedef unsigned long ma_thread_result; +#else + #define MA_THREADCALL + typedef void* ma_thread_result; +#endif +typedef ma_thread_result (MA_THREADCALL * ma_thread_entry_proc)(void* pData); + #ifdef MA_WIN32 static int ma_thread_priority_to_win32(ma_thread_priority priority) { @@ -4857,14 +4874,14 @@ static ma_result ma_event_alloc_and_init(ma_event** ppEvent, ma_allocation_callb *ppEvent = NULL; - pEvent = ma_malloc(sizeof(*pEvent), pAllocationCallbacks/*, MA_ALLOCATION_TYPE_EVENT*/); + pEvent = ma_malloc(sizeof(*pEvent), pAllocationCallbacks); if (pEvent == NULL) { return MA_OUT_OF_MEMORY; } result = ma_event_init(pEvent); if (result != MA_SUCCESS) { - ma_free(pEvent, pAllocationCallbacks/*, MA_ALLOCATION_TYPE_EVENT*/); + ma_free(pEvent, pAllocationCallbacks); return result; } @@ -4895,7 +4912,7 @@ static void ma_event_uninit_and_free(ma_event* pEvent, ma_allocation_callbacks* } ma_event_uninit(pEvent); - ma_free(pEvent, pAllocationCallbacks/*, MA_ALLOCATION_TYPE_EVENT*/); + ma_free(pEvent, pAllocationCallbacks); } #endif @@ -4998,6 +5015,279 @@ MA_API ma_result ma_semaphore_release(ma_semaphore* pSemaphore) +#define MA_FENCE_COUNTER_MAX 0x7FFFFFFF + +MA_API ma_result ma_fence_init(ma_fence* pFence) +{ + if (pFence == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pFence); + pFence->counter = 0; + + #ifndef MA_NO_THREADING + { + ma_result result; + + result = ma_event_init(&pFence->e); + if (result != MA_SUCCESS) { + return result; + } + } + #endif + + return MA_SUCCESS; +} + +MA_API void ma_fence_uninit(ma_fence* pFence) +{ + if (pFence == NULL) { + return; + } + + #ifndef MA_NO_THREADING + { + ma_event_uninit(&pFence->e); + } + #endif + + MA_ZERO_OBJECT(pFence); +} + +MA_API ma_result ma_fence_acquire(ma_fence* pFence) +{ + if (pFence == NULL) { + return MA_INVALID_ARGS; + } + + for (;;) { + ma_uint32 oldCounter = c89atomic_load_32(&pFence->counter); + ma_uint32 newCounter = oldCounter + 1; + + /* Make sure we're not about to exceed our maximum value. */ + if (newCounter > MA_FENCE_COUNTER_MAX) { + MA_ASSERT(MA_FALSE); + return MA_OUT_OF_RANGE; + } + + if (c89atomic_compare_exchange_weak_32(&pFence->counter, &oldCounter, newCounter)) { + return MA_SUCCESS; + } else { + if (oldCounter == MA_FENCE_COUNTER_MAX) { + MA_ASSERT(MA_FALSE); + return MA_OUT_OF_RANGE; /* The other thread took the last available slot. Abort. */ + } + } + } + + /* Should never get here. */ + /*return MA_SUCCESS;*/ +} + +MA_API ma_result ma_fence_release(ma_fence* pFence) +{ + if (pFence == NULL) { + return MA_INVALID_ARGS; + } + + for (;;) { + ma_uint32 oldCounter = c89atomic_load_32(&pFence->counter); + ma_uint32 newCounter = oldCounter - 1; + + if (oldCounter == 0) { + MA_ASSERT(MA_FALSE); + return MA_INVALID_OPERATION; /* Acquire/release mismatch. */ + } + + if (c89atomic_compare_exchange_weak_32(&pFence->counter, &oldCounter, newCounter)) { + #ifndef MA_NO_THREADING + { + if (newCounter == 0) { + ma_event_signal(&pFence->e); /* <-- ma_fence_wait() will be waiting on this. */ + } + } + #endif + + return MA_SUCCESS; + } else { + if (oldCounter == 0) { + MA_ASSERT(MA_FALSE); + return MA_INVALID_OPERATION; /* Another thread has taken the 0 slot. Acquire/release mismatch. */ + } + } + } + + /* Should never get here. */ + /*return MA_SUCCESS;*/ +} + +MA_API ma_result ma_fence_wait(ma_fence* pFence) +{ + if (pFence == NULL) { + return MA_INVALID_ARGS; + } + + for (;;) { + ma_uint32 counter; + + counter = c89atomic_load_32(&pFence->counter); + if (counter == 0) { + /* + Counter has hit zero. By the time we get here some other thread may have acquired the + fence again, but that is where the caller needs to take care with how they se the fence. + */ + return MA_SUCCESS; + } + + /* Getting here means the counter is > 0. We'll need to wait for something to happen. */ + #ifndef MA_NO_THREADING + { + ma_result result; + + result = ma_event_wait(&pFence->e); + if (result != MA_SUCCESS) { + return result; + } + } + #endif + } + + /* Should never get here. */ + /*return MA_INVALID_OPERATION;*/ +} + + +MA_API ma_result ma_async_notification_signal(ma_async_notification* pNotification) +{ + ma_async_notification_callbacks* pNotificationCallbacks = (ma_async_notification_callbacks*)pNotification; + + if (pNotification == NULL) { + return MA_INVALID_ARGS; + } + + if (pNotificationCallbacks->onSignal == NULL) { + return MA_NOT_IMPLEMENTED; + } + + pNotificationCallbacks->onSignal(pNotification); + return MA_INVALID_ARGS; +} + + +static void ma_async_notification_poll__on_signal(ma_async_notification* pNotification) +{ + ((ma_async_notification_poll*)pNotification)->signalled = MA_TRUE; +} + +MA_API ma_result ma_async_notification_poll_init(ma_async_notification_poll* pNotificationPoll) +{ + if (pNotificationPoll == NULL) { + return MA_INVALID_ARGS; + } + + pNotificationPoll->cb.onSignal = ma_async_notification_poll__on_signal; + pNotificationPoll->signalled = MA_FALSE; + + return MA_SUCCESS; +} + +MA_API ma_bool32 ma_async_notification_poll_is_signalled(const ma_async_notification_poll* pNotificationPoll) +{ + if (pNotificationPoll == NULL) { + return MA_FALSE; + } + + return pNotificationPoll->signalled; +} + + +static void ma_async_notification_event__on_signal(ma_async_notification* pNotification) +{ + ma_async_notification_event_signal((ma_async_notification_event*)pNotification); +} + +MA_API ma_result ma_async_notification_event_init(ma_async_notification_event* pNotificationEvent) +{ + if (pNotificationEvent == NULL) { + return MA_INVALID_ARGS; + } + + pNotificationEvent->cb.onSignal = ma_async_notification_event__on_signal; + + #ifndef MA_NO_THREADING + { + ma_result result; + + result = ma_event_init(&pNotificationEvent->e); + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; + } + #else + { + return MA_NOT_IMPLEMENTED; /* Threading is disabled. */ + } + #endif +} + +MA_API ma_result ma_async_notification_event_uninit(ma_async_notification_event* pNotificationEvent) +{ + if (pNotificationEvent == NULL) { + return MA_INVALID_ARGS; + } + + #ifndef MA_NO_THREADING + { + ma_event_uninit(&pNotificationEvent->e); + return MA_SUCCESS; + } + #else + { + return MA_NOT_IMPLEMENTED; /* Threading is disabled. */ + } + #endif +} + +MA_API ma_result ma_async_notification_event_wait(ma_async_notification_event* pNotificationEvent) +{ + if (pNotificationEvent == NULL) { + return MA_INVALID_ARGS; + } + + #ifndef MA_NO_THREADING + { + return ma_event_wait(&pNotificationEvent->e); + } + #else + { + return MA_NOT_IMPLEMENTED; /* Threading is disabled. */ + } + #endif +} + +MA_API ma_result ma_async_notification_event_signal(ma_async_notification_event* pNotificationEvent) +{ + if (pNotificationEvent == NULL) { + return MA_INVALID_ARGS; + } + + #ifndef MA_NO_THREADING + { + return ma_event_signal(&pNotificationEvent->e); + } + #else + { + return MA_NOT_IMPLEMENTED; /* Threading is disabled. */ + } + #endif +} + + + + /************************************************************************************************************************************************************ ************************************************************************************************************************************************************* @@ -5422,47 +5712,6 @@ typedef LONG (WINAPI * MA_PFN_RegQueryValueExA)(HKEY hKey, LPCSTR lpValueName, L #define MA_DEFAULT_CAPTURE_DEVICE_NAME "Default Capture Device" -/* Posts a log message. */ -static void ma_post_log_message(ma_context* pContext, ma_device* pDevice, ma_uint32 logLevel, const char* message) -{ - if (pContext == NULL) { - if (pDevice != NULL) { - pContext = pDevice->pContext; - } - } - - if (pContext == NULL) { - return; - } - - ma_log_post(ma_context_get_log(pContext), logLevel, message); /* <-- This will deal with MA_DEBUG_OUTPUT. */ - - /* Legacy. */ -#if defined(MA_LOG_LEVEL) - if (logLevel <= MA_LOG_LEVEL) { - ma_log_proc onLog; - - onLog = pContext->logCallback; - if (onLog) { - onLog(pContext, pDevice, logLevel, message); - } - } -#endif -} - -/* Posts an log message. Throw a breakpoint in here if you're needing to debug. The return value is always "resultCode". */ -static ma_result ma_context_post_error(ma_context* pContext, ma_device* pDevice, ma_uint32 logLevel, const char* message, ma_result resultCode) -{ - ma_post_log_message(pContext, pDevice, logLevel, message); - return resultCode; -} - -static ma_result ma_post_error(ma_device* pDevice, ma_uint32 logLevel, const char* message, ma_result resultCode) -{ - return ma_context_post_error(ma_device_get_context(pDevice), pDevice, logLevel, message, resultCode); -} - - /******************************************************************************* @@ -5472,7 +5721,7 @@ Timing *******************************************************************************/ #ifdef MA_WIN32 static LARGE_INTEGER g_ma_TimerFrequency; /* <-- Initialized to zero since it's static. */ - static void ma_timer_init(ma_timer* pTimer) + void ma_timer_init(ma_timer* pTimer) { LARGE_INTEGER counter; @@ -5484,7 +5733,7 @@ Timing pTimer->counter = counter.QuadPart; } - static double ma_timer_get_time_in_seconds(ma_timer* pTimer) + double ma_timer_get_time_in_seconds(ma_timer* pTimer) { LARGE_INTEGER counter; if (!QueryPerformanceCounter(&counter)) { @@ -5522,7 +5771,7 @@ Timing return (emscripten_get_now() - pTimer->counterD) / 1000; /* Emscripten is in milliseconds. */ } #else - #if _POSIX_C_SOURCE >= 199309L + #if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309L #if defined(CLOCK_MONOTONIC) #define MA_CLOCK_ID CLOCK_MONOTONIC #else @@ -5686,6 +5935,29 @@ static ma_uint32 ma_get_closest_standard_sample_rate(ma_uint32 sampleRateIn) #endif +static MA_INLINE unsigned int ma_device_disable_denormals(ma_device* pDevice) +{ + MA_ASSERT(pDevice != NULL); + + if (!pDevice->noDisableDenormals) { + return ma_disable_denormals(); + } else { + return 0; + } +} + +static MA_INLINE void ma_device_restore_denormals(ma_device* pDevice, unsigned int prevState) +{ + MA_ASSERT(pDevice != NULL); + + if (!pDevice->noDisableDenormals) { + ma_restore_denormals(prevState); + } else { + /* Do nothing. */ + (void)prevState; + } +} + static void ma_device__on_data(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) { float masterVolumeFactor; @@ -5693,44 +5965,48 @@ static void ma_device__on_data(ma_device* pDevice, void* pFramesOut, const void* ma_device_get_master_volume(pDevice, &masterVolumeFactor); /* Use ma_device_get_master_volume() to ensure the volume is loaded atomically. */ if (pDevice->onData) { - if (!pDevice->noPreZeroedOutputBuffer && pFramesOut != NULL) { - ma_silence_pcm_frames(pFramesOut, frameCount, pDevice->playback.format, pDevice->playback.channels); - } + unsigned int prevDenormalState = ma_device_disable_denormals(pDevice); + { + if (!pDevice->noPreSilencedOutputBuffer && pFramesOut != NULL) { + ma_silence_pcm_frames(pFramesOut, frameCount, pDevice->playback.format, pDevice->playback.channels); + } - /* Volume control of input makes things a bit awkward because the input buffer is read-only. We'll need to use a temp buffer and loop in this case. */ - if (pFramesIn != NULL && masterVolumeFactor < 1) { - ma_uint8 tempFramesIn[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 bpfCapture = ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); - ma_uint32 bpfPlayback = ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); - ma_uint32 totalFramesProcessed = 0; - while (totalFramesProcessed < frameCount) { - ma_uint32 framesToProcessThisIteration = frameCount - totalFramesProcessed; - if (framesToProcessThisIteration > sizeof(tempFramesIn)/bpfCapture) { - framesToProcessThisIteration = sizeof(tempFramesIn)/bpfCapture; + /* Volume control of input makes things a bit awkward because the input buffer is read-only. We'll need to use a temp buffer and loop in this case. */ + if (pFramesIn != NULL && masterVolumeFactor < 1) { + ma_uint8 tempFramesIn[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; + ma_uint32 bpfCapture = ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); + ma_uint32 bpfPlayback = ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); + ma_uint32 totalFramesProcessed = 0; + while (totalFramesProcessed < frameCount) { + ma_uint32 framesToProcessThisIteration = frameCount - totalFramesProcessed; + if (framesToProcessThisIteration > sizeof(tempFramesIn)/bpfCapture) { + framesToProcessThisIteration = sizeof(tempFramesIn)/bpfCapture; + } + + ma_copy_and_apply_volume_factor_pcm_frames(tempFramesIn, ma_offset_ptr(pFramesIn, totalFramesProcessed*bpfCapture), framesToProcessThisIteration, pDevice->capture.format, pDevice->capture.channels, masterVolumeFactor); + + pDevice->onData(pDevice, ma_offset_ptr(pFramesOut, totalFramesProcessed*bpfPlayback), tempFramesIn, framesToProcessThisIteration); + + totalFramesProcessed += framesToProcessThisIteration; + } + } else { + pDevice->onData(pDevice, pFramesOut, pFramesIn, frameCount); + } + + /* Volume control and clipping for playback devices. */ + if (pFramesOut != NULL) { + if (masterVolumeFactor < 1) { + if (pFramesIn == NULL) { /* <-- In full-duplex situations, the volume will have been applied to the input samples before the data callback. Applying it again post-callback will incorrectly compound it. */ + ma_apply_volume_factor_pcm_frames(pFramesOut, frameCount, pDevice->playback.format, pDevice->playback.channels, masterVolumeFactor); + } } - ma_copy_and_apply_volume_factor_pcm_frames(tempFramesIn, ma_offset_ptr(pFramesIn, totalFramesProcessed*bpfCapture), framesToProcessThisIteration, pDevice->capture.format, pDevice->capture.channels, masterVolumeFactor); - - pDevice->onData(pDevice, ma_offset_ptr(pFramesOut, totalFramesProcessed*bpfPlayback), tempFramesIn, framesToProcessThisIteration); - - totalFramesProcessed += framesToProcessThisIteration; - } - } else { - pDevice->onData(pDevice, pFramesOut, pFramesIn, frameCount); - } - - /* Volume control and clipping for playback devices. */ - if (pFramesOut != NULL) { - if (masterVolumeFactor < 1) { - if (pFramesIn == NULL) { /* <-- In full-duplex situations, the volume will have been applied to the input samples before the data callback. Applying it again post-callback will incorrectly compound it. */ - ma_apply_volume_factor_pcm_frames(pFramesOut, frameCount, pDevice->playback.format, pDevice->playback.channels, masterVolumeFactor); + if (!pDevice->noClip && pDevice->playback.format == ma_format_f32) { + ma_clip_samples_f32((float*)pFramesOut, (const float*)pFramesOut, frameCount * pDevice->playback.channels); /* Intentionally specifying the same pointer for both input and output for in-place processing. */ } } - - if (!pDevice->noClip && pDevice->playback.format == ma_format_f32) { - ma_clip_pcm_frames_f32((float*)pFramesOut, frameCount, pDevice->playback.channels); - } } + ma_device_restore_denormals(pDevice, prevDenormalState); } } @@ -5748,54 +6024,95 @@ static void ma_device__read_frames_from_client(ma_device* pDevice, ma_uint32 fra } else { ma_result result; ma_uint64 totalFramesReadOut; - ma_uint64 totalFramesReadIn; void* pRunningFramesOut; totalFramesReadOut = 0; - totalFramesReadIn = 0; pRunningFramesOut = pFramesOut; - while (totalFramesReadOut < frameCount) { - ma_uint8 pIntermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; /* In client format. */ - ma_uint64 intermediaryBufferCap = sizeof(pIntermediaryBuffer) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); - ma_uint64 framesToReadThisIterationIn; - ma_uint64 framesReadThisIterationIn; - ma_uint64 framesToReadThisIterationOut; - ma_uint64 framesReadThisIterationOut; - ma_uint64 requiredInputFrameCount; + /* + We run slightly different logic depending on whether or not we're using a heap-allocated + buffer for caching input data. This will be the case if the data converter does not have + the ability to retrieve the required input frame count for a given output frame count. + */ + if (pDevice->playback.pInputCache != NULL) { + while (totalFramesReadOut < frameCount) { + ma_uint64 framesToReadThisIterationIn; + ma_uint64 framesToReadThisIterationOut; - framesToReadThisIterationOut = (frameCount - totalFramesReadOut); - framesToReadThisIterationIn = framesToReadThisIterationOut; - if (framesToReadThisIterationIn > intermediaryBufferCap) { - framesToReadThisIterationIn = intermediaryBufferCap; + /* If there's any data available in the cache, that needs to get processed first. */ + if (pDevice->playback.inputCacheRemaining > 0) { + framesToReadThisIterationOut = (frameCount - totalFramesReadOut); + framesToReadThisIterationIn = framesToReadThisIterationOut; + if (framesToReadThisIterationIn > pDevice->playback.inputCacheRemaining) { + framesToReadThisIterationIn = pDevice->playback.inputCacheRemaining; + } + + result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, ma_offset_pcm_frames_ptr(pDevice->playback.pInputCache, pDevice->playback.inputCacheConsumed, pDevice->playback.format, pDevice->playback.channels), &framesToReadThisIterationIn, pRunningFramesOut, &framesToReadThisIterationOut); + if (result != MA_SUCCESS) { + break; + } + + pDevice->playback.inputCacheConsumed += framesToReadThisIterationIn; + pDevice->playback.inputCacheRemaining -= framesToReadThisIterationIn; + + totalFramesReadOut += framesToReadThisIterationOut; + pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesToReadThisIterationOut * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); + + if (framesToReadThisIterationIn == 0 && framesToReadThisIterationOut == 0) { + break; /* We're done. */ + } + } + + /* Getting here means there's no data in the cache and we need to fill it up with data from the client. */ + if (pDevice->playback.inputCacheRemaining == 0) { + ma_device__on_data(pDevice, pDevice->playback.pInputCache, NULL, (ma_uint32)pDevice->playback.inputCacheCap); + + pDevice->playback.inputCacheConsumed = 0; + pDevice->playback.inputCacheRemaining = pDevice->playback.inputCacheCap; + } } + } else { + while (totalFramesReadOut < frameCount) { + ma_uint8 pIntermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; /* In client format. */ + ma_uint64 intermediaryBufferCap = sizeof(pIntermediaryBuffer) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); + ma_uint64 framesToReadThisIterationIn; + ma_uint64 framesReadThisIterationIn; + ma_uint64 framesToReadThisIterationOut; + ma_uint64 framesReadThisIterationOut; + ma_uint64 requiredInputFrameCount; - requiredInputFrameCount = ma_data_converter_get_required_input_frame_count(&pDevice->playback.converter, framesToReadThisIterationOut); - if (framesToReadThisIterationIn > requiredInputFrameCount) { - framesToReadThisIterationIn = requiredInputFrameCount; - } + framesToReadThisIterationOut = (frameCount - totalFramesReadOut); + framesToReadThisIterationIn = framesToReadThisIterationOut; + if (framesToReadThisIterationIn > intermediaryBufferCap) { + framesToReadThisIterationIn = intermediaryBufferCap; + } - if (framesToReadThisIterationIn > 0) { - ma_device__on_data(pDevice, pIntermediaryBuffer, NULL, (ma_uint32)framesToReadThisIterationIn); - totalFramesReadIn += framesToReadThisIterationIn; - } + ma_data_converter_get_required_input_frame_count(&pDevice->playback.converter, framesToReadThisIterationOut, &requiredInputFrameCount); + if (framesToReadThisIterationIn > requiredInputFrameCount) { + framesToReadThisIterationIn = requiredInputFrameCount; + } - /* - At this point we have our decoded data in input format and now we need to convert to output format. Note that even if we didn't read any - input frames, we still want to try processing frames because there may some output frames generated from cached input data. - */ - framesReadThisIterationIn = framesToReadThisIterationIn; - framesReadThisIterationOut = framesToReadThisIterationOut; - result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, pIntermediaryBuffer, &framesReadThisIterationIn, pRunningFramesOut, &framesReadThisIterationOut); - if (result != MA_SUCCESS) { - break; - } + if (framesToReadThisIterationIn > 0) { + ma_device__on_data(pDevice, pIntermediaryBuffer, NULL, (ma_uint32)framesToReadThisIterationIn); + } - totalFramesReadOut += framesReadThisIterationOut; - pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesReadThisIterationOut * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); + /* + At this point we have our decoded data in input format and now we need to convert to output format. Note that even if we didn't read any + input frames, we still want to try processing frames because there may some output frames generated from cached input data. + */ + framesReadThisIterationIn = framesToReadThisIterationIn; + framesReadThisIterationOut = framesToReadThisIterationOut; + result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, pIntermediaryBuffer, &framesReadThisIterationIn, pRunningFramesOut, &framesReadThisIterationOut); + if (result != MA_SUCCESS) { + break; + } - if (framesReadThisIterationIn == 0 && framesReadThisIterationOut == 0) { - break; /* We're done. */ + totalFramesReadOut += framesReadThisIterationOut; + pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesReadThisIterationOut * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); + + if (framesReadThisIterationIn == 0 && framesReadThisIterationOut == 0) { + break; /* We're done. */ + } } } } @@ -5867,7 +6184,7 @@ static ma_result ma_device__handle_duplex_callback_capture(ma_device* pDevice, m result = ma_pcm_rb_acquire_write(pRB, &framesToProcessInClientFormat, &pFramesInClientFormat); if (result != MA_SUCCESS) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "Failed to acquire capture PCM frames from ring buffer.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to acquire capture PCM frames from ring buffer."); break; } @@ -5885,9 +6202,9 @@ static ma_result ma_device__handle_duplex_callback_capture(ma_device* pDevice, m break; } - result = ma_pcm_rb_commit_write(pRB, (ma_uint32)framesProcessedInClientFormat, pFramesInClientFormat); /* Safe cast. */ + result = ma_pcm_rb_commit_write(pRB, (ma_uint32)framesProcessedInClientFormat); /* Safe cast. */ if (result != MA_SUCCESS) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "Failed to commit capture PCM frames to ring buffer.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to commit capture PCM frames to ring buffer."); break; } @@ -5906,16 +6223,14 @@ static ma_result ma_device__handle_duplex_callback_capture(ma_device* pDevice, m static ma_result ma_device__handle_duplex_callback_playback(ma_device* pDevice, ma_uint32 frameCount, void* pFramesInInternalFormat, ma_pcm_rb* pRB) { ma_result result; - ma_uint8 playbackFramesInExternalFormat[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; ma_uint8 silentInputFrames[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 totalFramesToReadFromClient; - ma_uint32 totalFramesReadFromClient; ma_uint32 totalFramesReadOut = 0; MA_ASSERT(pDevice != NULL); MA_ASSERT(frameCount > 0); MA_ASSERT(pFramesInInternalFormat != NULL); MA_ASSERT(pRB != NULL); + MA_ASSERT(pDevice->playback.pInputCache != NULL); /* Sitting in the ring buffer should be captured data from the capture callback in external format. If there's not enough data in there for @@ -5923,68 +6238,61 @@ static ma_result ma_device__handle_duplex_callback_playback(ma_device* pDevice, */ MA_ZERO_MEMORY(silentInputFrames, sizeof(silentInputFrames)); - /* We need to calculate how many output frames are required to be read from the client to completely fill frameCount internal frames. */ - totalFramesToReadFromClient = (ma_uint32)ma_data_converter_get_required_input_frame_count(&pDevice->playback.converter, frameCount); - totalFramesReadFromClient = 0; - while (totalFramesReadFromClient < totalFramesToReadFromClient && ma_device_is_started(pDevice)) { - ma_uint32 framesRemainingFromClient; - ma_uint32 framesToProcessFromClient; - ma_uint32 inputFrameCount; - void* pInputFrames; - - framesRemainingFromClient = (totalFramesToReadFromClient - totalFramesReadFromClient); - framesToProcessFromClient = sizeof(playbackFramesInExternalFormat) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); - if (framesToProcessFromClient > framesRemainingFromClient) { - framesToProcessFromClient = framesRemainingFromClient; - } - - /* We need to grab captured samples before firing the callback. If there's not enough input samples we just pass silence. */ - inputFrameCount = framesToProcessFromClient; - result = ma_pcm_rb_acquire_read(pRB, &inputFrameCount, &pInputFrames); - if (result == MA_SUCCESS) { - if (inputFrameCount > 0) { - /* Use actual input frames. */ - ma_device__on_data(pDevice, playbackFramesInExternalFormat, pInputFrames, inputFrameCount); - } else { - if (ma_pcm_rb_pointer_distance(pRB) == 0) { - break; /* Underrun. */ - } - } - - /* We're done with the captured samples. */ - result = ma_pcm_rb_commit_read(pRB, inputFrameCount, pInputFrames); - if (result != MA_SUCCESS) { - break; /* Don't know what to do here... Just abandon ship. */ - } - } else { - /* Use silent input frames. */ - inputFrameCount = ma_min( - sizeof(playbackFramesInExternalFormat) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels), - sizeof(silentInputFrames) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels) - ); - - ma_device__on_data(pDevice, playbackFramesInExternalFormat, silentInputFrames, inputFrameCount); - } - - /* We have samples in external format so now we need to convert to internal format and output to the device. */ - { - ma_uint64 framesConvertedIn = inputFrameCount; + while (totalFramesReadOut < frameCount && ma_device_is_started(pDevice)) { + /* + We should have a buffer allocated on the heap. Any playback frames still sitting in there + need to be sent to the internal device before we process any more data from the client. + */ + if (pDevice->playback.inputCacheRemaining > 0) { + ma_uint64 framesConvertedIn = pDevice->playback.inputCacheRemaining; ma_uint64 framesConvertedOut = (frameCount - totalFramesReadOut); - ma_data_converter_process_pcm_frames(&pDevice->playback.converter, playbackFramesInExternalFormat, &framesConvertedIn, pFramesInInternalFormat, &framesConvertedOut); + ma_data_converter_process_pcm_frames(&pDevice->playback.converter, ma_offset_pcm_frames_ptr(pDevice->playback.pInputCache, pDevice->playback.inputCacheConsumed, pDevice->playback.format, pDevice->playback.channels), &framesConvertedIn, pFramesInInternalFormat, &framesConvertedOut); + + pDevice->playback.inputCacheConsumed += framesConvertedIn; + pDevice->playback.inputCacheRemaining -= framesConvertedIn; - totalFramesReadFromClient += (ma_uint32)framesConvertedIn; /* Safe cast. */ totalFramesReadOut += (ma_uint32)framesConvertedOut; /* Safe cast. */ pFramesInInternalFormat = ma_offset_ptr(pFramesInInternalFormat, framesConvertedOut * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); } + + /* If there's no more data in the cache we'll need to fill it with some. */ + if (totalFramesReadOut < frameCount && pDevice->playback.inputCacheRemaining == 0) { + ma_uint32 inputFrameCount; + void* pInputFrames; + + inputFrameCount = (ma_uint32)pDevice->playback.inputCacheCap; + result = ma_pcm_rb_acquire_read(pRB, &inputFrameCount, &pInputFrames); + if (result == MA_SUCCESS) { + if (inputFrameCount > 0) { + ma_device__on_data(pDevice, pDevice->playback.pInputCache, pInputFrames, inputFrameCount); + } else { + if (ma_pcm_rb_pointer_distance(pRB) == 0) { + break; /* Underrun. */ + } + } + } else { + /* No capture data available. Feed in silence. */ + inputFrameCount = (ma_uint32)ma_min(pDevice->playback.inputCacheCap, sizeof(silentInputFrames) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels)); + ma_device__on_data(pDevice, pDevice->playback.pInputCache, silentInputFrames, inputFrameCount); + } + + pDevice->playback.inputCacheConsumed = 0; + pDevice->playback.inputCacheRemaining = inputFrameCount; + + result = ma_pcm_rb_commit_read(pRB, inputFrameCount); + if (result != MA_SUCCESS) { + return result; /* Should never happen. */ + } + } } return MA_SUCCESS; } /* A helper for changing the state of the device. */ -static MA_INLINE void ma_device__set_state(ma_device* pDevice, ma_uint32 newState) +static MA_INLINE void ma_device__set_state(ma_device* pDevice, ma_device_state newState) { - c89atomic_exchange_32(&pDevice->state, newState); + c89atomic_exchange_i32((ma_int32*)&pDevice->state, (ma_int32)newState); } @@ -6023,7 +6331,7 @@ static ma_bool32 ma_device_descriptor_is_valid(const ma_device_descriptor* pDevi return MA_FALSE; } - if (pDeviceDescriptor->channels < MA_MIN_CHANNELS || pDeviceDescriptor->channels > MA_MAX_CHANNELS) { + if (pDeviceDescriptor->channels == 0 || pDeviceDescriptor->channels > MA_MAX_CHANNELS) { return MA_FALSE; } @@ -6065,7 +6373,7 @@ static ma_result ma_device_audio_thread__default_read_write(ma_device* pDevice) /* NOTE: The device was started outside of this function, in the worker thread. */ - while (ma_device_get_state(pDevice) == MA_STATE_STARTED && !exitLoop) { + while (ma_device_get_state(pDevice) == ma_device_state_started && !exitLoop) { switch (pDevice->type) { case ma_device_type_duplex: { @@ -6445,7 +6753,7 @@ static ma_result ma_device_init__null(ma_device* pDevice, const ma_device_config pDescriptorCapture->sampleRate = (pDescriptorCapture->sampleRate != 0) ? pDescriptorCapture->sampleRate : MA_DEFAULT_SAMPLE_RATE; if (pDescriptorCapture->channelMap[0] == MA_CHANNEL_NONE) { - ma_get_standard_channel_map(ma_standard_channel_map_default, pDescriptorCapture->channels, pDescriptorCapture->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_default, pDescriptorCapture->channelMap, ma_countof(pDescriptorCapture->channelMap), pDescriptorCapture->channels); } pDescriptorCapture->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorCapture, pDescriptorCapture->sampleRate, pConfig->performanceProfile); @@ -6457,7 +6765,7 @@ static ma_result ma_device_init__null(ma_device* pDevice, const ma_device_config pDescriptorPlayback->sampleRate = (pDescriptorPlayback->sampleRate != 0) ? pDescriptorPlayback->sampleRate : MA_DEFAULT_SAMPLE_RATE; if (pDescriptorPlayback->channelMap[0] == MA_CHANNEL_NONE) { - ma_get_standard_channel_map(ma_standard_channel_map_default, pDescriptorPlayback->channels, pDescriptorPlayback->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_default, pDescriptorPlayback->channelMap, ma_countof(pDescriptorCapture->channelMap), pDescriptorPlayback->channels); } pDescriptorPlayback->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorPlayback, pDescriptorPlayback->sampleRate, pConfig->performanceProfile); @@ -7500,7 +7808,7 @@ typedef struct struct ma_completion_handler_uwp { ma_completion_handler_uwp_vtbl* lpVtbl; - MA_ATOMIC ma_uint32 counter; + MA_ATOMIC(4, ma_uint32) counter; HANDLE hEvent; }; @@ -7654,7 +7962,7 @@ static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDeviceStateChanged(m use this to determine whether or not we need to automatically start the device when it's plugged back in again. */ - if (ma_device_get_state(pThis->pDevice) == MA_STATE_STARTED) { + if (ma_device_get_state(pThis->pDevice) == ma_device_state_started) { if (isPlayback) { pThis->pDevice->wasapi.isDetachedPlayback = MA_TRUE; } @@ -7764,13 +8072,13 @@ static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDefaultDeviceChanged /* Second attempt at device rerouting. We're going to retrieve the device's state at the time of the route change. We're then going to stop the device, reinitialize the device, and then start - it again if the state before stopping was MA_STATE_STARTED. + it again if the state before stopping was ma_device_state_started. */ { ma_uint32 previousState = ma_device_get_state(pThis->pDevice); ma_bool8 restartDevice = MA_FALSE; - if (previousState == MA_STATE_STARTED) { + if (previousState == ma_device_state_started) { ma_device_stop(pThis->pDevice); restartDevice = MA_TRUE; } @@ -8055,7 +8363,8 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context if (SUCCEEDED(hr)) { ma_add_native_data_format_to_device_info_from_WAVEFORMATEX(pWF, ma_share_mode_shared, pInfo); } else { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve mix format for device info retrieval.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve mix format for device info retrieval."); + return ma_result_from_HRESULT(hr); } /* @@ -8093,7 +8402,7 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context The format returned by PKEY_AudioEngine_DeviceFormat is not supported, so fall back to a search. We assume the channel count returned by MA_PKEY_AudioEngine_DeviceFormat is valid and correct. For simplicity we're only returning one format. */ - ma_uint32 channels = pInfo->minChannels; + ma_uint32 channels = pWF->nChannels; ma_channel defaultChannelMap[MA_MAX_CHANNELS]; WAVEFORMATEXTENSIBLE wf; ma_bool32 found; @@ -8104,7 +8413,7 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context channels = MA_MAX_CHANNELS; } - ma_get_standard_channel_map(ma_standard_channel_map_microsoft, channels, defaultChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_microsoft, defaultChannelMap, ma_countof(defaultChannelMap), channels); MA_ZERO_OBJECT(&wf); wf.Format.cbSize = sizeof(wf); @@ -8146,16 +8455,16 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context ma_PropVariantClear(pContext, &var); if (!found) { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "[WASAPI] Failed to find suitable device format for device info retrieval.", MA_FORMAT_NOT_SUPPORTED); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "[WASAPI] Failed to find suitable device format for device info retrieval."); } } } else { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "[WASAPI] Failed to retrieve device format for device info retrieval.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "[WASAPI] Failed to retrieve device format for device info retrieval."); } ma_IPropertyStore_Release(pProperties); } else { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "[WASAPI] Failed to open property store for device info retrieval.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "[WASAPI] Failed to open property store for device info retrieval."); } } #endif @@ -8188,7 +8497,8 @@ static ma_result ma_context_create_IMMDeviceEnumerator__wasapi(ma_context* pCont hr = ma_CoCreateInstance(pContext, MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create device enumerator.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create device enumerator."); + return ma_result_from_HRESULT(hr); } *ppDeviceEnumerator = pDeviceEnumerator; @@ -8261,7 +8571,8 @@ static ma_result ma_context_get_MMDevice__wasapi(ma_context* pContext, ma_device hr = ma_CoCreateInstance(pContext, MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create IMMDeviceEnumerator.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create IMMDeviceEnumerator."); + return ma_result_from_HRESULT(hr); } if (pDeviceID == NULL) { @@ -8272,7 +8583,8 @@ static ma_result ma_context_get_MMDevice__wasapi(ma_context* pContext, ma_device ma_IMMDeviceEnumerator_Release(pDeviceEnumerator); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve IMMDevice.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve IMMDevice."); + return ma_result_from_HRESULT(hr); } return MA_SUCCESS; @@ -8352,7 +8664,8 @@ static ma_result ma_context_get_device_info_from_MMDevice__wasapi(ma_context* pC ma_IAudioClient_Release(pAudioClient); return result; } else { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to activate audio client for device info retrieval.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to activate audio client for device info retrieval."); + return ma_result_from_HRESULT(hr); } } @@ -8379,7 +8692,8 @@ static ma_result ma_context_enumerate_devices_by_type__wasapi(ma_context* pConte if (SUCCEEDED(hr)) { hr = ma_IMMDeviceCollection_GetCount(pDeviceCollection, &deviceCount); if (FAILED(hr)) { - result = ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to get device count.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to get device count."); + result = ma_result_from_HRESULT(hr); goto done; } @@ -8470,13 +8784,15 @@ static ma_result ma_context_get_IAudioClient_UWP__wasapi(ma_context* pContext, m hr = StringFromIID(&iid, &iidStr); #endif if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to convert device IID to string for ActivateAudioInterfaceAsync(). Out of memory.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to convert device IID to string for ActivateAudioInterfaceAsync(). Out of memory."); + return ma_result_from_HRESULT(hr); } result = ma_completion_handler_uwp_init(&completionHandler); if (result != MA_SUCCESS) { ma_CoTaskMemFree(pContext, iidStr); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for waiting for ActivateAudioInterfaceAsync().", result); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for waiting for ActivateAudioInterfaceAsync()."); + return result; } #if defined(__cplusplus) @@ -8487,7 +8803,8 @@ static ma_result ma_context_get_IAudioClient_UWP__wasapi(ma_context* pContext, m if (FAILED(hr)) { ma_completion_handler_uwp_uninit(&completionHandler); ma_CoTaskMemFree(pContext, iidStr); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] ActivateAudioInterfaceAsync() failed.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] ActivateAudioInterfaceAsync() failed."); + return ma_result_from_HRESULT(hr); } ma_CoTaskMemFree(pContext, iidStr); @@ -8500,13 +8817,15 @@ static ma_result ma_context_get_IAudioClient_UWP__wasapi(ma_context* pContext, m ma_IActivateAudioInterfaceAsyncOperation_Release(pAsyncOp); if (FAILED(hr) || FAILED(activateResult)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to activate device.", FAILED(hr) ? ma_result_from_HRESULT(hr) : ma_result_from_HRESULT(activateResult)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to activate device."); + return FAILED(hr) ? ma_result_from_HRESULT(hr) : ma_result_from_HRESULT(activateResult); } /* Here is where we grab the IAudioClient interface. */ hr = ma_IUnknown_QueryInterface(pActivatedInterface, &MA_IID_IAudioClient, (void**)ppAudioClient); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to query IAudioClient interface.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to query IAudioClient interface."); + return ma_result_from_HRESULT(hr); } if (ppActivatedInterface) { @@ -8539,7 +8858,8 @@ static ma_result ma_context_enumerate_devices__wasapi(ma_context* pContext, ma_e hr = ma_CoCreateInstance(pContext, MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create device enumerator.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create device enumerator."); + return ma_result_from_HRESULT(hr); } ma_context_enumerate_devices_by_type__wasapi(pContext, pDeviceEnumerator, ma_device_type_playback, callback, pUserData); @@ -9123,7 +9443,7 @@ done: } if (errorMsg != NULL && errorMsg[0] != '\0') { - ma_post_log_message(pContext, NULL, MA_LOG_LEVEL_ERROR, errorMsg); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "%s", errorMsg); } return result; @@ -9316,7 +9636,8 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf pDevice->wasapi.pAudioClientCapture = NULL; } - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for capture.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for capture."); + return result; } ma_IAudioClient_SetEventHandle((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, pDevice->wasapi.hEventCapture); @@ -9409,7 +9730,8 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf pDevice->wasapi.pAudioClientPlayback = NULL; } - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for playback.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for playback."); + return result; } ma_IAudioClient_SetEventHandle((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, pDevice->wasapi.hEventPlayback); @@ -9446,7 +9768,8 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf hr = ma_CoCreateInstance(pDevice->pContext, MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); if (FAILED(hr)) { ma_device_uninit__wasapi(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create device enumerator.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create device enumerator."); + return ma_result_from_HRESULT(hr); } pDevice->wasapi.notificationClient.lpVtbl = (void*)&g_maNotificationCientVtbl; @@ -9554,7 +9877,8 @@ static ma_result ma_device_start__wasapi(ma_device* pDevice) if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal capture device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal capture device."); + return ma_result_from_HRESULT(hr); } c89atomic_exchange_32(&pDevice->wasapi.isStartedCapture, MA_TRUE); @@ -9577,13 +9901,15 @@ static ma_result ma_device_stop__wasapi(ma_device* pDevice) if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { hr = ma_IAudioClient_Stop((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal capture device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal capture device."); + return ma_result_from_HRESULT(hr); } /* The audio client needs to be reset otherwise restarting will fail. */ hr = ma_IAudioClient_Reset((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal capture device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal capture device."); + return ma_result_from_HRESULT(hr); } c89atomic_exchange_32(&pDevice->wasapi.isStartedCapture, MA_FALSE); @@ -9630,13 +9956,15 @@ static ma_result ma_device_stop__wasapi(ma_device* pDevice) hr = ma_IAudioClient_Stop((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal playback device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal playback device."); + return ma_result_from_HRESULT(hr); } /* The audio client needs to be reset otherwise restarting will fail. */ hr = ma_IAudioClient_Reset((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal playback device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal playback device."); + return ma_result_from_HRESULT(hr); } c89atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_FALSE); @@ -9685,7 +10013,7 @@ static ma_result ma_device_data_loop__wasapi(ma_device* pDevice) outputDataInClientFormatCap = sizeof(outputDataInClientFormat) / bpfPlaybackClient; } - while (ma_device_get_state(pDevice) == MA_STATE_STARTED && !exitLoop) { + while (ma_device_get_state(pDevice) == ma_device_state_started && !exitLoop) { switch (pDevice->type) { case ma_device_type_duplex: @@ -9711,7 +10039,7 @@ static ma_result ma_device_data_loop__wasapi(ma_device* pDevice) /* We're ready to map the playback device's buffer. We don't release this until it's been entirely filled. */ hr = ma_IAudioRenderClient_GetBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, framesAvailablePlayback, &pMappedDeviceBufferPlayback); if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for writing to the device."); exitLoop = MA_TRUE; break; } @@ -9753,7 +10081,7 @@ static ma_result ma_device_data_loop__wasapi(ma_device* pDevice) mappedDeviceBufferSizeInFramesCapture = ma_min(framesAvailableCapture, periodSizeInFramesCapture); hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pMappedDeviceBufferCapture, &mappedDeviceBufferSizeInFramesCapture, &flagsCapture, NULL, NULL); if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device."); exitLoop = MA_TRUE; break; } @@ -9784,7 +10112,7 @@ static ma_result ma_device_data_loop__wasapi(ma_device* pDevice) mappedDeviceBufferSizeInFramesCapture = ma_min(framesAvailableCapture, periodSizeInFramesCapture); hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pMappedDeviceBufferCapture, &mappedDeviceBufferSizeInFramesCapture, &flagsCapture, NULL, NULL); if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device."); exitLoop = MA_TRUE; break; } @@ -9913,7 +10241,7 @@ static ma_result ma_device_data_loop__wasapi(ma_device* pDevice) if (mappedDeviceBufferFramesRemainingCapture == 0 && pMappedDeviceBufferCapture != NULL) { hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedDeviceBufferSizeInFramesCapture); if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from capture device after reading from the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from capture device after reading from the device."); exitLoop = MA_TRUE; break; } @@ -9935,7 +10263,7 @@ static ma_result ma_device_data_loop__wasapi(ma_device* pDevice) if (mappedDeviceBufferFramesRemainingPlayback == 0 && pMappedDeviceBufferPlayback != NULL) { hr = ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, mappedDeviceBufferSizeInFramesPlayback, 0); if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from playback device after writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from playback device after writing to the device."); exitLoop = MA_TRUE; break; } @@ -9960,7 +10288,8 @@ static ma_result ma_device_data_loop__wasapi(ma_device* pDevice) if (FAILED(hr)) { ma_IAudioClient_Stop((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); ma_IAudioClient_Reset((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal playback device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal playback device."); + return ma_result_from_HRESULT(hr); } c89atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_TRUE); @@ -10010,7 +10339,7 @@ static ma_result ma_device_data_loop__wasapi(ma_device* pDevice) mappedDeviceBufferSizeInFramesCapture = framesAvailableCapture; hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pMappedDeviceBufferCapture, &mappedDeviceBufferSizeInFramesCapture, &flagsCapture, NULL, NULL); if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device."); exitLoop = MA_TRUE; break; } @@ -10040,7 +10369,7 @@ static ma_result ma_device_data_loop__wasapi(ma_device* pDevice) mappedDeviceBufferSizeInFramesCapture = ma_min(framesAvailableCapture, periodSizeInFramesCapture); hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pMappedDeviceBufferCapture, &mappedDeviceBufferSizeInFramesCapture, &flagsCapture, NULL, NULL); if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device."); exitLoop = MA_TRUE; break; } @@ -10068,7 +10397,7 @@ static ma_result ma_device_data_loop__wasapi(ma_device* pDevice) pMappedDeviceBufferCapture = NULL; /* <-- Important. Not doing this can result in an error once we leave this loop because it will use this to know whether or not a final ReleaseBuffer() needs to be called. */ mappedDeviceBufferSizeInFramesCapture = 0; if (FAILED(hr)) { - ma_post_log_message(ma_device_get_context(pDevice), pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from capture device after reading from the device."); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from capture device after reading from the device."); exitLoop = MA_TRUE; break; } @@ -10092,7 +10421,7 @@ static ma_result ma_device_data_loop__wasapi(ma_device* pDevice) /* Map a the data buffer in preparation for the callback. */ hr = ma_IAudioRenderClient_GetBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, framesAvailablePlayback, &pMappedDeviceBufferPlayback); if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for writing to the device."); exitLoop = MA_TRUE; break; } @@ -10106,7 +10435,7 @@ static ma_result ma_device_data_loop__wasapi(ma_device* pDevice) mappedDeviceBufferSizeInFramesPlayback = 0; if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from playback device after writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from playback device after writing to the device."); exitLoop = MA_TRUE; break; } @@ -10117,7 +10446,7 @@ static ma_result ma_device_data_loop__wasapi(ma_device* pDevice) if (!c89atomic_load_32(&pDevice->wasapi.isStartedPlayback)) { hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal playback device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal playback device."); exitLoop = MA_TRUE; break; } @@ -10721,7 +11050,8 @@ static ma_result ma_context_create_IDirectSound__dsound(ma_context* pContext, ma pDirectSound = NULL; if (FAILED(((ma_DirectSoundCreateProc)pContext->dsound.DirectSoundCreate)((pDeviceID == NULL) ? NULL : (const GUID*)pDeviceID->dsound, &pDirectSound, NULL))) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[DirectSound] DirectSoundCreate() failed for playback device.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[DirectSound] DirectSoundCreate() failed for playback device."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } /* The cooperative level must be set before doing anything else. */ @@ -10732,7 +11062,8 @@ static ma_result ma_context_create_IDirectSound__dsound(ma_context* pContext, ma hr = ma_IDirectSound_SetCooperativeLevel(pDirectSound, hWnd, (shareMode == ma_share_mode_exclusive) ? MA_DSSCL_EXCLUSIVE : MA_DSSCL_PRIORITY); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_SetCooperateiveLevel() failed for playback device.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_SetCooperateiveLevel() failed for playback device."); + return ma_result_from_HRESULT(hr); } *ppDirectSound = pDirectSound; @@ -10757,7 +11088,8 @@ static ma_result ma_context_create_IDirectSoundCapture__dsound(ma_context* pCont hr = ((ma_DirectSoundCaptureCreateProc)pContext->dsound.DirectSoundCaptureCreate)((pDeviceID == NULL) ? NULL : (const GUID*)pDeviceID->dsound, &pDirectSoundCapture, NULL); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[DirectSound] DirectSoundCaptureCreate() failed for capture device.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[DirectSound] DirectSoundCaptureCreate() failed for capture device."); + return ma_result_from_HRESULT(hr); } *ppDirectSoundCapture = pDirectSoundCapture; @@ -10788,7 +11120,8 @@ static ma_result ma_context_get_format_info_for_IDirectSoundCapture__dsound(ma_c caps.dwSize = sizeof(caps); hr = ma_IDirectSoundCapture_GetCaps(pDirectSoundCapture, &caps); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCapture_GetCaps() failed for capture device.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCapture_GetCaps() failed for capture device."); + return ma_result_from_HRESULT(hr); } if (pChannels) { @@ -11023,7 +11356,8 @@ static ma_result ma_context_get_device_info__dsound(ma_context* pContext, ma_dev caps.dwSize = sizeof(caps); hr = ma_IDirectSound_GetCaps(pDirectSound, &caps); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_GetCaps() failed for playback device.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_GetCaps() failed for playback device."); + return ma_result_from_HRESULT(hr); } @@ -11099,13 +11433,13 @@ static ma_result ma_context_get_device_info__dsound(ma_context* pContext, ma_dev /* The format is always an integer format and is based on the bits per sample. */ if (bitsPerSample == 8) { - pDeviceInfo->formats[0] = ma_format_u8; + pDeviceInfo->nativeDataFormats[0].format = ma_format_u8; } else if (bitsPerSample == 16) { - pDeviceInfo->formats[0] = ma_format_s16; + pDeviceInfo->nativeDataFormats[0].format = ma_format_s16; } else if (bitsPerSample == 24) { - pDeviceInfo->formats[0] = ma_format_s24; + pDeviceInfo->nativeDataFormats[0].format = ma_format_s24; } else if (bitsPerSample == 32) { - pDeviceInfo->formats[0] = ma_format_s32; + pDeviceInfo->nativeDataFormats[0].format = ma_format_s32; } else { return MA_FORMAT_NOT_SUPPORTED; } @@ -11270,7 +11604,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSoundCapture_CreateCaptureBuffer((ma_IDirectSoundCapture*)pDevice->dsound.pCapture, &descDS, (ma_IDirectSoundCaptureBuffer**)&pDevice->dsound.pCaptureBuffer, NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCapture_CreateCaptureBuffer() failed for capture device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCapture_CreateCaptureBuffer() failed for capture device."); + return ma_result_from_HRESULT(hr); } /* Get the _actual_ properties of the buffer. */ @@ -11278,7 +11613,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSoundCaptureBuffer_GetFormat((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, (WAVEFORMATEX*)pActualFormat, sizeof(rawdata), NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to retrieve the actual format of the capture device's buffer.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to retrieve the actual format of the capture device's buffer."); + return ma_result_from_HRESULT(hr); } /* We can now start setting the output data formats. */ @@ -11304,7 +11640,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSoundCapture_CreateCaptureBuffer((ma_IDirectSoundCapture*)pDevice->dsound.pCapture, &descDS, (ma_IDirectSoundCaptureBuffer**)&pDevice->dsound.pCaptureBuffer, NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Second attempt at IDirectSoundCapture_CreateCaptureBuffer() failed for capture device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Second attempt at IDirectSoundCapture_CreateCaptureBuffer() failed for capture device."); + return ma_result_from_HRESULT(hr); } } @@ -11340,7 +11677,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSound_CreateSoundBuffer((ma_IDirectSound*)pDevice->dsound.pPlayback, &descDSPrimary, (ma_IDirectSoundBuffer**)&pDevice->dsound.pPlaybackPrimaryBuffer, NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_CreateSoundBuffer() failed for playback device's primary buffer.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_CreateSoundBuffer() failed for playback device's primary buffer."); + return ma_result_from_HRESULT(hr); } @@ -11350,7 +11688,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSound_GetCaps((ma_IDirectSound*)pDevice->dsound.pPlayback, &caps); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_GetCaps() failed for playback device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_GetCaps() failed for playback device."); + return ma_result_from_HRESULT(hr); } if (pDescriptorPlayback->channels == 0) { @@ -11392,7 +11731,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSoundBuffer_SetFormat((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackPrimaryBuffer, (WAVEFORMATEX*)&wf); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to set format of playback device's primary buffer.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to set format of playback device's primary buffer."); + return ma_result_from_HRESULT(hr); } /* Get the _actual_ properties of the buffer. */ @@ -11400,7 +11740,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSoundBuffer_GetFormat((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackPrimaryBuffer, (WAVEFORMATEX*)pActualFormat, sizeof(rawdata), NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to retrieve the actual format of the playback device's primary buffer.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to retrieve the actual format of the playback device's primary buffer."); + return ma_result_from_HRESULT(hr); } /* We now have enough information to start setting some output properties. */ @@ -11442,7 +11783,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSound_CreateSoundBuffer((ma_IDirectSound*)pDevice->dsound.pPlayback, &descDS, (ma_IDirectSoundBuffer**)&pDevice->dsound.pPlaybackBuffer, NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_CreateSoundBuffer() failed for playback device's secondary buffer.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_CreateSoundBuffer() failed for playback device's secondary buffer."); + return ma_result_from_HRESULT(hr); } /* DirectSound should give us a buffer exactly the size we asked for. */ @@ -11482,12 +11824,14 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) /* The first thing to do is start the capture device. The playback device is only started after the first period is written. */ if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - if (FAILED(ma_IDirectSoundCaptureBuffer_Start((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, MA_DSCBSTART_LOOPING))) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCaptureBuffer_Start() failed.", MA_FAILED_TO_START_BACKEND_DEVICE); + hr = ma_IDirectSoundCaptureBuffer_Start((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, MA_DSCBSTART_LOOPING); + if (FAILED(hr)) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCaptureBuffer_Start() failed."); + return ma_result_from_HRESULT(hr); } } - while (ma_device_get_state(pDevice) == MA_STATE_STARTED) { + while (ma_device_get_state(pDevice) == ma_device_state_started) { switch (pDevice->type) { case ma_device_type_duplex: @@ -11536,7 +11880,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundCaptureBuffer_Lock((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, lockOffsetInBytesCapture, lockSizeInBytesCapture, &pMappedDeviceBufferCapture, &mappedSizeInBytesCapture, NULL, NULL, 0); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from capture device in preparation for writing to the device."); + return ma_result_from_HRESULT(hr); } @@ -11611,7 +11956,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundBuffer_Play((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING); if (FAILED(hr)) { ma_IDirectSoundCaptureBuffer_Stop((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed."); + return ma_result_from_HRESULT(hr); } isPlaybackDeviceStarted = MA_TRUE; } else { @@ -11633,7 +11979,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundBuffer_Lock((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, lockOffsetInBytesPlayback, lockSizeInBytesPlayback, &pMappedDeviceBufferPlayback, &mappedSizeInBytesPlayback, NULL, NULL, 0); if (FAILED(hr)) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from playback device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from playback device in preparation for writing to the device."); + result = ma_result_from_HRESULT(hr); break; } @@ -11675,7 +12022,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundBuffer_Unlock((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, pMappedDeviceBufferPlayback, framesWrittenThisIteration*bpfDevicePlayback, NULL, 0); if (FAILED(hr)) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from playback device after writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from playback device after writing to the device."); + result = ma_result_from_HRESULT(hr); break; } @@ -11694,7 +12042,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundBuffer_Play((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING); if (FAILED(hr)) { ma_IDirectSoundCaptureBuffer_Stop((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed."); + return ma_result_from_HRESULT(hr); } isPlaybackDeviceStarted = MA_TRUE; } @@ -11713,7 +12062,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) /* At this point we're done with the mapped portion of the capture buffer. */ hr = ma_IDirectSoundCaptureBuffer_Unlock((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, pMappedDeviceBufferCapture, mappedSizeInBytesCapture, NULL, 0); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from capture device after reading from the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from capture device after reading from the device."); + return ma_result_from_HRESULT(hr); } prevReadCursorInBytesCapture = (lockOffsetInBytesCapture + mappedSizeInBytesCapture); } break; @@ -11763,7 +12113,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundCaptureBuffer_Lock((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, lockOffsetInBytesCapture, lockSizeInBytesCapture, &pMappedDeviceBufferCapture, &mappedSizeInBytesCapture, NULL, NULL, 0); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from capture device in preparation for writing to the device."); + result = ma_result_from_HRESULT(hr); } #ifdef MA_DEBUG_OUTPUT @@ -11776,7 +12127,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundCaptureBuffer_Unlock((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, pMappedDeviceBufferCapture, mappedSizeInBytesCapture, NULL, 0); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from capture device after reading from the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from capture device after reading from the device."); + return ma_result_from_HRESULT(hr); } prevReadCursorInBytesCapture = lockOffsetInBytesCapture + mappedSizeInBytesCapture; @@ -11830,7 +12182,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) if (availableBytesPlayback == 0 && !isPlaybackDeviceStarted) { hr = ma_IDirectSoundBuffer_Play((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed."); + return ma_result_from_HRESULT(hr); } isPlaybackDeviceStarted = MA_TRUE; } else { @@ -11851,7 +12204,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundBuffer_Lock((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, lockOffsetInBytesPlayback, lockSizeInBytesPlayback, &pMappedDeviceBufferPlayback, &mappedSizeInBytesPlayback, NULL, NULL, 0); if (FAILED(hr)) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from playback device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from playback device in preparation for writing to the device."); + result = ma_result_from_HRESULT(hr); break; } @@ -11860,7 +12214,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundBuffer_Unlock((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, pMappedDeviceBufferPlayback, mappedSizeInBytesPlayback, NULL, 0); if (FAILED(hr)) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from playback device after writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from playback device after writing to the device."); + result = ma_result_from_HRESULT(hr); break; } @@ -11878,7 +12233,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) if (!isPlaybackDeviceStarted && framesWrittenToPlaybackDevice >= pDevice->playback.internalPeriodSizeInFrames) { hr = ma_IDirectSoundBuffer_Play((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed."); + return ma_result_from_HRESULT(hr); } isPlaybackDeviceStarted = MA_TRUE; } @@ -11897,7 +12253,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { hr = ma_IDirectSoundCaptureBuffer_Stop((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCaptureBuffer_Stop() failed.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCaptureBuffer_Stop() failed."); + return ma_result_from_HRESULT(hr); } } @@ -11945,7 +12302,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundBuffer_Stop((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Stop() failed.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Stop() failed."); + return ma_result_from_HRESULT(hr); } ma_IDirectSoundBuffer_SetCurrentPosition((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0); @@ -12471,7 +12829,7 @@ static ma_result ma_device_uninit__winmm(ma_device* pDevice) CloseHandle((HANDLE)pDevice->winmm.hEventPlayback); } - ma__free_from_callbacks(pDevice->winmm._pHeapData, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->winmm._pHeapData, &pDevice->pContext->allocationCallbacks); MA_ZERO_OBJECT(&pDevice->winmm); /* Safety. */ @@ -12556,7 +12914,7 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi pDescriptorCapture->format = ma_format_from_WAVEFORMATEX(&wf); pDescriptorCapture->channels = wf.nChannels; pDescriptorCapture->sampleRate = wf.nSamplesPerSec; - ma_get_standard_channel_map(ma_standard_channel_map_microsoft, pDescriptorCapture->channels, pDescriptorCapture->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_microsoft, pDescriptorCapture->channelMap, ma_countof(pDescriptorCapture->channelMap), pDescriptorCapture->channels); pDescriptorCapture->periodCount = pDescriptorCapture->periodCount; pDescriptorCapture->periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__winmm(pDescriptorCapture, pDescriptorCapture->sampleRate, pConfig->performanceProfile); } @@ -12594,7 +12952,7 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi pDescriptorPlayback->format = ma_format_from_WAVEFORMATEX(&wf); pDescriptorPlayback->channels = wf.nChannels; pDescriptorPlayback->sampleRate = wf.nSamplesPerSec; - ma_get_standard_channel_map(ma_standard_channel_map_microsoft, pDescriptorPlayback->channels, pDescriptorPlayback->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_microsoft, pDescriptorPlayback->channelMap, ma_countof(pDescriptorPlayback->channelMap), pDescriptorPlayback->channels); pDescriptorPlayback->periodCount = pDescriptorPlayback->periodCount; pDescriptorPlayback->periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__winmm(pDescriptorPlayback, pDescriptorPlayback->sampleRate, pConfig->performanceProfile); } @@ -12612,7 +12970,7 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi heapSize += sizeof(WAVEHDR)*pDescriptorPlayback->periodCount + (pDescriptorPlayback->periodSizeInFrames * pDescriptorPlayback->periodCount * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels)); } - pDevice->winmm._pHeapData = (ma_uint8*)ma__calloc_from_callbacks(heapSize, &pDevice->pContext->allocationCallbacks); + pDevice->winmm._pHeapData = (ma_uint8*)ma_calloc(heapSize, &pDevice->pContext->allocationCallbacks); if (pDevice->winmm._pHeapData == NULL) { errorMsg = "[WinMM] Failed to allocate memory for the intermediary buffer.", errorCode = MA_OUT_OF_MEMORY; goto on_error; @@ -12703,8 +13061,13 @@ on_error: ((MA_PFN_waveOutClose)pDevice->pContext->winmm.waveOutClose)((HWAVEOUT)pDevice->winmm.hDevicePlayback); } - ma__free_from_callbacks(pDevice->winmm._pHeapData, &pDevice->pContext->allocationCallbacks); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, errorMsg, errorCode); + ma_free(pDevice->winmm._pHeapData, &pDevice->pContext->allocationCallbacks); + + if (errorMsg != NULL && errorMsg[0] != '\0') { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "%s", errorMsg); + } + + return errorCode; } static ma_result ma_device_start__winmm(ma_device* pDevice) @@ -12725,7 +13088,8 @@ static ma_result ma_device_start__winmm(ma_device* pDevice) for (iPeriod = 0; iPeriod < pDevice->capture.internalPeriods; ++iPeriod) { resultMM = ((MA_PFN_waveInAddBuffer)pDevice->pContext->winmm.waveInAddBuffer)((HWAVEIN)pDevice->winmm.hDeviceCapture, &((LPWAVEHDR)pDevice->winmm.pWAVEHDRCapture)[iPeriod], sizeof(WAVEHDR)); if (resultMM != MMSYSERR_NOERROR) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] Failed to attach input buffers to capture device in preparation for capture.", ma_result_from_MMRESULT(resultMM)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WinMM] Failed to attach input buffers to capture device in preparation for capture."); + return ma_result_from_MMRESULT(resultMM); } /* Make sure all of the buffers start out locked. We don't want to access them until the backend tells us we can. */ @@ -12735,7 +13099,8 @@ static ma_result ma_device_start__winmm(ma_device* pDevice) /* Capture devices need to be explicitly started, unlike playback devices. */ resultMM = ((MA_PFN_waveInStart)pDevice->pContext->winmm.waveInStart)((HWAVEIN)pDevice->winmm.hDeviceCapture); if (resultMM != MMSYSERR_NOERROR) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] Failed to start backend device.", ma_result_from_MMRESULT(resultMM)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WinMM] Failed to start backend device."); + return ma_result_from_MMRESULT(resultMM); } } @@ -12759,7 +13124,7 @@ static ma_result ma_device_stop__winmm(ma_device* pDevice) resultMM = ((MA_PFN_waveInReset)pDevice->pContext->winmm.waveInReset)((HWAVEIN)pDevice->winmm.hDeviceCapture); if (resultMM != MMSYSERR_NOERROR) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] WARNING: Failed to reset capture device.", ma_result_from_MMRESULT(resultMM)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[WinMM] WARNING: Failed to reset capture device."); } } @@ -12785,7 +13150,7 @@ static ma_result ma_device_stop__winmm(ma_device* pDevice) resultMM = ((MA_PFN_waveOutReset)pDevice->pContext->winmm.waveOutReset)((HWAVEOUT)pDevice->winmm.hDevicePlayback); if (resultMM != MMSYSERR_NOERROR) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] WARNING: Failed to reset playback device.", ma_result_from_MMRESULT(resultMM)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[WinMM] WARNING: Failed to reset playback device."); } } @@ -12840,7 +13205,7 @@ static ma_result ma_device_write__winmm(ma_device* pDevice, const void* pPCMFram resultMM = ((MA_PFN_waveOutWrite)pDevice->pContext->winmm.waveOutWrite)((HWAVEOUT)pDevice->winmm.hDevicePlayback, &pWAVEHDR[pDevice->winmm.iNextHeaderPlayback], sizeof(WAVEHDR)); if (resultMM != MMSYSERR_NOERROR) { result = ma_result_from_MMRESULT(resultMM); - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] waveOutWrite() failed.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WinMM] waveOutWrite() failed."); break; } @@ -12872,7 +13237,7 @@ static ma_result ma_device_write__winmm(ma_device* pDevice, const void* pPCMFram } /* If the device has been stopped we need to break. */ - if (ma_device_get_state(pDevice) != MA_STATE_STARTED) { + if (ma_device_get_state(pDevice) != ma_device_state_started) { break; } } @@ -12929,7 +13294,7 @@ static ma_result ma_device_read__winmm(ma_device* pDevice, void* pPCMFrames, ma_ resultMM = ((MA_PFN_waveInAddBuffer)pDevice->pContext->winmm.waveInAddBuffer)((HWAVEIN)pDevice->winmm.hDeviceCapture, &((LPWAVEHDR)pDevice->winmm.pWAVEHDRCapture)[pDevice->winmm.iNextHeaderCapture], sizeof(WAVEHDR)); if (resultMM != MMSYSERR_NOERROR) { result = ma_result_from_MMRESULT(resultMM); - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] waveInAddBuffer() failed.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WinMM] waveInAddBuffer() failed."); break; } @@ -12961,7 +13326,7 @@ static ma_result ma_device_read__winmm(ma_device* pDevice, void* pPCMFrames, ma_ } /* If the device has been stopped we need to break. */ - if (ma_device_get_state(pDevice) != MA_STATE_STARTED) { + if (ma_device_get_state(pDevice) != ma_device_state_started) { break; } } @@ -13677,7 +14042,8 @@ static ma_result ma_context_open_pcm__alsa(ma_context* pContext, ma_share_mode s } if (!isDeviceOpen) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_open() failed when trying to open an appropriate default device.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_open() failed when trying to open an appropriate default device."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } } else { /* @@ -13725,7 +14091,8 @@ static ma_result ma_context_open_pcm__alsa(ma_context* pContext, ma_share_mode s } if (resultALSA < 0) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_open() failed.", ma_result_from_errno(-resultALSA)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_open() failed."); + return ma_result_from_errno(-resultALSA); } } @@ -13796,9 +14163,8 @@ static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enu goto next_device; /* The device has already been enumerated. Move on to the next one. */ } else { /* The device has not yet been enumerated. Make sure it's added to our list so that it's not enumerated again. */ - size_t oldCapacity = sizeof(*pUniqueIDs) * uniqueIDCount; size_t newCapacity = sizeof(*pUniqueIDs) * (uniqueIDCount + 1); - ma_device_id* pNewUniqueIDs = (ma_device_id*)ma__realloc_from_callbacks(pUniqueIDs, newCapacity, oldCapacity, &pContext->allocationCallbacks); + ma_device_id* pNewUniqueIDs = (ma_device_id*)ma_realloc(pUniqueIDs, newCapacity, &pContext->allocationCallbacks); if (pNewUniqueIDs == NULL) { goto next_device; /* Failed to allocate memory. */ } @@ -13896,7 +14262,7 @@ static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enu } } - ma__free_from_callbacks(pUniqueIDs, &pContext->allocationCallbacks); + ma_free(pUniqueIDs, &pContext->allocationCallbacks); ((ma_snd_device_name_free_hint_proc)pContext->alsa.snd_device_name_free_hint)((void**)ppDeviceHints); ma_mutex_unlock(&pContext->alsa.internalDeviceEnumLock); @@ -14020,7 +14386,7 @@ static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_devic } /* We need to initialize a HW parameters object in order to know what formats are supported. */ - pHWParams = (ma_snd_pcm_hw_params_t*)ma__calloc_from_callbacks(((ma_snd_pcm_hw_params_sizeof_proc)pContext->alsa.snd_pcm_hw_params_sizeof)(), &pContext->allocationCallbacks); + pHWParams = (ma_snd_pcm_hw_params_t*)ma_calloc(((ma_snd_pcm_hw_params_sizeof_proc)pContext->alsa.snd_pcm_hw_params_sizeof)(), &pContext->allocationCallbacks); if (pHWParams == NULL) { ((ma_snd_pcm_close_proc)pContext->alsa.snd_pcm_close)(pPCM); return MA_OUT_OF_MEMORY; @@ -14028,9 +14394,10 @@ static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_devic resultALSA = ((ma_snd_pcm_hw_params_any_proc)pContext->alsa.snd_pcm_hw_params_any)(pPCM, pHWParams); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pContext->allocationCallbacks); + ma_free(pHWParams, &pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pContext->alsa.snd_pcm_close)(pPCM); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed.", ma_result_from_errno(-resultALSA)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed."); + return ma_result_from_errno(-resultALSA); } /* @@ -14114,7 +14481,7 @@ static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_devic } } - ma__free_from_callbacks(pHWParams, &pContext->allocationCallbacks); + ma_free(pHWParams, &pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pContext->alsa.snd_pcm_close)(pPCM); return MA_SUCCESS; @@ -14184,16 +14551,19 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic /* Hardware parameters. */ - pHWParams = (ma_snd_pcm_hw_params_t*)ma__calloc_from_callbacks(((ma_snd_pcm_hw_params_sizeof_proc)pDevice->pContext->alsa.snd_pcm_hw_params_sizeof)(), &pDevice->pContext->allocationCallbacks); + pHWParams = (ma_snd_pcm_hw_params_t*)ma_calloc(((ma_snd_pcm_hw_params_sizeof_proc)pDevice->pContext->alsa.snd_pcm_hw_params_sizeof)(), &pDevice->pContext->allocationCallbacks); if (pHWParams == NULL) { + ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to allocate memory for hardware parameters."); return MA_OUT_OF_MEMORY; } resultALSA = ((ma_snd_pcm_hw_params_any_proc)pDevice->pContext->alsa.snd_pcm_hw_params_any)(pPCM, pHWParams); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed."); + return ma_result_from_errno(-resultALSA); } /* MMAP Mode. Try using interleaved MMAP access. If this fails, fall back to standard readi/writei. */ @@ -14211,9 +14581,10 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic if (!isUsingMMap) { resultALSA = ((ma_snd_pcm_hw_params_set_access_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_access)(pPCM, pHWParams, MA_SND_PCM_ACCESS_RW_INTERLEAVED); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set access mode to neither SND_PCM_ACCESS_MMAP_INTERLEAVED nor SND_PCM_ACCESS_RW_INTERLEAVED. snd_pcm_hw_params_set_access() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set access mode to neither SND_PCM_ACCESS_MMAP_INTERLEAVED nor SND_PCM_ACCESS_RW_INTERLEAVED. snd_pcm_hw_params_set_access() failed."); + return ma_result_from_errno(-resultALSA); } } @@ -14241,24 +14612,27 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic } if (formatALSA == MA_SND_PCM_FORMAT_UNKNOWN) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Format not supported. The device does not support any miniaudio formats.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Format not supported. The device does not support any miniaudio formats."); + return MA_FORMAT_NOT_SUPPORTED; } } resultALSA = ((ma_snd_pcm_hw_params_set_format_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_format)(pPCM, pHWParams, formatALSA); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Format not supported. snd_pcm_hw_params_set_format() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Format not supported. snd_pcm_hw_params_set_format() failed."); + return ma_result_from_errno(-resultALSA); } internalFormat = ma_format_from_alsa(formatALSA); if (internalFormat == ma_format_unknown) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] The chosen format is not supported by miniaudio.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] The chosen format is not supported by miniaudio."); + return MA_FORMAT_NOT_SUPPORTED; } } @@ -14271,9 +14645,10 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic resultALSA = ((ma_snd_pcm_hw_params_set_channels_near_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_channels_near)(pPCM, pHWParams, &channels); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set channel count. snd_pcm_hw_params_set_channels_near() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set channel count. snd_pcm_hw_params_set_channels_near() failed."); + return ma_result_from_errno(-resultALSA); } internalChannels = (ma_uint32)channels; @@ -14309,9 +14684,10 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic resultALSA = ((ma_snd_pcm_hw_params_set_rate_near_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_rate_near)(pPCM, pHWParams, &sampleRate, 0); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Sample rate not supported. snd_pcm_hw_params_set_rate_near() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Sample rate not supported. snd_pcm_hw_params_set_rate_near() failed."); + return ma_result_from_errno(-resultALSA); } internalSampleRate = (ma_uint32)sampleRate; @@ -14323,9 +14699,10 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic resultALSA = ((ma_snd_pcm_hw_params_set_periods_near_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_periods_near)(pPCM, pHWParams, &periods, NULL); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set period count. snd_pcm_hw_params_set_periods_near() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set period count. snd_pcm_hw_params_set_periods_near() failed."); + return ma_result_from_errno(-resultALSA); } internalPeriods = periods; @@ -14337,9 +14714,10 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic resultALSA = ((ma_snd_pcm_hw_params_set_buffer_size_near_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_buffer_size_near)(pPCM, pHWParams, &actualBufferSizeInFrames); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set buffer size for device. snd_pcm_hw_params_set_buffer_size() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set buffer size for device. snd_pcm_hw_params_set_buffer_size() failed."); + return ma_result_from_errno(-resultALSA); } internalPeriodSizeInFrames = actualBufferSizeInFrames / internalPeriods; @@ -14348,34 +14726,38 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic /* Apply hardware parameters. */ resultALSA = ((ma_snd_pcm_hw_params_proc)pDevice->pContext->alsa.snd_pcm_hw_params)(pPCM, pHWParams); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set hardware parameters. snd_pcm_hw_params() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set hardware parameters. snd_pcm_hw_params() failed."); + return ma_result_from_errno(-resultALSA); } - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); pHWParams = NULL; /* Software parameters. */ - pSWParams = (ma_snd_pcm_sw_params_t*)ma__calloc_from_callbacks(((ma_snd_pcm_sw_params_sizeof_proc)pDevice->pContext->alsa.snd_pcm_sw_params_sizeof)(), &pDevice->pContext->allocationCallbacks); + pSWParams = (ma_snd_pcm_sw_params_t*)ma_calloc(((ma_snd_pcm_sw_params_sizeof_proc)pDevice->pContext->alsa.snd_pcm_sw_params_sizeof)(), &pDevice->pContext->allocationCallbacks); if (pSWParams == NULL) { ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to allocate memory for software parameters."); return MA_OUT_OF_MEMORY; } resultALSA = ((ma_snd_pcm_sw_params_current_proc)pDevice->pContext->alsa.snd_pcm_sw_params_current)(pPCM, pSWParams); if (resultALSA < 0) { - ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize software parameters. snd_pcm_sw_params_current() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize software parameters. snd_pcm_sw_params_current() failed."); + return ma_result_from_errno(-resultALSA); } resultALSA = ((ma_snd_pcm_sw_params_set_avail_min_proc)pDevice->pContext->alsa.snd_pcm_sw_params_set_avail_min)(pPCM, pSWParams, ma_prev_power_of_2(internalPeriodSizeInFrames)); if (resultALSA < 0) { - ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_sw_params_set_avail_min() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_sw_params_set_avail_min() failed."); + return ma_result_from_errno(-resultALSA); } resultALSA = ((ma_snd_pcm_sw_params_get_boundary_proc)pDevice->pContext->alsa.snd_pcm_sw_params_get_boundary)(pSWParams, &bufferBoundary); @@ -14390,27 +14772,30 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic */ resultALSA = ((ma_snd_pcm_sw_params_set_start_threshold_proc)pDevice->pContext->alsa.snd_pcm_sw_params_set_start_threshold)(pPCM, pSWParams, internalPeriodSizeInFrames*2); if (resultALSA < 0) { - ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set start threshold for playback device. snd_pcm_sw_params_set_start_threshold() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set start threshold for playback device. snd_pcm_sw_params_set_start_threshold() failed."); + return ma_result_from_errno(-resultALSA); } resultALSA = ((ma_snd_pcm_sw_params_set_stop_threshold_proc)pDevice->pContext->alsa.snd_pcm_sw_params_set_stop_threshold)(pPCM, pSWParams, bufferBoundary); if (resultALSA < 0) { /* Set to boundary to loop instead of stop in the event of an xrun. */ - ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set stop threshold for playback device. snd_pcm_sw_params_set_stop_threshold() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set stop threshold for playback device. snd_pcm_sw_params_set_stop_threshold() failed."); + return ma_result_from_errno(-resultALSA); } } resultALSA = ((ma_snd_pcm_sw_params_proc)pDevice->pContext->alsa.snd_pcm_sw_params)(pPCM, pSWParams); if (resultALSA < 0) { - ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set software parameters. snd_pcm_sw_params() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set software parameters. snd_pcm_sw_params() failed."); + return ma_result_from_errno(-resultALSA); } - ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); pSWParams = NULL; @@ -14436,7 +14821,7 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic ma_bool32 isValid = MA_TRUE; /* Fill with defaults. */ - ma_get_standard_channel_map(ma_standard_channel_map_alsa, internalChannels, internalChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_alsa, internalChannelMap, ma_countof(internalChannelMap), internalChannels); /* Overwrite first pChmap->channels channels. */ for (iChannel = 0; iChannel < pChmap->channels; ++iChannel) { @@ -14456,7 +14841,7 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic /* If our channel map is invalid, fall back to defaults. */ if (!isValid) { - ma_get_standard_channel_map(ma_standard_channel_map_alsa, internalChannels, internalChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_alsa, internalChannelMap, ma_countof(internalChannelMap), internalChannels); } } @@ -14464,7 +14849,7 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic pChmap = NULL; } else { /* Could not retrieve the channel map. Fall back to a hard-coded assumption. */ - ma_get_standard_channel_map(ma_standard_channel_map_alsa, internalChannels, internalChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_alsa, internalChannelMap, ma_countof(internalChannelMap), internalChannels); } } @@ -14477,13 +14862,15 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic pollDescriptorCount = ((ma_snd_pcm_poll_descriptors_count_proc)pDevice->pContext->alsa.snd_pcm_poll_descriptors_count)(pPCM); if (pollDescriptorCount <= 0) { ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors count.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors count."); + return MA_ERROR; } - pPollDescriptors = (struct pollfd*)ma_malloc(sizeof(*pPollDescriptors) * (pollDescriptorCount + 1), &pDevice->pContext->allocationCallbacks/*, MA_ALLOCATION_TYPE_GENERAL*/); /* +1 because we want room for the wakeup descriptor. */ + pPollDescriptors = (struct pollfd*)ma_malloc(sizeof(*pPollDescriptors) * (pollDescriptorCount + 1), &pDevice->pContext->allocationCallbacks); /* +1 because we want room for the wakeup descriptor. */ if (pPollDescriptors == NULL) { ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to allocate memory for poll descriptors.", MA_OUT_OF_MEMORY); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to allocate memory for poll descriptors."); + return MA_OUT_OF_MEMORY; } /* @@ -14494,7 +14881,8 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic if (wakeupfd < 0) { ma_free(pPollDescriptors, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to create eventfd for poll wakeup.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to create eventfd for poll wakeup."); + return ma_result_from_errno(errno); } /* We'll place the wakeup fd at the start of the buffer. */ @@ -14508,7 +14896,8 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic close(wakeupfd); ma_free(pPollDescriptors, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors."); + return MA_ERROR; } if (deviceType == ma_device_type_capture) { @@ -14528,7 +14917,8 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic close(wakeupfd); ma_free(pPollDescriptors, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to prepare device.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to prepare device."); + return ma_result_from_errno(-resultALSA); } @@ -14584,7 +14974,8 @@ static ma_result ma_device_start__alsa(ma_device* pDevice) if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { resultALSA = ((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture); if (resultALSA < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start capture device.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start capture device."); + return ma_result_from_errno(-resultALSA); } } @@ -14635,7 +15026,8 @@ static ma_result ma_device_wait__alsa(ma_device* pDevice, ma_snd_pcm_t* pPCM, st int resultALSA; int resultPoll = poll(pPollDescriptors, pollDescriptorCount, -1); if (resultPoll < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] poll() failed.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] poll() failed."); + return ma_result_from_errno(errno); } /* @@ -14645,10 +15037,13 @@ static ma_result ma_device_wait__alsa(ma_device* pDevice, ma_snd_pcm_t* pPCM, st */ if ((pPollDescriptors[0].revents & POLLIN) != 0) { ma_uint64 t; - read(pPollDescriptors[0].fd, &t, sizeof(t)); /* <-- Important that we read here so that the next write() does not block. */ + int resultRead = read(pPollDescriptors[0].fd, &t, sizeof(t)); /* <-- Important that we read here so that the next write() does not block. */ + if (resultRead < 0) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] read() failed."); + return ma_result_from_errno(errno); + } ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] POLLIN set for wakeupfd\n"); - return MA_DEVICE_NOT_STARTED; } @@ -14658,11 +15053,13 @@ static ma_result ma_device_wait__alsa(ma_device* pDevice, ma_snd_pcm_t* pPCM, st */ resultALSA = ((ma_snd_pcm_poll_descriptors_revents_proc)pDevice->pContext->alsa.snd_pcm_poll_descriptors_revents)(pPCM, pPollDescriptors + 1, pollDescriptorCount - 1, &revents); /* +1, -1 to ignore the wakeup descriptor. */ if (resultALSA < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_poll_descriptors_revents() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_poll_descriptors_revents() failed."); + return ma_result_from_errno(-resultALSA); } if ((revents & POLLERR) != 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] POLLERR detected.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] POLLERR detected."); + return ma_result_from_errno(errno); } if ((revents & requiredEvent) == requiredEvent) { @@ -14694,7 +15091,7 @@ static ma_result ma_device_read__alsa(ma_device* pDevice, void* pFramesOut, ma_u *pFramesRead = 0; } - while (ma_device_get_state(pDevice) == MA_STATE_STARTED) { + while (ma_device_get_state(pDevice) == ma_device_state_started) { ma_result result; /* The first thing to do is wait for data to become available for reading. This will return an error code if the device has been stopped. */ @@ -14709,20 +15106,22 @@ static ma_result ma_device_read__alsa(ma_device* pDevice, void* pFramesOut, ma_u break; /* Success. */ } else { if (resultALSA == -EAGAIN) { - /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "TRACE: EGAIN (read)\n");*/ + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EGAIN (read)\n");*/ continue; /* Try again. */ } else if (resultALSA == -EPIPE) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "TRACE: EPIPE (read)\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EPIPE (read)\n"); /* Overrun. Recover and try again. If this fails we need to return an error. */ resultALSA = ((ma_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture, resultALSA, MA_TRUE); if (resultALSA < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover device after overrun.", ma_result_from_errno((int)-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover device after overrun."); + return ma_result_from_errno((int)-resultALSA); } resultALSA = ((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture); if (resultALSA < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start device after underrun.", ma_result_from_errno((int)-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start device after underrun."); + return ma_result_from_errno((int)-resultALSA); } continue; /* Try reading again. */ @@ -14748,7 +15147,7 @@ static ma_result ma_device_write__alsa(ma_device* pDevice, const void* pFrames, *pFramesWritten = 0; } - while (ma_device_get_state(pDevice) == MA_STATE_STARTED) { + while (ma_device_get_state(pDevice) == ma_device_state_started) { ma_result result; /* The first thing to do is wait for space to become available for writing. This will return an error code if the device has been stopped. */ @@ -14762,15 +15161,16 @@ static ma_result ma_device_write__alsa(ma_device* pDevice, const void* pFrames, break; /* Success. */ } else { if (resultALSA == -EAGAIN) { - /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "TRACE: EGAIN (write)\n");*/ + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EGAIN (write)\n");*/ continue; /* Try again. */ } else if (resultALSA == -EPIPE) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "TRACE: EPIPE (write)\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EPIPE (write)\n"); /* Underrun. Recover and try again. If this fails we need to return an error. */ resultALSA = ((ma_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback, resultALSA, MA_TRUE); /* MA_TRUE=silent (don't print anything on error). */ if (resultALSA < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover device after underrun.", ma_result_from_errno((int)-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover device after underrun."); + return ma_result_from_errno((int)-resultALSA); } /* @@ -14782,7 +15182,8 @@ static ma_result ma_device_write__alsa(ma_device* pDevice, const void* pFrames, */ resultALSA = ((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback); if (resultALSA < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start device after underrun.", ma_result_from_errno((int)-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start device after underrun."); + return ma_result_from_errno((int)-resultALSA); } continue; /* Try writing again. */ @@ -14800,6 +15201,7 @@ static ma_result ma_device_write__alsa(ma_device* pDevice, const void* pFrames, static ma_result ma_device_data_loop_wakeup__alsa(ma_device* pDevice) { ma_uint64 t = 1; + int resultWrite = 0; MA_ASSERT(pDevice != NULL); @@ -14807,10 +15209,15 @@ static ma_result ma_device_data_loop_wakeup__alsa(ma_device* pDevice) /* Write to an eventfd to trigger a wakeup from poll() and abort any reading or writing. */ if (pDevice->alsa.pPollDescriptorsCapture != NULL) { - write(pDevice->alsa.wakeupfdCapture, &t, sizeof(t)); + resultWrite = write(pDevice->alsa.wakeupfdCapture, &t, sizeof(t)); } if (pDevice->alsa.pPollDescriptorsPlayback != NULL) { - write(pDevice->alsa.wakeupfdPlayback, &t, sizeof(t)); + resultWrite = write(pDevice->alsa.wakeupfdPlayback, &t, sizeof(t)); + } + + if (resultWrite < 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] write() failed."); + return ma_result_from_errno(errno); } ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "Done\n"); @@ -14837,6 +15244,7 @@ static ma_result ma_context_uninit__alsa(ma_context* pContext) static ma_result ma_context_init__alsa(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) { + ma_result result; #ifndef MA_NO_RUNTIME_LINKING const char* libasoundNames[] = { "libasound.so.2", @@ -15061,8 +15469,10 @@ static ma_result ma_context_init__alsa(ma_context* pContext, const ma_context_co pContext->alsa.useVerboseDeviceEnumeration = pConfig->alsa.useVerboseDeviceEnumeration; - if (ma_mutex_init(&pContext->alsa.internalDeviceEnumLock) != MA_SUCCESS) { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[ALSA] WARNING: Failed to initialize mutex for internal device enumeration.", MA_ERROR); + result = ma_mutex_init(&pContext->alsa.internalDeviceEnumLock); + if (result != MA_SUCCESS) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] WARNING: Failed to initialize mutex for internal device enumeration."); + return result; } pCallbacks->onContextInit = ma_context_init__alsa; @@ -15164,7 +15574,7 @@ that point (it may still need to load files or whatnot). Instead, this callback stream be started which is how it works with literally *every* other callback-based audio API. Since miniaudio forbids firing of the data callback until the device has been started (as it should be with *all* callback based APIs), logic needs to be added to ensure miniaudio doesn't just blindly fire the application-defined data callback from within the PulseAudio callback before the stream has actually been -started. The device state is used for this - if the state is anything other than `MA_STATE_STARTING` or `MA_STATE_STARTED`, the main data +started. The device state is used for this - if the state is anything other than `ma_device_state_starting` or `ma_device_state_started`, the main data callback is not fired. This, unfortunately, is not the end of the problems with the PulseAudio write callback. Any normal callback based audio API will @@ -16003,7 +16413,8 @@ static ma_result ma_context_wait_for_pa_context_to_connect__pulse(ma_context* pC } if (state == MA_PA_CONTEXT_FAILED || state == MA_PA_CONTEXT_TERMINATED) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio context.", MA_ERROR); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio context."); + return MA_ERROR; } resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pContext->pulse.pMainLoop, 1, NULL); @@ -16028,7 +16439,8 @@ static ma_result ma_context_wait_for_pa_stream_to_connect__pulse(ma_context* pCo } if (state == MA_PA_STREAM_FAILED || state == MA_PA_STREAM_TERMINATED) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio stream.", MA_ERROR); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio stream."); + return MA_ERROR; } resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pContext->pulse.pMainLoop, 1, NULL); @@ -16494,7 +16906,7 @@ static void ma_device_on_read__pulse(ma_pa_stream* pStream, size_t byteCount, vo can fire this callback before the stream has even started. Ridiculous. */ deviceState = ma_device_get_state(pDevice); - if (deviceState != MA_STATE_STARTING && deviceState != MA_STATE_STARTED) { + if (deviceState != ma_device_state_starting && deviceState != ma_device_state_started) { return; } @@ -16504,7 +16916,7 @@ static void ma_device_on_read__pulse(ma_pa_stream* pStream, size_t byteCount, vo frameCount = byteCount / bpf; framesProcessed = 0; - while (ma_device_get_state(pDevice) == MA_STATE_STARTED && framesProcessed < frameCount) { + while (ma_device_get_state(pDevice) == ma_device_state_started && framesProcessed < frameCount) { const void* pMappedPCMFrames; size_t bytesMapped; ma_uint64 framesMapped; @@ -16566,7 +16978,7 @@ static ma_result ma_device_write_to_stream__pulse(ma_device* pDevice, ma_pa_stre framesMapped = bytesMapped / bpf; - if (deviceState == MA_STATE_STARTED || deviceState == MA_STATE_STARTING) { /* Check for starting state just in case this is being used to do the initial fill. */ + if (deviceState == ma_device_state_started || deviceState == ma_device_state_starting) { /* Check for starting state just in case this is being used to do the initial fill. */ ma_device_handle_backend_data_callback(pDevice, pMappedPCMFrames, NULL, framesMapped); } else { /* Device is not started. Write silence. */ @@ -16613,7 +17025,7 @@ static void ma_device_on_write__pulse(ma_pa_stream* pStream, size_t byteCount, v can fire this callback before the stream has even started. Ridiculous. */ deviceState = ma_device_get_state(pDevice); - if (deviceState != MA_STATE_STARTING && deviceState != MA_STATE_STARTED) { + if (deviceState != ma_device_state_starting && deviceState != ma_device_state_started) { return; } @@ -16628,7 +17040,7 @@ static void ma_device_on_write__pulse(ma_pa_stream* pStream, size_t byteCount, v /* Don't keep trying to process frames if the device isn't started. */ deviceState = ma_device_get_state(pDevice); - if (deviceState != MA_STATE_STARTING && deviceState != MA_STATE_STARTED) { + if (deviceState != ma_device_state_starting && deviceState != ma_device_state_started) { break; } @@ -16657,13 +17069,13 @@ static void ma_device_on_suspended__pulse(ma_pa_stream* pStream, void* pUserData if (suspended == 1) { ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[Pulse] Device suspended state changed. Suspended.\n"); - + if (pDevice->onStop) { pDevice->onStop(pDevice); } } else { ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[Pulse] Device suspended state changed. Resumed.\n"); - } + } } static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) @@ -16738,7 +17150,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { result = ma_context_get_source_info__pulse(pDevice->pContext, devCapture, &sourceInfo); if (result != MA_SUCCESS) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve source info for capture device.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve source info for capture device."); goto on_error0; } @@ -16770,7 +17182,8 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi pDevice->pulse.pStreamCapture = ma_context__pa_stream_new__pulse(pDevice->pContext, pConfig->pulse.pStreamNameCapture, &ss, &cmap); if (pDevice->pulse.pStreamCapture == NULL) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio capture stream.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio capture stream."); + result = MA_ERROR; goto on_error0; } @@ -16790,7 +17203,8 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi error = ((ma_pa_stream_connect_record_proc)pDevice->pContext->pulse.pa_stream_connect_record)((ma_pa_stream*)pDevice->pulse.pStreamCapture, devCapture, &attr, streamFlags); if (error != MA_PA_OK) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio capture stream.", ma_result_from_pulse(error)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio capture stream."); + result = ma_result_from_pulse(error); goto on_error1; } @@ -16843,7 +17257,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { result = ma_context_get_sink_info__pulse(pDevice->pContext, devPlayback, &sinkInfo); if (result != MA_SUCCESS) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve sink info for playback device.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve sink info for playback device."); goto on_error2; } @@ -16876,14 +17290,15 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi pDevice->pulse.pStreamPlayback = ma_context__pa_stream_new__pulse(pDevice->pContext, pConfig->pulse.pStreamNamePlayback, &ss, &cmap); if (pDevice->pulse.pStreamPlayback == NULL) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio playback stream.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio playback stream."); + result = MA_ERROR; goto on_error2; } /* Note that this callback will be fired as soon as the stream is connected, even though it's started as corked. The callback needs to handle a - device state of MA_STATE_UNINITIALIZED. + device state of ma_device_state_uninitialized. */ ((ma_pa_stream_set_write_callback_proc)pDevice->pContext->pulse.pa_stream_set_write_callback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_device_on_write__pulse, pDevice); @@ -16899,7 +17314,8 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi error = ((ma_pa_stream_connect_playback_proc)pDevice->pContext->pulse.pa_stream_connect_playback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, devPlayback, &attr, streamFlags, NULL, NULL); if (error != MA_PA_OK) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio playback stream.", ma_result_from_pulse(error)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio playback stream."); + result = ma_result_from_pulse(error); goto on_error3; } @@ -16957,9 +17373,13 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi onDeviceDataLoop callback is NULL, which is not the case for PulseAudio. */ if (pConfig->deviceType == ma_device_type_duplex) { - result = ma_duplex_rb_init(format, channels, sampleRate, pDescriptorCapture->sampleRate, pDescriptorCapture->periodSizeInFrames, &pDevice->pContext->allocationCallbacks, &pDevice->duplexRB); + ma_format rbFormat = (format != ma_format_unknown) ? format : pDescriptorCapture->format; + ma_uint32 rbChannels = (channels > 0) ? channels : pDescriptorCapture->channels; + ma_uint32 rbSampleRate = (sampleRate > 0) ? sampleRate : pDescriptorCapture->sampleRate; + + result = ma_duplex_rb_init(rbFormat, rbChannels, rbSampleRate, pDescriptorCapture->sampleRate, pDescriptorCapture->periodSizeInFrames, &pDevice->pContext->allocationCallbacks, &pDevice->duplexRB); if (result != MA_SUCCESS) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to initialize ring buffer.", result); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to initialize ring buffer. %s.\n", ma_result_description(result)); goto on_error4; } } @@ -17018,20 +17438,19 @@ static ma_result ma_device__cork_stream__pulse(ma_device* pDevice, ma_device_typ pOP = ((ma_pa_stream_cork_proc)pContext->pulse.pa_stream_cork)(pStream, cork, ma_pulse_operation_complete_callback, &wasSuccessful); if (pOP == NULL) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to cork PulseAudio stream.", (cork == 0) ? MA_FAILED_TO_START_BACKEND_DEVICE : MA_FAILED_TO_STOP_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to cork PulseAudio stream."); + return MA_ERROR; } result = ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pOP); if (result != MA_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while waiting for the PulseAudio stream to cork.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while waiting for the PulseAudio stream to cork."); + return result; } if (!wasSuccessful) { - if (cork) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to stop PulseAudio stream.", MA_FAILED_TO_STOP_BACKEND_DEVICE); - } else { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to start PulseAudio stream.", MA_FAILED_TO_START_BACKEND_DEVICE); - } + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to %s PulseAudio stream.", (cork) ? "stop" : "start"); + return MA_ERROR; } return MA_SUCCESS; @@ -17106,7 +17525,7 @@ static ma_result ma_device_data_loop__pulse(ma_device* pDevice) All data is handled through callbacks. All we need to do is iterate over the main loop and let the callbacks deal with it. */ - while (ma_device_get_state(pDevice) == MA_STATE_STARTED) { + while (ma_device_get_state(pDevice) == ma_device_state_started) { resultPA = ((ma_pa_mainloop_iterate_proc)pDevice->pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pContext->pulse.pMainLoop, 1, NULL); if (resultPA < 0) { break; @@ -17351,27 +17770,27 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c /* The PulseAudio context maps well to miniaudio's notion of a context. The pa_context object will be initialized as part of the ma_context. */ pContext->pulse.pMainLoop = ((ma_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); if (pContext->pulse.pMainLoop == NULL) { - result = ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create mainloop.", MA_FAILED_TO_INIT_BACKEND); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create mainloop."); #ifndef MA_NO_RUNTIME_LINKING ma_dlclose(pContext, pContext->pulse.pulseSO); #endif - return result; + return MA_FAILED_TO_INIT_BACKEND; } pContext->pulse.pPulseContext = ((ma_pa_context_new_proc)pContext->pulse.pa_context_new)(((ma_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)((ma_pa_mainloop*)pContext->pulse.pMainLoop), pConfig->pulse.pApplicationName); if (pContext->pulse.pPulseContext == NULL) { - result = ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio context.", MA_FAILED_TO_INIT_BACKEND); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio context."); ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pContext->pulse.pMainLoop)); #ifndef MA_NO_RUNTIME_LINKING ma_dlclose(pContext, pContext->pulse.pulseSO); #endif - return result; + return MA_FAILED_TO_INIT_BACKEND; } /* Now we need to connect to the context. Everything is asynchronous so we need to wait for it to connect before returning. */ result = ma_result_from_pulse(((ma_pa_context_connect_proc)pContext->pulse.pa_context_connect)((ma_pa_context*)pContext->pulse.pPulseContext, pConfig->pulse.pServerName, (pConfig->pulse.tryAutoSpawn) ? 0 : MA_PA_CONTEXT_NOAUTOSPAWN, NULL)); if (result != MA_SUCCESS) { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio context.", result); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio context."); ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pContext->pulse.pMainLoop)); #ifndef MA_NO_RUNTIME_LINKING ma_dlclose(pContext, pContext->pulse.pulseSO); @@ -17554,7 +17973,8 @@ static ma_result ma_context_get_device_info__jack(ma_context* pContext, ma_devic /* The channel count and sample rate can only be determined by opening the device. */ result = ma_context_open_client__jack(pContext, &pClient); if (result != MA_SUCCESS) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[JACK] Failed to open client.", result); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[JACK] Failed to open client."); + return result; } pDeviceInfo->nativeDataFormats[0].sampleRate = ((ma_jack_get_sample_rate_proc)pContext->jack.jack_get_sample_rate)((ma_jack_client_t*)pClient); @@ -17563,7 +17983,8 @@ static ma_result ma_context_get_device_info__jack(ma_context* pContext, ma_devic ppPorts = ((ma_jack_get_ports_proc)pContext->jack.jack_get_ports)((ma_jack_client_t*)pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ((deviceType == ma_device_type_playback) ? ma_JackPortIsInput : ma_JackPortIsOutput)); if (ppPorts == NULL) { ((ma_jack_client_close_proc)pContext->jack.jack_client_close)((ma_jack_client_t*)pClient); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } while (ppPorts[pDeviceInfo->nativeDataFormats[0].channels] != NULL) { @@ -17595,11 +18016,13 @@ static ma_result ma_device_uninit__jack(ma_device* pDevice) } if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ma__free_from_callbacks(pDevice->jack.pIntermediaryBufferCapture, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->jack.pIntermediaryBufferCapture, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->jack.ppPortsCapture, &pDevice->pContext->allocationCallbacks); } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ma__free_from_callbacks(pDevice->jack.pIntermediaryBufferPlayback, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->jack.pIntermediaryBufferPlayback, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->jack.ppPortsPlayback, &pDevice->pContext->allocationCallbacks); } return MA_SUCCESS; @@ -17621,12 +18044,12 @@ static int ma_device__jack_buffer_size_callback(ma_jack_nframes_t frameCount, vo if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { size_t newBufferSize = frameCount * (pDevice->capture.internalChannels * ma_get_bytes_per_sample(pDevice->capture.internalFormat)); - float* pNewBuffer = (float*)ma__calloc_from_callbacks(newBufferSize, &pDevice->pContext->allocationCallbacks); + float* pNewBuffer = (float*)ma_calloc(newBufferSize, &pDevice->pContext->allocationCallbacks); if (pNewBuffer == NULL) { return MA_OUT_OF_MEMORY; } - ma__free_from_callbacks(pDevice->jack.pIntermediaryBufferCapture, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->jack.pIntermediaryBufferCapture, &pDevice->pContext->allocationCallbacks); pDevice->jack.pIntermediaryBufferCapture = pNewBuffer; pDevice->playback.internalPeriodSizeInFrames = frameCount; @@ -17634,12 +18057,12 @@ static int ma_device__jack_buffer_size_callback(ma_jack_nframes_t frameCount, vo if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { size_t newBufferSize = frameCount * (pDevice->playback.internalChannels * ma_get_bytes_per_sample(pDevice->playback.internalFormat)); - float* pNewBuffer = (float*)ma__calloc_from_callbacks(newBufferSize, &pDevice->pContext->allocationCallbacks); + float* pNewBuffer = (float*)ma_calloc(newBufferSize, &pDevice->pContext->allocationCallbacks); if (pNewBuffer == NULL) { return MA_OUT_OF_MEMORY; } - ma__free_from_callbacks(pDevice->jack.pIntermediaryBufferPlayback, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->jack.pIntermediaryBufferPlayback, &pDevice->pContext->allocationCallbacks); pDevice->jack.pIntermediaryBufferPlayback = pNewBuffer; pDevice->playback.internalPeriodSizeInFrames = frameCount; @@ -17663,7 +18086,7 @@ static int ma_device__jack_process_callback(ma_jack_nframes_t frameCount, void* if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { /* Channels need to be interleaved. */ for (iChannel = 0; iChannel < pDevice->capture.internalChannels; ++iChannel) { - const float* pSrc = (const float*)((ma_jack_port_get_buffer_proc)pContext->jack.jack_port_get_buffer)((ma_jack_port_t*)pDevice->jack.pPortsCapture[iChannel], frameCount); + const float* pSrc = (const float*)((ma_jack_port_get_buffer_proc)pContext->jack.jack_port_get_buffer)((ma_jack_port_t*)pDevice->jack.ppPortsCapture[iChannel], frameCount); if (pSrc != NULL) { float* pDst = pDevice->jack.pIntermediaryBufferCapture + iChannel; ma_jack_nframes_t iFrame; @@ -17684,7 +18107,7 @@ static int ma_device__jack_process_callback(ma_jack_nframes_t frameCount, void* /* Channels need to be deinterleaved. */ for (iChannel = 0; iChannel < pDevice->playback.internalChannels; ++iChannel) { - float* pDst = (float*)((ma_jack_port_get_buffer_proc)pContext->jack.jack_port_get_buffer)((ma_jack_port_t*)pDevice->jack.pPortsPlayback[iChannel], frameCount); + float* pDst = (float*)((ma_jack_port_get_buffer_proc)pContext->jack.jack_port_get_buffer)((ma_jack_port_t*)pDevice->jack.ppPortsPlayback[iChannel], frameCount); if (pDst != NULL) { const float* pSrc = pDevice->jack.pIntermediaryBufferPlayback + iChannel; ma_jack_nframes_t iFrame; @@ -17710,33 +18133,39 @@ static ma_result ma_device_init__jack(ma_device* pDevice, const ma_device_config MA_ASSERT(pDevice != NULL); if (pConfig->deviceType == ma_device_type_loopback) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Loopback mode not supported."); return MA_DEVICE_TYPE_NOT_SUPPORTED; } /* Only supporting default devices with JACK. */ if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->pDeviceID != NULL && pDescriptorPlayback->pDeviceID->jack != 0) || ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->pDeviceID != NULL && pDescriptorCapture->pDeviceID->jack != 0)) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Only default devices are supported."); return MA_NO_DEVICE; } /* No exclusive mode with the JACK backend. */ if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Exclusive mode not supported."); return MA_SHARE_MODE_NOT_SUPPORTED; } /* Open the client. */ result = ma_context_open_client__jack(pDevice->pContext, (ma_jack_client_t**)&pDevice->jack.pClient); if (result != MA_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to open client.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to open client."); + return result; } /* Callbacks. */ if (((ma_jack_set_process_callback_proc)pDevice->pContext->jack.jack_set_process_callback)((ma_jack_client_t*)pDevice->jack.pClient, ma_device__jack_process_callback, pDevice) != 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to set process callback.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to set process callback."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } if (((ma_jack_set_buffer_size_callback_proc)pDevice->pContext->jack.jack_set_buffer_size_callback)((ma_jack_client_t*)pDevice->jack.pClient, ma_device__jack_buffer_size_callback, pDevice) != 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to set buffer size callback.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to set buffer size callback."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } ((ma_jack_on_shutdown_proc)pDevice->pContext->jack.jack_on_shutdown)((ma_jack_client_t*)pDevice->jack.pClient, ma_device__jack_shutdown_callback, pDevice); @@ -17746,31 +18175,42 @@ static ma_result ma_device_init__jack(ma_device* pDevice, const ma_device_config periodSizeInFrames = ((ma_jack_get_buffer_size_proc)pDevice->pContext->jack.jack_get_buffer_size)((ma_jack_client_t*)pDevice->jack.pClient); if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { + ma_uint32 iPort; const char** ppPorts; pDescriptorCapture->format = ma_format_f32; pDescriptorCapture->channels = 0; pDescriptorCapture->sampleRate = ((ma_jack_get_sample_rate_proc)pDevice->pContext->jack.jack_get_sample_rate)((ma_jack_client_t*)pDevice->jack.pClient); - ma_get_standard_channel_map(ma_standard_channel_map_alsa, pDescriptorCapture->channels, pDescriptorCapture->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_alsa, pDescriptorCapture->channelMap, ma_countof(pDescriptorCapture->channelMap), pDescriptorCapture->channels); ppPorts = ((ma_jack_get_ports_proc)pDevice->pContext->jack.jack_get_ports)((ma_jack_client_t*)pDevice->jack.pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ma_JackPortIsOutput); if (ppPorts == NULL) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } + /* Need to count the number of ports first so we can allocate some memory. */ while (ppPorts[pDescriptorCapture->channels] != NULL) { + pDescriptorCapture->channels += 1; + } + + pDevice->jack.ppPortsCapture = (ma_ptr*)ma_malloc(sizeof(*pDevice->jack.ppPortsCapture) * pDescriptorCapture->channels, &pDevice->pContext->allocationCallbacks); + if (pDevice->jack.ppPortsCapture == NULL) { + return MA_OUT_OF_MEMORY; + } + + for (iPort = 0; iPort < pDescriptorCapture->channels; iPort += 1) { char name[64]; ma_strcpy_s(name, sizeof(name), "capture"); - ma_itoa_s((int)pDescriptorCapture->channels, name+7, sizeof(name)-7, 10); /* 7 = length of "capture" */ + ma_itoa_s((int)iPort, name+7, sizeof(name)-7, 10); /* 7 = length of "capture" */ - pDevice->jack.pPortsCapture[pDescriptorCapture->channels] = ((ma_jack_port_register_proc)pDevice->pContext->jack.jack_port_register)((ma_jack_client_t*)pDevice->jack.pClient, name, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsInput, 0); - if (pDevice->jack.pPortsCapture[pDescriptorCapture->channels] == NULL) { + pDevice->jack.ppPortsCapture[iPort] = ((ma_jack_port_register_proc)pDevice->pContext->jack.jack_port_register)((ma_jack_client_t*)pDevice->jack.pClient, name, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsInput, 0); + if (pDevice->jack.ppPortsCapture[iPort] == NULL) { ((ma_jack_free_proc)pDevice->pContext->jack.jack_free)((void*)ppPorts); ma_device_uninit__jack(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to register ports.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to register ports."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } - - pDescriptorCapture->channels += 1; } ((ma_jack_free_proc)pDevice->pContext->jack.jack_free)((void*)ppPorts); @@ -17778,7 +18218,7 @@ static ma_result ma_device_init__jack(ma_device* pDevice, const ma_device_config pDescriptorCapture->periodSizeInFrames = periodSizeInFrames; pDescriptorCapture->periodCount = 1; /* There's no notion of a period in JACK. Just set to 1. */ - pDevice->jack.pIntermediaryBufferCapture = (float*)ma__calloc_from_callbacks(pDescriptorCapture->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels), &pDevice->pContext->allocationCallbacks); + pDevice->jack.pIntermediaryBufferCapture = (float*)ma_calloc(pDescriptorCapture->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels), &pDevice->pContext->allocationCallbacks); if (pDevice->jack.pIntermediaryBufferCapture == NULL) { ma_device_uninit__jack(pDevice); return MA_OUT_OF_MEMORY; @@ -17786,31 +18226,43 @@ static ma_result ma_device_init__jack(ma_device* pDevice, const ma_device_config } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { + ma_uint32 iPort; const char** ppPorts; pDescriptorPlayback->format = ma_format_f32; pDescriptorPlayback->channels = 0; pDescriptorPlayback->sampleRate = ((ma_jack_get_sample_rate_proc)pDevice->pContext->jack.jack_get_sample_rate)((ma_jack_client_t*)pDevice->jack.pClient); - ma_get_standard_channel_map(ma_standard_channel_map_alsa, pDescriptorPlayback->channels, pDescriptorPlayback->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_alsa, pDescriptorPlayback->channelMap, ma_countof(pDescriptorPlayback->channelMap), pDescriptorPlayback->channels); ppPorts = ((ma_jack_get_ports_proc)pDevice->pContext->jack.jack_get_ports)((ma_jack_client_t*)pDevice->jack.pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ma_JackPortIsInput); if (ppPorts == NULL) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } + /* Need to count the number of ports first so we can allocate some memory. */ while (ppPorts[pDescriptorPlayback->channels] != NULL) { + pDescriptorPlayback->channels += 1; + } + + pDevice->jack.ppPortsPlayback = (ma_ptr*)ma_malloc(sizeof(*pDevice->jack.ppPortsPlayback) * pDescriptorPlayback->channels, &pDevice->pContext->allocationCallbacks); + if (pDevice->jack.ppPortsPlayback == NULL) { + ma_free(pDevice->jack.ppPortsCapture, &pDevice->pContext->allocationCallbacks); + return MA_OUT_OF_MEMORY; + } + + for (iPort = 0; iPort < pDescriptorPlayback->channels; iPort += 1) { char name[64]; ma_strcpy_s(name, sizeof(name), "playback"); - ma_itoa_s((int)pDescriptorPlayback->channels, name+8, sizeof(name)-8, 10); /* 8 = length of "playback" */ + ma_itoa_s((int)iPort, name+8, sizeof(name)-8, 10); /* 8 = length of "playback" */ - pDevice->jack.pPortsPlayback[pDescriptorPlayback->channels] = ((ma_jack_port_register_proc)pDevice->pContext->jack.jack_port_register)((ma_jack_client_t*)pDevice->jack.pClient, name, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsOutput, 0); - if (pDevice->jack.pPortsPlayback[pDescriptorPlayback->channels] == NULL) { + pDevice->jack.ppPortsPlayback[iPort] = ((ma_jack_port_register_proc)pDevice->pContext->jack.jack_port_register)((ma_jack_client_t*)pDevice->jack.pClient, name, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsOutput, 0); + if (pDevice->jack.ppPortsPlayback[iPort] == NULL) { ((ma_jack_free_proc)pDevice->pContext->jack.jack_free)((void*)ppPorts); ma_device_uninit__jack(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to register ports.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to register ports."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } - - pDescriptorPlayback->channels += 1; } ((ma_jack_free_proc)pDevice->pContext->jack.jack_free)((void*)ppPorts); @@ -17818,7 +18270,7 @@ static ma_result ma_device_init__jack(ma_device* pDevice, const ma_device_config pDescriptorPlayback->periodSizeInFrames = periodSizeInFrames; pDescriptorPlayback->periodCount = 1; /* There's no notion of a period in JACK. Just set to 1. */ - pDevice->jack.pIntermediaryBufferPlayback = (float*)ma__calloc_from_callbacks(pDescriptorPlayback->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels), &pDevice->pContext->allocationCallbacks); + pDevice->jack.pIntermediaryBufferPlayback = (float*)ma_calloc(pDescriptorPlayback->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels), &pDevice->pContext->allocationCallbacks); if (pDevice->jack.pIntermediaryBufferPlayback == NULL) { ma_device_uninit__jack(pDevice); return MA_OUT_OF_MEMORY; @@ -17837,25 +18289,28 @@ static ma_result ma_device_start__jack(ma_device* pDevice) resultJACK = ((ma_jack_activate_proc)pContext->jack.jack_activate)((ma_jack_client_t*)pDevice->jack.pClient); if (resultJACK != 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to activate the JACK client.", MA_FAILED_TO_START_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to activate the JACK client."); + return MA_FAILED_TO_START_BACKEND_DEVICE; } if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { const char** ppServerPorts = ((ma_jack_get_ports_proc)pContext->jack.jack_get_ports)((ma_jack_client_t*)pDevice->jack.pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ma_JackPortIsOutput); if (ppServerPorts == NULL) { ((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to retrieve physical ports.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to retrieve physical ports."); + return MA_ERROR; } for (i = 0; ppServerPorts[i] != NULL; ++i) { const char* pServerPort = ppServerPorts[i]; - const char* pClientPort = ((ma_jack_port_name_proc)pContext->jack.jack_port_name)((ma_jack_port_t*)pDevice->jack.pPortsCapture[i]); + const char* pClientPort = ((ma_jack_port_name_proc)pContext->jack.jack_port_name)((ma_jack_port_t*)pDevice->jack.ppPortsCapture[i]); resultJACK = ((ma_jack_connect_proc)pContext->jack.jack_connect)((ma_jack_client_t*)pDevice->jack.pClient, pServerPort, pClientPort); if (resultJACK != 0) { ((ma_jack_free_proc)pContext->jack.jack_free)((void*)ppServerPorts); ((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to connect ports.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to connect ports."); + return MA_ERROR; } } @@ -17866,18 +18321,20 @@ static ma_result ma_device_start__jack(ma_device* pDevice) const char** ppServerPorts = ((ma_jack_get_ports_proc)pContext->jack.jack_get_ports)((ma_jack_client_t*)pDevice->jack.pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ma_JackPortIsInput); if (ppServerPorts == NULL) { ((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to retrieve physical ports.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to retrieve physical ports."); + return MA_ERROR; } for (i = 0; ppServerPorts[i] != NULL; ++i) { const char* pServerPort = ppServerPorts[i]; - const char* pClientPort = ((ma_jack_port_name_proc)pContext->jack.jack_port_name)((ma_jack_port_t*)pDevice->jack.pPortsPlayback[i]); + const char* pClientPort = ((ma_jack_port_name_proc)pContext->jack.jack_port_name)((ma_jack_port_t*)pDevice->jack.ppPortsPlayback[i]); resultJACK = ((ma_jack_connect_proc)pContext->jack.jack_connect)((ma_jack_client_t*)pDevice->jack.pClient, pClientPort, pServerPort); if (resultJACK != 0) { ((ma_jack_free_proc)pContext->jack.jack_free)((void*)ppServerPorts); ((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to connect ports.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to connect ports."); + return MA_ERROR; } } @@ -17893,7 +18350,8 @@ static ma_result ma_device_stop__jack(ma_device* pDevice) ma_stop_proc onStop; if (((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient) != 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] An error occurred when deactivating the JACK client.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] An error occurred when deactivating the JACK client."); + return MA_ERROR; } onStop = pDevice->onStop; @@ -18064,6 +18522,13 @@ References #if defined(TARGET_OS_WATCH) && TARGET_OS_WATCH == 1 #define MA_APPLE_WATCH #endif + #if __has_feature(objc_arc) + #define MA_BRIDGE_TRANSFER __bridge_transfer + #define MA_BRIDGE_RETAINED __bridge_retained + #else + #define MA_BRIDGE_TRANSFER + #define MA_BRIDGE_RETAINED + #endif #else #define MA_APPLE_DESKTOP #endif @@ -18402,7 +18867,7 @@ static ma_result ma_get_channel_map_from_AudioChannelLayout(AudioChannelLayout* case kAudioChannelLayoutTag_Binaural: case kAudioChannelLayoutTag_Ambisonic_B_Format: { - ma_get_standard_channel_map(ma_standard_channel_map_default, channelCount, pChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, channelCount); } break; case kAudioChannelLayoutTag_Octagonal: @@ -18430,7 +18895,7 @@ static ma_result ma_get_channel_map_from_AudioChannelLayout(AudioChannelLayout* default: { - ma_get_standard_channel_map(ma_standard_channel_map_default, channelCount, pChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, channelCount); } break; } } @@ -18567,14 +19032,14 @@ static ma_bool32 ma_does_AudioObject_support_scope(ma_context* pContext, AudioOb return MA_FALSE; } - pBufferList = (AudioBufferList*)ma__malloc_from_callbacks(dataSize, &pContext->allocationCallbacks); + pBufferList = (AudioBufferList*)ma_malloc(dataSize, &pContext->allocationCallbacks); if (pBufferList == NULL) { return MA_FALSE; /* Out of memory. */ } status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, pBufferList); if (status != noErr) { - ma__free_from_callbacks(pBufferList, &pContext->allocationCallbacks); + ma_free(pBufferList, &pContext->allocationCallbacks); return MA_FALSE; } @@ -18583,7 +19048,7 @@ static ma_bool32 ma_does_AudioObject_support_scope(ma_context* pContext, AudioOb isSupported = MA_TRUE; } - ma__free_from_callbacks(pBufferList, &pContext->allocationCallbacks); + ma_free(pBufferList, &pContext->allocationCallbacks); return isSupported; } @@ -19212,24 +19677,24 @@ static ma_result ma_get_AudioUnit_channel_map(ma_context* pContext, AudioUnit au return ma_result_from_OSStatus(status); } - pChannelLayout = (AudioChannelLayout*)ma__malloc_from_callbacks(channelLayoutSize, &pContext->allocationCallbacks); + pChannelLayout = (AudioChannelLayout*)ma_malloc(channelLayoutSize, &pContext->allocationCallbacks); if (pChannelLayout == NULL) { return MA_OUT_OF_MEMORY; } status = ((ma_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)(audioUnit, kAudioUnitProperty_AudioChannelLayout, deviceScope, deviceBus, pChannelLayout, &channelLayoutSize); if (status != noErr) { - ma__free_from_callbacks(pChannelLayout, &pContext->allocationCallbacks); + ma_free(pChannelLayout, &pContext->allocationCallbacks); return ma_result_from_OSStatus(status); } result = ma_get_channel_map_from_AudioChannelLayout(pChannelLayout, pChannelMap, channelMapCap); if (result != MA_SUCCESS) { - ma__free_from_callbacks(pChannelLayout, &pContext->allocationCallbacks); + ma_free(pChannelLayout, &pContext->allocationCallbacks); return result; } - ma__free_from_callbacks(pChannelLayout, &pContext->allocationCallbacks); + ma_free(pChannelLayout, &pContext->allocationCallbacks); return MA_SUCCESS; } #endif /* MA_APPLE_DESKTOP */ @@ -19581,7 +20046,7 @@ static AudioBufferList* ma_allocate_AudioBufferList__coreaudio(ma_uint32 sizeInF allocationSize += sizeInFrames * ma_get_bytes_per_frame(format, channels); - pBufferList = (AudioBufferList*)ma__malloc_from_callbacks(allocationSize, pAllocationCallbacks); + pBufferList = (AudioBufferList*)ma_malloc(allocationSize, pAllocationCallbacks); if (pBufferList == NULL) { return NULL; } @@ -19622,7 +20087,7 @@ static ma_result ma_device_realloc_AudioBufferList__coreaudio(ma_device* pDevice } /* At this point we'll have a new AudioBufferList and we can free the old one. */ - ma__free_from_callbacks(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); pDevice->coreaudio.pAudioBufferList = pNewAudioBufferList; pDevice->coreaudio.audioBufferCapInFrames = sizeInFrames; } @@ -19639,7 +20104,7 @@ static OSStatus ma_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFl MA_ASSERT(pDevice != NULL); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "INFO: Output Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", busNumber, frameCount, pBufferList->mNumberBuffers); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "INFO: Output Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", (int)busNumber, (int)frameCount, (int)pBufferList->mNumberBuffers); /* We need to check whether or not we are outputting interleaved or non-interleaved samples. The way we do this is slightly different for each type. */ layout = ma_stream_layout_interleaved; @@ -19657,7 +20122,7 @@ static OSStatus ma_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFl ma_device_handle_backend_data_callback(pDevice, pBufferList->mBuffers[iBuffer].mData, NULL, frameCountForThisBuffer); } - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pBufferList->mBuffers[iBuffer].mNumberChannels, pBufferList->mBuffers[iBuffer].mDataByteSize); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", (int)frameCount, (int)pBufferList->mBuffers[iBuffer].mNumberChannels, (int)pBufferList->mBuffers[iBuffer].mDataByteSize); } else { /* This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's @@ -19665,7 +20130,7 @@ static OSStatus ma_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFl output silence here. */ MA_ZERO_MEMORY(pBufferList->mBuffers[iBuffer].mData, pBufferList->mBuffers[iBuffer].mDataByteSize); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pBufferList->mBuffers[iBuffer].mNumberChannels, pBufferList->mBuffers[iBuffer].mDataByteSize); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", (int)frameCount, (int)pBufferList->mBuffers[iBuffer].mNumberChannels, (int)pBufferList->mBuffers[iBuffer].mDataByteSize); } } } else { @@ -19734,7 +20199,7 @@ static OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFla layout = ma_stream_layout_deinterleaved; } - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "INFO: Input Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", busNumber, frameCount, pRenderedBufferList->mNumberBuffers); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "INFO: Input Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", (int)busNumber, (int)frameCount, (int)pRenderedBufferList->mNumberBuffers); /* There has been a situation reported where frame count passed into this function is greater than the capacity of @@ -19762,7 +20227,7 @@ static OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFla status = ((ma_AudioUnitRender_proc)pDevice->pContext->coreaudio.AudioUnitRender)((AudioUnit)pDevice->coreaudio.audioUnitCapture, pActionFlags, pTimeStamp, busNumber, frameCount, pRenderedBufferList); if (status != noErr) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " ERROR: AudioUnitRender() failed with %d\n", status); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " ERROR: AudioUnitRender() failed with %d\n", (int)status); return status; } @@ -19770,7 +20235,7 @@ static OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFla for (iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; ++iBuffer) { if (pRenderedBufferList->mBuffers[iBuffer].mNumberChannels == pDevice->capture.internalChannels) { ma_device_handle_backend_data_callback(pDevice, NULL, pRenderedBufferList->mBuffers[iBuffer].mData, frameCount); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " mDataByteSize=%d\n", pRenderedBufferList->mBuffers[iBuffer].mDataByteSize); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " mDataByteSize=%d\n", (int)pRenderedBufferList->mBuffers[iBuffer].mDataByteSize); } else { /* This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's @@ -19793,7 +20258,7 @@ static OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFla framesRemaining -= framesToSend; } - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pRenderedBufferList->mBuffers[iBuffer].mNumberChannels, pRenderedBufferList->mBuffers[iBuffer].mDataByteSize); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", (int)frameCount, (int)pRenderedBufferList->mBuffers[iBuffer].mNumberChannels, (int)pRenderedBufferList->mBuffers[iBuffer].mDataByteSize); } } } else { @@ -19855,7 +20320,7 @@ static void on_start_stop__coreaudio(void* pUserData, AudioUnit audioUnit, Audio can try waiting on the same lock. I'm going to try working around this by not calling any Core Audio APIs in the callback when the device has been stopped or uninitialized. */ - if (ma_device_get_state(pDevice) == MA_STATE_UNINITIALIZED || ma_device_get_state(pDevice) == MA_STATE_STOPPING || ma_device_get_state(pDevice) == MA_STATE_STOPPED) { + if (ma_device_get_state(pDevice) == ma_device_state_uninitialized || ma_device_get_state(pDevice) == ma_device_state_stopping || ma_device_get_state(pDevice) == ma_device_state_stopped) { ma_stop_proc onStop = pDevice->onStop; if (onStop) { onStop(pDevice); @@ -19963,7 +20428,7 @@ static OSStatus ma_default_device_changed__coreaudio(AudioObjectID objectID, UIn ma_device__post_init_setup(pDevice, deviceType); /* Restart the device if required. If this fails we need to stop the device entirely. */ - if (ma_device_get_state(pDevice) == MA_STATE_STARTED) { + if (ma_device_get_state(pDevice) == ma_device_state_started) { OSStatus status; if (deviceType == ma_device_type_playback) { status = ((ma_AudioOutputUnitStart_proc)pDevice->pContext->coreaudio.AudioOutputUnitStart)((AudioUnit)pDevice->coreaudio.audioUnitPlayback); @@ -19971,7 +20436,7 @@ static OSStatus ma_default_device_changed__coreaudio(AudioObjectID objectID, UIn if (pDevice->type == ma_device_type_duplex) { ((ma_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitCapture); } - ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_device__set_state(pDevice, ma_device_state_stopped); } } else if (deviceType == ma_device_type_capture) { status = ((ma_AudioOutputUnitStart_proc)pDevice->pContext->coreaudio.AudioOutputUnitStart)((AudioUnit)pDevice->coreaudio.audioUnitCapture); @@ -19979,7 +20444,7 @@ static OSStatus ma_default_device_changed__coreaudio(AudioObjectID objectID, UIn if (pDevice->type == ma_device_type_duplex) { ((ma_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitPlayback); } - ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_device__set_state(pDevice, ma_device_state_stopped); } } } @@ -20046,7 +20511,7 @@ static ma_result ma_context__uninit_device_tracking__coreaudio(ma_context* pCont /* At this point there should be no tracked devices. If not there's an error somewhere. */ if (g_ppTrackedDevices_CoreAudio != NULL) { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "You have uninitialized all contexts while an associated device is still active.", MA_INVALID_OPERATION); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "You have uninitialized all contexts while an associated device is still active."); ma_spinlock_unlock(&g_DeviceTrackingInitLock_CoreAudio); return MA_INVALID_OPERATION; } @@ -20067,17 +20532,15 @@ static ma_result ma_device__track__coreaudio(ma_device* pDevice) { /* Allocate memory if required. */ if (g_TrackedDeviceCap_CoreAudio <= g_TrackedDeviceCount_CoreAudio) { - ma_uint32 oldCap; ma_uint32 newCap; ma_device** ppNewDevices; - oldCap = g_TrackedDeviceCap_CoreAudio; newCap = g_TrackedDeviceCap_CoreAudio * 2; if (newCap == 0) { newCap = 1; } - ppNewDevices = (ma_device**)ma__realloc_from_callbacks(g_ppTrackedDevices_CoreAudio, sizeof(*g_ppTrackedDevices_CoreAudio)*newCap, sizeof(*g_ppTrackedDevices_CoreAudio)*oldCap, &pDevice->pContext->allocationCallbacks); + ppNewDevices = (ma_device**)ma_realloc(g_ppTrackedDevices_CoreAudio, sizeof(*g_ppTrackedDevices_CoreAudio)*newCap, &pDevice->pContext->allocationCallbacks); if (ppNewDevices == NULL) { ma_mutex_unlock(&g_DeviceTrackingMutex_CoreAudio); return MA_OUT_OF_MEMORY; @@ -20114,7 +20577,7 @@ static ma_result ma_device__untrack__coreaudio(ma_device* pDevice) /* If there's nothing else in the list we need to free memory. */ if (g_TrackedDeviceCount_CoreAudio == 0) { - ma__free_from_callbacks(g_ppTrackedDevices_CoreAudio, &pDevice->pContext->allocationCallbacks); + ma_free(g_ppTrackedDevices_CoreAudio, &pDevice->pContext->allocationCallbacks); g_ppTrackedDevices_CoreAudio = NULL; g_TrackedDeviceCap_CoreAudio = 0; } @@ -20149,6 +20612,7 @@ static ma_result ma_device__untrack__coreaudio(ma_device* pDevice) -(void)dealloc { [self remove_handler]; + [super dealloc]; } -(void)remove_handler @@ -20206,7 +20670,7 @@ static ma_result ma_device__untrack__coreaudio(ma_device* pDevice) #if 0 ma_uint32 previousState = ma_device_get_state(m_pDevice); - if (previousState == MA_STATE_STARTED) { + if (previousState == ma_device_state_started) { ma_device_stop(m_pDevice); } @@ -20221,7 +20685,7 @@ static ma_result ma_device__untrack__coreaudio(ma_device* pDevice) ma_device__post_init_setup(m_pDevice, ma_device_type_playback); } - if (previousState == MA_STATE_STARTED) { + if (previousState == ma_device_state_started) { ma_device_start(m_pDevice); } #endif @@ -20232,7 +20696,7 @@ static ma_result ma_device__untrack__coreaudio(ma_device* pDevice) static ma_result ma_device_uninit__coreaudio(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); - MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_UNINITIALIZED); + MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_uninitialized); #if defined(MA_APPLE_DESKTOP) /* @@ -20243,7 +20707,7 @@ static ma_result ma_device_uninit__coreaudio(ma_device* pDevice) #endif #if defined(MA_APPLE_MOBILE) if (pDevice->coreaudio.pRouteChangeHandler != NULL) { - ma_router_change_handler* pRouteChangeHandler = (__bridge_transfer ma_router_change_handler*)pDevice->coreaudio.pRouteChangeHandler; + ma_router_change_handler* pRouteChangeHandler = (MA_BRIDGE_TRANSFER ma_router_change_handler*)pDevice->coreaudio.pRouteChangeHandler; [pRouteChangeHandler remove_handler]; } #endif @@ -20256,7 +20720,7 @@ static ma_result ma_device_uninit__coreaudio(ma_device* pDevice) } if (pDevice->coreaudio.pAudioBufferList) { - ma__free_from_callbacks(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); } return MA_SUCCESS; @@ -20559,12 +21023,12 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev } #else /* Fall back to default assumptions. */ - ma_get_standard_channel_map(ma_standard_channel_map_default, pData->channelsOut, pData->channelMapOut); + ma_channel_map_init_standard(ma_standard_channel_map_default, pData->channelMapOut, ma_countof(pData->channelMapOut), pData->channelsOut); #endif } #else /* TODO: Figure out how to get the channel map using AVAudioSession. */ - ma_get_standard_channel_map(ma_standard_channel_map_default, pData->channelsOut, pData->channelMapOut); + ma_channel_map_init_standard(ma_standard_channel_map_default, pData->channelMapOut, ma_countof(pData->channelMapOut), pData->channelsOut); #endif @@ -20661,7 +21125,7 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev /* Initialize the audio unit. */ status = ((ma_AudioUnitInitialize_proc)pContext->coreaudio.AudioUnitInitialize)(pData->audioUnit); if (status != noErr) { - ma__free_from_callbacks(pData->pAudioBufferList, &pContext->allocationCallbacks); + ma_free(pData->pAudioBufferList, &pContext->allocationCallbacks); pData->pAudioBufferList = NULL; ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); return ma_result_from_OSStatus(status); @@ -20708,7 +21172,7 @@ static ma_result ma_device_reinit_internal__coreaudio(ma_device* pDevice, ma_dev ((ma_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnitCapture); } if (pDevice->coreaudio.pAudioBufferList) { - ma__free_from_callbacks(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); } } else if (deviceType == ma_device_type_playback) { data.formatIn = pDevice->playback.format; @@ -20870,7 +21334,7 @@ static ma_result ma_device_init__coreaudio(ma_device* pDevice, const ma_device_c if (pConfig->deviceType == ma_device_type_duplex) { ((ma_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnitCapture); if (pDevice->coreaudio.pAudioBufferList) { - ma__free_from_callbacks(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); } } return result; @@ -20917,7 +21381,7 @@ static ma_result ma_device_init__coreaudio(ma_device* pDevice, const ma_device_c differently on non-Desktop Apple platforms. */ #if defined(MA_APPLE_MOBILE) - pDevice->coreaudio.pRouteChangeHandler = (__bridge_retained void*)[[ma_router_change_handler alloc] init:pDevice]; + pDevice->coreaudio.pRouteChangeHandler = (MA_BRIDGE_RETAINED void*)[[ma_router_change_handler alloc] init:pDevice]; #endif return MA_SUCCESS; @@ -20982,7 +21446,8 @@ static ma_result ma_context_uninit__coreaudio(ma_context* pContext) #if defined(MA_APPLE_MOBILE) if (!pContext->coreaudio.noAudioSessionDeactivate) { if (![[AVAudioSession sharedInstance] setActive:false error:nil]) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to deactivate audio session.", MA_FAILED_TO_INIT_BACKEND); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "Failed to deactivate audio session."); + return MA_FAILED_TO_INIT_BACKEND; } } #endif @@ -21001,7 +21466,7 @@ static ma_result ma_context_uninit__coreaudio(ma_context* pContext) return MA_SUCCESS; } -#if defined(MA_APPLE_MOBILE) +#if defined(MA_APPLE_MOBILE) && defined(__IPHONE_12_0) static AVAudioSessionCategory ma_to_AVAudioSessionCategory(ma_ios_session_category category) { /* The "default" and "none" categories are treated different and should not be used as an input into this function. */ @@ -21058,15 +21523,21 @@ static ma_result ma_context_init__coreaudio(ma_context* pContext, const ma_conte } } else { if (pConfig->coreaudio.sessionCategory != ma_ios_session_category_none) { + #if defined(__IPHONE_12_0) if (![pAudioSession setCategory: ma_to_AVAudioSessionCategory(pConfig->coreaudio.sessionCategory) withOptions:options error:nil]) { return MA_INVALID_OPERATION; /* Failed to set session category. */ } + #else + /* Ignore the session category on version 11 and older, but post a warning. */ + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "Session category only supported in iOS 12 and newer."); + #endif } } if (!pConfig->coreaudio.noAudioSessionActivate) { if (![pAudioSession setActive:true error:nil]) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to activate audio session.", MA_FAILED_TO_INIT_BACKEND); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "Failed to activate audio session."); + return MA_FAILED_TO_INIT_BACKEND; } } } @@ -21727,7 +22198,7 @@ static ma_result ma_device_init_handle__sndio(ma_device* pDevice, const ma_devic MA_ASSERT(pDevice != NULL); if (deviceType == ma_device_type_capture) { - openFlags = MA_SIO_REC; + openFlags = MA_SIO_REC; } else { openFlags = MA_SIO_PLAY; } @@ -21744,13 +22215,15 @@ static ma_result ma_device_init_handle__sndio(ma_device* pDevice, const ma_devic handle = (ma_ptr)((ma_sio_open_proc)pDevice->pContext->sndio.sio_open)(pDeviceName, openFlags, 0); if (handle == NULL) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to open device.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to open device."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } /* We need to retrieve the device caps to determine the most appropriate format to use. */ if (((ma_sio_getcap_proc)pDevice->pContext->sndio.sio_getcap)((struct ma_sio_hdl*)handle, &caps) == 0) { ((ma_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct ma_sio_hdl*)handle); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve device caps.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve device caps."); + return MA_ERROR; } /* @@ -21844,12 +22317,14 @@ static ma_result ma_device_init_handle__sndio(ma_device* pDevice, const ma_devic if (((ma_sio_setpar_proc)pDevice->pContext->sndio.sio_setpar)((struct ma_sio_hdl*)handle, &par) == 0) { ((ma_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct ma_sio_hdl*)handle); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to set buffer size.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to set buffer size."); + return MA_ERROR; } if (((ma_sio_getpar_proc)pDevice->pContext->sndio.sio_getpar)((struct ma_sio_hdl*)handle, &par) == 0) { ((ma_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct ma_sio_hdl*)handle); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve buffer size.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve buffer size."); + return MA_ERROR; } internalFormat = ma_format_from_sio_enc__sndio(par.bits, par.bps, par.sig, par.le, par.msb); @@ -21867,7 +22342,7 @@ static ma_result ma_device_init_handle__sndio(ma_device* pDevice, const ma_devic pDescriptor->format = internalFormat; pDescriptor->channels = internalChannels; pDescriptor->sampleRate = internalSampleRate; - ma_get_standard_channel_map(ma_standard_channel_map_sndio, pDevice->playback.internalChannels, pDevice->playback.internalChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_sndio, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), internalChannels); pDescriptor->periodSizeInFrames = internalPeriodSizeInFrames; pDescriptor->periodCount = internalPeriods; @@ -21964,7 +22439,8 @@ static ma_result ma_device_write__sndio(ma_device* pDevice, const void* pPCMFram result = ((ma_sio_write_proc)pDevice->pContext->sndio.sio_write)((struct ma_sio_hdl*)pDevice->sndio.handlePlayback, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); if (result == 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to send data from the client to the device.", MA_IO_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to send data from the client to the device."); + return MA_IO_ERROR; } if (pFramesWritten != NULL) { @@ -21984,7 +22460,8 @@ static ma_result ma_device_read__sndio(ma_device* pDevice, void* pPCMFrames, ma_ result = ((ma_sio_read_proc)pDevice->pContext->sndio.sio_read)((struct ma_sio_hdl*)pDevice->sndio.handleCapture, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); if (result == 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to read data from the device to be sent to the device.", MA_IO_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to read data from the device to be sent to the device."); + return MA_IO_ERROR; } if (pFramesRead != NULL) { @@ -22527,7 +23004,8 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c } if (fd == -1) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to open device.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to open device."); + return ma_result_from_errno(errno); } #if !defined(MA_AUDIO4_USE_NEW_API) /* Old API */ @@ -22579,12 +23057,14 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c if (ioctl(fd, AUDIO_SETINFO, &fdInfo) < 0) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to set device format. AUDIO_SETINFO failed.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to set device format. AUDIO_SETINFO failed."); + return ma_result_from_errno(errno); } if (ioctl(fd, AUDIO_GETINFO, &fdInfo) < 0) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] AUDIO_GETINFO failed.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] AUDIO_GETINFO failed."); + return ma_result_from_errno(errno); } if (deviceType == ma_device_type_capture) { @@ -22599,7 +23079,8 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c if (internalFormat == ma_format_unknown) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable."); + return MA_FORMAT_NOT_SUPPORTED; } /* Buffer. */ @@ -22625,7 +23106,8 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c fdInfo.blocksize = internalPeriodSizeInBytes; if (ioctl(fd, AUDIO_SETINFO, &fdInfo) < 0) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to set internal buffer size. AUDIO_SETINFO failed.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to set internal buffer size. AUDIO_SETINFO failed."); + return ma_result_from_errno(errno); } internalPeriods = fdInfo.hiwat; @@ -22639,7 +23121,8 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c /* We need to retrieve the format of the device so we can know the channel count and sample rate. Then we can calculate the buffer size. */ if (ioctl(fd, AUDIO_GETPAR, &fdPar) < 0) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to retrieve initial device parameters.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to retrieve initial device parameters."); + return ma_result_from_errno(errno); } internalFormat = ma_format_from_swpar__audio4(&fdPar); @@ -22648,7 +23131,8 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c if (internalFormat == ma_format_unknown) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable."); + return MA_FORMAT_NOT_SUPPORTED; } /* Buffer. */ @@ -22668,12 +23152,14 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c if (ioctl(fd, AUDIO_SETPAR, &fdPar) < 0) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to set device parameters.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to set device parameters."); + return ma_result_from_errno(errno); } if (ioctl(fd, AUDIO_GETPAR, &fdPar) < 0) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to retrieve actual device parameters.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to retrieve actual device parameters."); + return ma_result_from_errno(errno); } } @@ -22687,7 +23173,8 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c if (internalFormat == ma_format_unknown) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable."); + return MA_FORMAT_NOT_SUPPORTED; } if (deviceType == ma_device_type_capture) { @@ -22699,7 +23186,7 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c pDescriptor->format = internalFormat; pDescriptor->channels = internalChannels; pDescriptor->sampleRate = internalSampleRate; - ma_get_standard_channel_map(ma_standard_channel_map_sound4, internalChannels, pDescriptor->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_sound4, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), internalChannels); pDescriptor->periodSizeInFrames = internalPeriodSizeInFrames; pDescriptor->periodCount = internalPeriods; @@ -22781,11 +23268,13 @@ static ma_result ma_device_stop_fd__audio4(ma_device* pDevice, int fd) #if !defined(MA_AUDIO4_USE_NEW_API) if (ioctl(fd, AUDIO_FLUSH, 0) < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to stop device. AUDIO_FLUSH failed.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to stop device. AUDIO_FLUSH failed."); + return ma_result_from_errno(errno); } #else if (ioctl(fd, AUDIO_STOP, 0) < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to stop device. AUDIO_STOP failed.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to stop device. AUDIO_STOP failed."); + return ma_result_from_errno(errno); } #endif @@ -22833,7 +23322,8 @@ static ma_result ma_device_write__audio4(ma_device* pDevice, const void* pPCMFra result = write(pDevice->audio4.fdPlayback, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); if (result < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to write data to the device.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to write data to the device."); + return ma_result_from_errno(errno); } if (pFramesWritten != NULL) { @@ -22853,7 +23343,8 @@ static ma_result ma_device_read__audio4(ma_device* pDevice, void* pPCMFrames, ma result = read(pDevice->audio4.fdCapture, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); if (result < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to read data from the device.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to read data from the device."); + return ma_result_from_errno(errno); } if (pFramesRead != NULL) { @@ -22968,7 +23459,8 @@ static ma_result ma_context_enumerate_devices__oss(ma_context* pContext, ma_enum fd = ma_open_temp_device__oss(); if (fd == -1) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open a temporary device for retrieving system information used for device enumeration.", MA_NO_BACKEND); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open a temporary device for retrieving system information used for device enumeration."); + return MA_NO_BACKEND; } result = ioctl(fd, SNDCTL_SYSINFO, &si); @@ -23015,7 +23507,8 @@ static ma_result ma_context_enumerate_devices__oss(ma_context* pContext, ma_enum } } else { close(fd); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve system information for device enumeration.", MA_NO_BACKEND); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve system information for device enumeration."); + return MA_NO_BACKEND; } close(fd); @@ -23098,7 +23591,8 @@ static ma_result ma_context_get_device_info__oss(ma_context* pContext, ma_device fdTemp = ma_open_temp_device__oss(); if (fdTemp == -1) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open a temporary device for retrieving system information used for device enumeration.", MA_NO_BACKEND); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open a temporary device for retrieving system information used for device enumeration."); + return MA_NO_BACKEND; } result = ioctl(fdTemp, SNDCTL_SYSINFO, &si); @@ -23156,7 +23650,8 @@ static ma_result ma_context_get_device_info__oss(ma_context* pContext, ma_device } } else { close(fdTemp); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve system information for device enumeration.", MA_NO_BACKEND); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve system information for device enumeration."); + return MA_NO_BACKEND; } @@ -23246,7 +23741,8 @@ static ma_result ma_device_init_fd__oss(ma_device* pDevice, const ma_device_conf result = ma_context_open_device__oss(pDevice->pContext, deviceType, pDeviceID, shareMode, &fd); if (result != MA_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device."); + return result; } /* @@ -23260,21 +23756,24 @@ static ma_result ma_device_init_fd__oss(ma_device* pDevice, const ma_device_conf ossResult = ioctl(fd, SNDCTL_DSP_SETFMT, &ossFormat); if (ossResult == -1) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to set format.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to set format."); + return ma_result_from_errno(errno); } /* Channels. */ ossResult = ioctl(fd, SNDCTL_DSP_CHANNELS, &ossChannels); if (ossResult == -1) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to set channel count.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to set channel count."); + return ma_result_from_errno(errno); } /* Sample Rate. */ ossResult = ioctl(fd, SNDCTL_DSP_SPEED, &ossSampleRate); if (ossResult == -1) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to set sample rate.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to set sample rate."); + return ma_result_from_errno(errno); } /* @@ -23308,7 +23807,8 @@ static ma_result ma_device_init_fd__oss(ma_device* pDevice, const ma_device_conf ossResult = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &ossFragment); if (ossResult == -1) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to set fragment size and period count.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to set fragment size and period count."); + return ma_result_from_errno(errno); } } @@ -23322,12 +23822,13 @@ static ma_result ma_device_init_fd__oss(ma_device* pDevice, const ma_device_conf pDescriptor->format = ma_format_from_oss(ossFormat); pDescriptor->channels = ossChannels; pDescriptor->sampleRate = ossSampleRate; - ma_get_standard_channel_map(ma_standard_channel_map_sound4, pDescriptor->channels, pDescriptor->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_sound4, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), pDescriptor->channels); pDescriptor->periodCount = (ma_uint32)(ossFragment >> 16); pDescriptor->periodSizeInFrames = (ma_uint32)(1 << (ossFragment & 0xFFFF)) / ma_get_bytes_per_frame(pDescriptor->format, pDescriptor->channels); if (pDescriptor->format == ma_format_unknown) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] The device's internal format is not supported by miniaudio.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] The device's internal format is not supported by miniaudio."); + return MA_FORMAT_NOT_SUPPORTED; } return MA_SUCCESS; @@ -23347,14 +23848,16 @@ static ma_result ma_device_init__oss(ma_device* pDevice, const ma_device_config* if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { ma_result result = ma_device_init_fd__oss(pDevice, pConfig, pDescriptorCapture, ma_device_type_capture); if (result != MA_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device."); + return result; } } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { ma_result result = ma_device_init_fd__oss(pDevice, pConfig, pDescriptorPlayback, ma_device_type_playback); if (result != MA_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device."); + return result; } } @@ -23411,13 +23914,14 @@ static ma_result ma_device_write__oss(ma_device* pDevice, const void* pPCMFrames /* Don't do any processing if the device is stopped. */ deviceState = ma_device_get_state(pDevice); - if (deviceState != MA_STATE_STARTED && deviceState != MA_STATE_STARTING) { + if (deviceState != ma_device_state_started && deviceState != ma_device_state_starting) { return MA_SUCCESS; } resultOSS = write(pDevice->oss.fdPlayback, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); if (resultOSS < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to send data from the client to the device.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to send data from the client to the device."); + return ma_result_from_errno(errno); } if (pFramesWritten != NULL) { @@ -23438,13 +23942,14 @@ static ma_result ma_device_read__oss(ma_device* pDevice, void* pPCMFrames, ma_ui /* Don't do any processing if the device is stopped. */ deviceState = ma_device_get_state(pDevice); - if (deviceState != MA_STATE_STARTED && deviceState != MA_STATE_STARTING) { + if (deviceState != ma_device_state_started && deviceState != ma_device_state_starting) { return MA_SUCCESS; } resultOSS = read(pDevice->oss.fdCapture, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); if (resultOSS < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to read data from the device to be sent to the client.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to read data from the device to be sent to the client."); + return ma_result_from_errno(errno); } if (pFramesRead != NULL) { @@ -23476,7 +23981,8 @@ static ma_result ma_context_init__oss(ma_context* pContext, const ma_context_con /* Try opening a temporary device first so we can get version information. This is closed at the end. */ fd = ma_open_temp_device__oss(); if (fd == -1) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open temporary device for retrieving system properties.", MA_NO_BACKEND); /* Looks liks OSS isn't installed, or there are no available devices. */ + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open temporary device for retrieving system properties."); /* Looks liks OSS isn't installed, or there are no available devices. */ + return MA_NO_BACKEND; } /* Grab the OSS version. */ @@ -23484,7 +23990,8 @@ static ma_result ma_context_init__oss(ma_context* pContext, const ma_context_con result = ioctl(fd, OSS_GETVERSION, &ossVersion); if (result == -1) { close(fd); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve OSS version.", MA_NO_BACKEND); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve OSS version."); + return MA_NO_BACKEND; } /* The file handle to temp device is no longer needed. Close ASAP. */ @@ -24048,9 +24555,9 @@ static ma_result ma_device_init_by_type__aaudio(ma_device* pDevice, const ma_dev /* For the channel map we need to be sure we don't overflow any buffers. */ if (pDescriptor->channels <= MA_MAX_CHANNELS) { - ma_get_standard_channel_map(ma_standard_channel_map_default, pDescriptor->channels, pDescriptor->channelMap); /* <-- Cannot find info on channel order, so assuming a default. */ + ma_channel_map_init_standard(ma_standard_channel_map_default, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), pDescriptor->channels); /* <-- Cannot find info on channel order, so assuming a default. */ } else { - ma_channel_map_init_blank(MA_MAX_CHANNELS, pDescriptor->channelMap); /* Too many channels. Use a blank channel map. */ + ma_channel_map_init_blank(pDescriptor->channelMap, MA_MAX_CHANNELS); /* Too many channels. Use a blank channel map. */ } bufferCapacityInFrames = ((MA_PFN_AAudioStream_getBufferCapacityInFrames)pDevice->pContext->aaudio.AAudioStream_getBufferCapacityInFrames)(pStream); @@ -24781,7 +25288,7 @@ static void ma_buffer_queue_callback_capture__opensl_android(SLAndroidSimpleBuff */ /* Don't do anything if the device is not started. */ - if (ma_device_get_state(pDevice) != MA_STATE_STARTED) { + if (ma_device_get_state(pDevice) != ma_device_state_started) { return; } @@ -24815,7 +25322,7 @@ static void ma_buffer_queue_callback_playback__opensl_android(SLAndroidSimpleBuf (void)pBufferQueue; /* Don't do anything if the device is not started. */ - if (ma_device_get_state(pDevice) != MA_STATE_STARTED) { + if (ma_device_get_state(pDevice) != ma_device_state_started) { return; } @@ -24852,7 +25359,7 @@ static ma_result ma_device_uninit__opensl(ma_device* pDevice) MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->Destroy((SLObjectItf)pDevice->opensl.pAudioRecorderObj); } - ma__free_from_callbacks(pDevice->opensl.pBufferCapture, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->opensl.pBufferCapture, &pDevice->pContext->allocationCallbacks); } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { @@ -24863,7 +25370,7 @@ static ma_result ma_device_uninit__opensl(ma_device* pDevice) MA_OPENSL_OBJ(pDevice->opensl.pOutputMixObj)->Destroy((SLObjectItf)pDevice->opensl.pOutputMixObj); } - ma__free_from_callbacks(pDevice->opensl.pBufferPlayback, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->opensl.pBufferPlayback, &pDevice->pContext->allocationCallbacks); } return MA_SUCCESS; @@ -25053,7 +25560,8 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create audio recorder.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create audio recorder."); + return ma_result_from_OpenSL(resultSL); } @@ -25072,25 +25580,29 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->Realize((SLObjectItf)pDevice->opensl.pAudioRecorderObj, SL_BOOLEAN_FALSE); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize audio recorder.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize audio recorder."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioRecorderObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_RECORD, &pDevice->opensl.pAudioRecorder); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_RECORD interface.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_RECORD interface."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioRecorderObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &pDevice->opensl.pBufferQueueCapture); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueueCapture)->RegisterCallback((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueueCapture, ma_buffer_queue_callback_capture__opensl_android, pDevice); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to register buffer queue callback.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to register buffer queue callback."); + return ma_result_from_OpenSL(resultSL); } /* The internal format is determined by the "pcm" object. */ @@ -25101,10 +25613,11 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf pDevice->opensl.currentBufferIndexCapture = 0; bufferSizeInBytes = pDescriptorCapture->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels) * pDescriptorCapture->periodCount; - pDevice->opensl.pBufferCapture = (ma_uint8*)ma__calloc_from_callbacks(bufferSizeInBytes, &pDevice->pContext->allocationCallbacks); + pDevice->opensl.pBufferCapture = (ma_uint8*)ma_calloc(bufferSizeInBytes, &pDevice->pContext->allocationCallbacks); if (pDevice->opensl.pBufferCapture == NULL) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to allocate memory for data buffer.", MA_OUT_OF_MEMORY); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to allocate memory for data buffer."); + return MA_OUT_OF_MEMORY; } MA_ZERO_MEMORY(pDevice->opensl.pBufferCapture, bufferSizeInBytes); } @@ -25121,19 +25634,22 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf resultSL = (*g_maEngineSL)->CreateOutputMix(g_maEngineSL, (SLObjectItf*)&pDevice->opensl.pOutputMixObj, 0, NULL, NULL); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create output mix.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create output mix."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_OBJ(pDevice->opensl.pOutputMixObj)->Realize((SLObjectItf)pDevice->opensl.pOutputMixObj, SL_BOOLEAN_FALSE); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize output mix object.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize output mix object."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_OBJ(pDevice->opensl.pOutputMixObj)->GetInterface((SLObjectItf)pDevice->opensl.pOutputMixObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_OUTPUTMIX, &pDevice->opensl.pOutputMix); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_OUTPUTMIX interface.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_OUTPUTMIX interface."); + return ma_result_from_OpenSL(resultSL); } /* Set the output device. */ @@ -25167,7 +25683,8 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create audio player.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create audio player."); + return ma_result_from_OpenSL(resultSL); } @@ -25186,25 +25703,29 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->Realize((SLObjectItf)pDevice->opensl.pAudioPlayerObj, SL_BOOLEAN_FALSE); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize audio player.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize audio player."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioPlayerObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_PLAY, &pDevice->opensl.pAudioPlayer); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_PLAY interface.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_PLAY interface."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioPlayerObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &pDevice->opensl.pBufferQueuePlayback); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueuePlayback)->RegisterCallback((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueuePlayback, ma_buffer_queue_callback_playback__opensl_android, pDevice); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to register buffer queue callback.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to register buffer queue callback."); + return ma_result_from_OpenSL(resultSL); } /* The internal format is determined by the "pcm" object. */ @@ -25215,10 +25736,11 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf pDevice->opensl.currentBufferIndexPlayback = 0; bufferSizeInBytes = pDescriptorPlayback->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels) * pDescriptorPlayback->periodCount; - pDevice->opensl.pBufferPlayback = (ma_uint8*)ma__calloc_from_callbacks(bufferSizeInBytes, &pDevice->pContext->allocationCallbacks); + pDevice->opensl.pBufferPlayback = (ma_uint8*)ma_calloc(bufferSizeInBytes, &pDevice->pContext->allocationCallbacks); if (pDevice->opensl.pBufferPlayback == NULL) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to allocate memory for data buffer.", MA_OUT_OF_MEMORY); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to allocate memory for data buffer."); + return MA_OUT_OF_MEMORY; } MA_ZERO_MEMORY(pDevice->opensl.pBufferPlayback, bufferSizeInBytes); } @@ -25245,7 +25767,8 @@ static ma_result ma_device_start__opensl(ma_device* pDevice) if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { resultSL = MA_OPENSL_RECORD(pDevice->opensl.pAudioRecorder)->SetRecordState((SLRecordItf)pDevice->opensl.pAudioRecorder, SL_RECORDSTATE_RECORDING); if (resultSL != SL_RESULT_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to start internal capture device.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to start internal capture device."); + return ma_result_from_OpenSL(resultSL); } periodSizeInBytes = pDevice->capture.internalPeriodSizeInFrames * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); @@ -25253,7 +25776,8 @@ static ma_result ma_device_start__opensl(ma_device* pDevice) resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueueCapture)->Enqueue((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueueCapture, pDevice->opensl.pBufferCapture + (periodSizeInBytes * iPeriod), periodSizeInBytes); if (resultSL != SL_RESULT_SUCCESS) { MA_OPENSL_RECORD(pDevice->opensl.pAudioRecorder)->SetRecordState((SLRecordItf)pDevice->opensl.pAudioRecorder, SL_RECORDSTATE_STOPPED); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to enqueue buffer for capture device.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to enqueue buffer for capture device."); + return ma_result_from_OpenSL(resultSL); } } } @@ -25261,7 +25785,8 @@ static ma_result ma_device_start__opensl(ma_device* pDevice) if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { resultSL = MA_OPENSL_PLAY(pDevice->opensl.pAudioPlayer)->SetPlayState((SLPlayItf)pDevice->opensl.pAudioPlayer, SL_PLAYSTATE_PLAYING); if (resultSL != SL_RESULT_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to start internal playback device.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to start internal playback device."); + return ma_result_from_OpenSL(resultSL); } /* In playback mode (no duplex) we need to load some initial buffers. In duplex mode we need to enqueu silent buffers. */ @@ -25276,7 +25801,8 @@ static ma_result ma_device_start__opensl(ma_device* pDevice) resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueuePlayback)->Enqueue((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueuePlayback, pDevice->opensl.pBufferPlayback + (periodSizeInBytes * iPeriod), periodSizeInBytes); if (resultSL != SL_RESULT_SUCCESS) { MA_OPENSL_PLAY(pDevice->opensl.pAudioPlayer)->SetPlayState((SLPlayItf)pDevice->opensl.pAudioPlayer, SL_PLAYSTATE_STOPPED); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to enqueue buffer for playback device.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to enqueue buffer for playback device."); + return ma_result_from_OpenSL(resultSL); } } } @@ -25335,7 +25861,8 @@ static ma_result ma_device_stop__opensl(ma_device* pDevice) resultSL = MA_OPENSL_RECORD(pDevice->opensl.pAudioRecorder)->SetRecordState((SLRecordItf)pDevice->opensl.pAudioRecorder, SL_RECORDSTATE_STOPPED); if (resultSL != SL_RESULT_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to stop internal capture device.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to stop internal capture device."); + return ma_result_from_OpenSL(resultSL); } MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueueCapture)->Clear((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueueCapture); @@ -25346,7 +25873,8 @@ static ma_result ma_device_stop__opensl(ma_device* pDevice) resultSL = MA_OPENSL_PLAY(pDevice->opensl.pAudioPlayer)->SetPlayState((SLPlayItf)pDevice->opensl.pAudioPlayer, SL_PLAYSTATE_STOPPED); if (resultSL != SL_RESULT_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to stop internal playback device.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to stop internal playback device."); + return ma_result_from_OpenSL(resultSL); } MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueuePlayback)->Clear((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueuePlayback); @@ -25388,7 +25916,7 @@ static ma_result ma_dlsym_SLInterfaceID__opensl(ma_context* pContext, const char /* We need to return an error if the symbol cannot be found. This is important because there have been reports that some symbols do not exist. */ ma_handle* p = (ma_handle*)ma_dlsym(pContext, pContext->opensl.libOpenSLES, pName); if (p == NULL) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_INFO, "[OpenSL|ES] Cannot find symbol %s", pName); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_INFO, "[OpenSL] Cannot find symbol %s", pName); return MA_NO_BACKEND; } @@ -25451,7 +25979,7 @@ static ma_result ma_context_init__opensl(ma_context* pContext, const ma_context_ } if (pContext->opensl.libOpenSLES == NULL) { - ma_post_log_message(pContext, NULL, MA_LOG_LEVEL_INFO, "[OpenSL|ES] Could not find libOpenSLES.so"); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_INFO, "[OpenSL] Could not find libOpenSLES.so"); return MA_NO_BACKEND; } @@ -25500,7 +26028,7 @@ static ma_result ma_context_init__opensl(ma_context* pContext, const ma_context_ pContext->opensl.slCreateEngine = (ma_proc)ma_dlsym(pContext, pContext->opensl.libOpenSLES, "slCreateEngine"); if (pContext->opensl.slCreateEngine == NULL) { ma_dlclose(pContext, pContext->opensl.libOpenSLES); - ma_post_log_message(pContext, NULL, MA_LOG_LEVEL_INFO, "[OpenSL|ES] Cannot find symbol slCreateEngine."); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_INFO, "[OpenSL] Cannot find symbol slCreateEngine."); return MA_NO_BACKEND; } #else @@ -25524,7 +26052,7 @@ static ma_result ma_context_init__opensl(ma_context* pContext, const ma_context_ if (result != MA_SUCCESS) { ma_dlclose(pContext, pContext->opensl.libOpenSLES); - ma_post_log_message(pContext, NULL, MA_LOG_LEVEL_INFO, "[OpenSL|ES] Failed to initialize OpenSL engine."); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_INFO, "[OpenSL] Failed to initialize OpenSL engine."); return result; } @@ -25780,7 +26308,7 @@ static ma_result ma_device_init_by_type__webaudio(ma_device* pDevice, const ma_d /* The AudioContext must be created in a suspended state. */ device.webaudio = new (window.AudioContext || window.webkitAudioContext)({sampleRate:sampleRate}); device.webaudio.suspend(); - device.state = 1; /* MA_STATE_STOPPED */ + device.state = 1; /* ma_device_state_stopped */ /* We need an intermediary buffer which we use for JavaScript and C interop. This buffer stores interleaved f32 PCM data. Because it's passed between @@ -25938,12 +26466,12 @@ static ma_result ma_device_init_by_type__webaudio(ma_device* pDevice, const ma_d pDevice->webaudio.indexPlayback = deviceIndex; } - pDescriptor->format = ma_format_f32; - pDescriptor->channels = channels; - ma_get_standard_channel_map(ma_standard_channel_map_webaudio, pDescriptor->channels, pDescriptor->channelMap); - pDescriptor->sampleRate = EM_ASM_INT({ return miniaudio.get_device_by_index($0).webaudio.sampleRate; }, deviceIndex); - pDescriptor->periodSizeInFrames = periodSizeInFrames; - pDescriptor->periodCount = 1; + pDescriptor->format = ma_format_f32; + pDescriptor->channels = channels; + ma_channel_map_init_standard(ma_standard_channel_map_webaudio, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), pDescriptor->channels); + pDescriptor->sampleRate = EM_ASM_INT({ return miniaudio.get_device_by_index($0).webaudio.sampleRate; }, deviceIndex); + pDescriptor->periodSizeInFrames = periodSizeInFrames; + pDescriptor->periodCount = 1; return MA_SUCCESS; } @@ -25990,7 +26518,7 @@ static ma_result ma_device_start__webaudio(ma_device* pDevice) EM_ASM({ var device = miniaudio.get_device_by_index($0); device.webaudio.resume(); - device.state = 2; /* MA_STATE_STARTED */ + device.state = 2; /* ma_device_state_started */ }, pDevice->webaudio.indexCapture); } @@ -25998,7 +26526,7 @@ static ma_result ma_device_start__webaudio(ma_device* pDevice) EM_ASM({ var device = miniaudio.get_device_by_index($0); device.webaudio.resume(); - device.state = 2; /* MA_STATE_STARTED */ + device.state = 2; /* ma_device_state_started */ }, pDevice->webaudio.indexPlayback); } @@ -26023,7 +26551,7 @@ static ma_result ma_device_stop__webaudio(ma_device* pDevice) EM_ASM({ var device = miniaudio.get_device_by_index($0); device.webaudio.suspend(); - device.state = 1; /* MA_STATE_STOPPED */ + device.state = 1; /* ma_device_state_stopped */ }, pDevice->webaudio.indexCapture); } @@ -26031,7 +26559,7 @@ static ma_result ma_device_stop__webaudio(ma_device* pDevice) EM_ASM({ var device = miniaudio.get_device_by_index($0); device.webaudio.suspend(); - device.state = 1; /* MA_STATE_STOPPED */ + device.state = 1; /* ma_device_state_stopped */ }, pDevice->webaudio.indexPlayback); } @@ -26119,7 +26647,7 @@ static ma_result ma_context_init__webaudio(ma_context* pContext, const ma_contex miniaudio.unlock = function() { for(var i = 0; i < miniaudio.devices.length; ++i) { var device = miniaudio.devices[i]; - if (device != null && device.webaudio != null && device.state === 2 /* MA_STATE_STARTED */) { + if (device != null && device.webaudio != null && device.state === 2 /* ma_device_state_started */) { device.webaudio.resume(); } } @@ -26158,10 +26686,10 @@ static ma_result ma_context_init__webaudio(ma_context* pContext, const ma_contex -static ma_bool32 ma__is_channel_map_valid(const ma_channel* channelMap, ma_uint32 channels) +static ma_bool32 ma__is_channel_map_valid(const ma_channel* pChannelMap, ma_uint32 channels) { /* A blank channel map should be allowed, in which case it should use an appropriate default which will depend on context. */ - if (channelMap[0] != MA_CHANNEL_NONE) { + if (pChannelMap != NULL && pChannelMap[0] != MA_CHANNEL_NONE) { ma_uint32 iChannel; if (channels == 0 || channels > MA_MAX_CHANNELS) { @@ -26172,7 +26700,7 @@ static ma_bool32 ma__is_channel_map_valid(const ma_channel* channelMap, ma_uint3 for (iChannel = 0; iChannel < channels; ++iChannel) { ma_uint32 jChannel; for (jChannel = iChannel + 1; jChannel < channels; ++jChannel) { - if (channelMap[iChannel] == channelMap[jChannel]) { + if (pChannelMap[iChannel] == pChannelMap[jChannel]) { return MA_FALSE; } } @@ -26202,9 +26730,9 @@ static ma_result ma_device__post_init_setup(ma_device* pDevice, ma_device_type d ma_channel_map_copy(pDevice->capture.channelMap, pDevice->capture.internalChannelMap, pDevice->capture.channels); } else { if (pDevice->capture.channelMixMode == ma_channel_mix_mode_simple) { - ma_channel_map_init_blank(pDevice->capture.channels, pDevice->capture.channelMap); + ma_channel_map_init_blank(pDevice->capture.channelMap, pDevice->capture.channels); } else { - ma_get_standard_channel_map(ma_standard_channel_map_default, pDevice->capture.channels, pDevice->capture.channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_default, pDevice->capture.channelMap, ma_countof(pDevice->capture.channelMap), pDevice->capture.channels); } } } @@ -26223,9 +26751,9 @@ static ma_result ma_device__post_init_setup(ma_device* pDevice, ma_device_type d ma_channel_map_copy(pDevice->playback.channelMap, pDevice->playback.internalChannelMap, pDevice->playback.channels); } else { if (pDevice->playback.channelMixMode == ma_channel_mix_mode_simple) { - ma_channel_map_init_blank(pDevice->playback.channels, pDevice->playback.channelMap); + ma_channel_map_init_blank(pDevice->playback.channelMap, pDevice->playback.channels); } else { - ma_get_standard_channel_map(ma_standard_channel_map_default, pDevice->playback.channels, pDevice->playback.channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_default, pDevice->playback.channelMap, ma_countof(pDevice->playback.channelMap), pDevice->playback.channels); } } } @@ -26243,21 +26771,27 @@ static ma_result ma_device__post_init_setup(ma_device* pDevice, ma_device_type d if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { /* Converting from internal device format to client format. */ ma_data_converter_config converterConfig = ma_data_converter_config_init_default(); - converterConfig.formatIn = pDevice->capture.internalFormat; - converterConfig.channelsIn = pDevice->capture.internalChannels; - converterConfig.sampleRateIn = pDevice->capture.internalSampleRate; - ma_channel_map_copy(converterConfig.channelMapIn, pDevice->capture.internalChannelMap, ma_min(pDevice->capture.internalChannels, MA_MAX_CHANNELS)); - converterConfig.formatOut = pDevice->capture.format; - converterConfig.channelsOut = pDevice->capture.channels; - converterConfig.sampleRateOut = pDevice->sampleRate; - ma_channel_map_copy(converterConfig.channelMapOut, pDevice->capture.channelMap, ma_min(pDevice->capture.channels, MA_MAX_CHANNELS)); - converterConfig.channelMixMode = pDevice->capture.channelMixMode; - converterConfig.resampling.allowDynamicSampleRate = MA_FALSE; - converterConfig.resampling.algorithm = pDevice->resampling.algorithm; - converterConfig.resampling.linear.lpfOrder = pDevice->resampling.linear.lpfOrder; - converterConfig.resampling.speex.quality = pDevice->resampling.speex.quality; + converterConfig.formatIn = pDevice->capture.internalFormat; + converterConfig.channelsIn = pDevice->capture.internalChannels; + converterConfig.sampleRateIn = pDevice->capture.internalSampleRate; + converterConfig.pChannelMapIn = pDevice->capture.internalChannelMap; + converterConfig.formatOut = pDevice->capture.format; + converterConfig.channelsOut = pDevice->capture.channels; + converterConfig.sampleRateOut = pDevice->sampleRate; + converterConfig.pChannelMapOut = pDevice->capture.channelMap; + converterConfig.channelMixMode = pDevice->capture.channelMixMode; + converterConfig.allowDynamicSampleRate = MA_FALSE; + converterConfig.resampling.algorithm = pDevice->resampling.algorithm; + converterConfig.resampling.linear.lpfOrder = pDevice->resampling.linear.lpfOrder; + converterConfig.resampling.pBackendVTable = pDevice->resampling.pBackendVTable; + converterConfig.resampling.pBackendUserData = pDevice->resampling.pBackendUserData; - result = ma_data_converter_init(&converterConfig, &pDevice->capture.converter); + /* Make sure the old converter is uninitialized first. */ + if (ma_device_get_state(pDevice) != ma_device_state_uninitialized) { + ma_data_converter_uninit(&pDevice->capture.converter, &pDevice->pContext->allocationCallbacks); + } + + result = ma_data_converter_init(&converterConfig, &pDevice->pContext->allocationCallbacks, &pDevice->capture.converter); if (result != MA_SUCCESS) { return result; } @@ -26266,26 +26800,77 @@ static ma_result ma_device__post_init_setup(ma_device* pDevice, ma_device_type d if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { /* Converting from client format to device format. */ ma_data_converter_config converterConfig = ma_data_converter_config_init_default(); - converterConfig.formatIn = pDevice->playback.format; - converterConfig.channelsIn = pDevice->playback.channels; - converterConfig.sampleRateIn = pDevice->sampleRate; - ma_channel_map_copy(converterConfig.channelMapIn, pDevice->playback.channelMap, ma_min(pDevice->playback.channels, MA_MAX_CHANNELS)); - converterConfig.formatOut = pDevice->playback.internalFormat; - converterConfig.channelsOut = pDevice->playback.internalChannels; - converterConfig.sampleRateOut = pDevice->playback.internalSampleRate; - ma_channel_map_copy(converterConfig.channelMapOut, pDevice->playback.internalChannelMap, ma_min(pDevice->playback.internalChannels, MA_MAX_CHANNELS)); - converterConfig.channelMixMode = pDevice->playback.channelMixMode; - converterConfig.resampling.allowDynamicSampleRate = MA_FALSE; - converterConfig.resampling.algorithm = pDevice->resampling.algorithm; - converterConfig.resampling.linear.lpfOrder = pDevice->resampling.linear.lpfOrder; - converterConfig.resampling.speex.quality = pDevice->resampling.speex.quality; + converterConfig.formatIn = pDevice->playback.format; + converterConfig.channelsIn = pDevice->playback.channels; + converterConfig.sampleRateIn = pDevice->sampleRate; + converterConfig.pChannelMapIn = pDevice->playback.channelMap; + converterConfig.formatOut = pDevice->playback.internalFormat; + converterConfig.channelsOut = pDevice->playback.internalChannels; + converterConfig.sampleRateOut = pDevice->playback.internalSampleRate; + converterConfig.pChannelMapOut = pDevice->playback.internalChannelMap; + converterConfig.channelMixMode = pDevice->playback.channelMixMode; + converterConfig.allowDynamicSampleRate = MA_FALSE; + converterConfig.resampling.algorithm = pDevice->resampling.algorithm; + converterConfig.resampling.linear.lpfOrder = pDevice->resampling.linear.lpfOrder; + converterConfig.resampling.pBackendVTable = pDevice->resampling.pBackendVTable; + converterConfig.resampling.pBackendUserData = pDevice->resampling.pBackendUserData; - result = ma_data_converter_init(&converterConfig, &pDevice->playback.converter); + /* Make sure the old converter is uninitialized first. */ + if (ma_device_get_state(pDevice) != ma_device_state_uninitialized) { + ma_data_converter_uninit(&pDevice->playback.converter, &pDevice->pContext->allocationCallbacks); + } + + result = ma_data_converter_init(&converterConfig, &pDevice->pContext->allocationCallbacks, &pDevice->playback.converter); if (result != MA_SUCCESS) { return result; } } + + /* + In playback mode, if the data converter does not support retrieval of the required number of + input frames given a number of output frames, we need to fall back to a heap-allocated cache. + */ + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + ma_uint64 unused; + + pDevice->playback.inputCacheConsumed = 0; + pDevice->playback.inputCacheRemaining = 0; + + if (deviceType == ma_device_type_duplex || ma_data_converter_get_required_input_frame_count(&pDevice->playback.converter, 1, &unused) != MA_SUCCESS) { + /* We need a heap allocated cache. We want to size this based on the period size. */ + void* pNewInputCache; + ma_uint64 newInputCacheCap; + ma_uint64 newInputCacheSizeInBytes; + + newInputCacheCap = ma_calculate_frame_count_after_resampling(pDevice->playback.internalSampleRate, pDevice->sampleRate, pDevice->playback.internalPeriodSizeInFrames); + + newInputCacheSizeInBytes = newInputCacheCap * ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); + if (newInputCacheSizeInBytes > MA_SIZE_MAX) { + ma_free(pDevice->playback.pInputCache, &pDevice->pContext->allocationCallbacks); + pDevice->playback.pInputCache = NULL; + pDevice->playback.inputCacheCap = 0; + return MA_OUT_OF_MEMORY; /* Allocation too big. Should never hit this, but makes the cast below safer for 32-bit builds. */ + } + + pNewInputCache = ma_realloc(pDevice->playback.pInputCache, (size_t)newInputCacheSizeInBytes, &pDevice->pContext->allocationCallbacks); + if (pNewInputCache == NULL) { + ma_free(pDevice->playback.pInputCache, &pDevice->pContext->allocationCallbacks); + pDevice->playback.pInputCache = NULL; + pDevice->playback.inputCacheCap = 0; + return MA_OUT_OF_MEMORY; + } + + pDevice->playback.pInputCache = pNewInputCache; + pDevice->playback.inputCacheCap = newInputCacheCap; + } else { + /* Heap allocation not required. Make sure we clear out the old cache just in case this function was called in response to a route change. */ + ma_free(pDevice->playback.pInputCache, &pDevice->pContext->allocationCallbacks); + pDevice->playback.pInputCache = NULL; + pDevice->playback.inputCacheCap = 0; + } + } + return MA_SUCCESS; } @@ -26300,12 +26885,12 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) #endif /* - When the device is being initialized it's initial state is set to MA_STATE_UNINITIALIZED. Before returning from + When the device is being initialized it's initial state is set to ma_device_state_uninitialized. Before returning from ma_device_init(), the state needs to be set to something valid. In miniaudio the device's default state immediately after initialization is stopped, so therefore we need to mark the device as such. miniaudio will wait on the worker thread to signal an event to know when the worker thread is ready for action. */ - ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_device__set_state(pDevice, ma_device_state_stopped); ma_event_signal(&pDevice->stopEvent); for (;;) { /* <-- This loop just keeps the thread alive. The main audio loop is inside. */ @@ -26319,7 +26904,7 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) pDevice->workResult = MA_SUCCESS; /* If the reason for the wake up is that we are terminating, just break from the loop. */ - if (ma_device_get_state(pDevice) == MA_STATE_UNINITIALIZED) { + if (ma_device_get_state(pDevice) == ma_device_state_uninitialized) { break; } @@ -26328,7 +26913,7 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) be started will be waiting on an event (pDevice->startEvent) which means we need to make sure we signal the event in both the success and error case. It's important that the state of the device is set _before_ signaling the event. */ - MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_STARTING); + MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_starting); /* If the device has a start callback, start it now. */ if (pDevice->pContext->callbacks.onDeviceStart != NULL) { @@ -26343,7 +26928,7 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) } /* Make sure the state is set appropriately. */ - ma_device__set_state(pDevice, MA_STATE_STARTED); + ma_device__set_state(pDevice, ma_device_state_started); ma_event_signal(&pDevice->startEvent); if (pDevice->pContext->callbacks.onDeviceDataLoop != NULL) { @@ -26370,7 +26955,7 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) } /* A function somewhere is waiting for the device to have stopped for real so we need to signal an event to allow it to continue. */ - ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_device__set_state(pDevice, ma_device_state_stopped); ma_event_signal(&pDevice->stopEvent); } @@ -26389,17 +26974,22 @@ static ma_bool32 ma_device__is_initialized(ma_device* pDevice) return MA_FALSE; } - return ma_device_get_state(pDevice) != MA_STATE_UNINITIALIZED; + return ma_device_get_state(pDevice) != ma_device_state_uninitialized; } #ifdef MA_WIN32 static ma_result ma_context_uninit_backend_apis__win32(ma_context* pContext) { + /* For some reason UWP complains when CoUninitialize() is called. I'm just not going to call it on UWP. */ +#ifdef MA_WIN32_DESKTOP ma_CoUninitialize(pContext); ma_dlclose(pContext, pContext->win32.hUser32DLL); ma_dlclose(pContext, pContext->win32.hOle32DLL); ma_dlclose(pContext, pContext->win32.hAdvapi32DLL); +#else + (void)pContext; +#endif return MA_SUCCESS; } @@ -26606,7 +27196,6 @@ MA_API ma_result ma_context_init(const ma_backend backends[], ma_uint32 backendC } } - pContext->logCallback = pConfig->logCallback; pContext->threadPriority = pConfig->threadPriority; pContext->threadStackSize = pConfig->threadStackSize; pContext->pUserData = pConfig->pUserData; @@ -26744,12 +27333,12 @@ MA_API ma_result ma_context_init(const ma_backend backends[], ma_uint32 backendC if (result == MA_SUCCESS) { result = ma_mutex_init(&pContext->deviceEnumLock); if (result != MA_SUCCESS) { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "Failed to initialize mutex for device enumeration. ma_context_get_devices() is not thread safe.\n", result); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "Failed to initialize mutex for device enumeration. ma_context_get_devices() is not thread safe.\n"); } result = ma_mutex_init(&pContext->deviceInfoLock); if (result != MA_SUCCESS) { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "Failed to initialize mutex for device info retrieval. ma_context_get_device_info() is not thread safe.\n", result); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "Failed to initialize mutex for device info retrieval. ma_context_get_device_info() is not thread safe.\n"); } #ifdef MA_DEBUG_OUTPUT @@ -26757,7 +27346,6 @@ MA_API ma_result ma_context_init(const ma_backend backends[], ma_uint32 backendC ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[miniaudio] Endian: %s\n", ma_is_little_endian() ? "LE" : "BE"); ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[miniaudio] SSE2: %s\n", ma_has_sse2() ? "YES" : "NO"); ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[miniaudio] AVX2: %s\n", ma_has_avx2() ? "YES" : "NO"); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[miniaudio] AVX512F: %s\n", ma_has_avx512f() ? "YES" : "NO"); ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[miniaudio] NEON: %s\n", ma_has_neon() ? "YES" : "NO"); } #endif @@ -26786,7 +27374,7 @@ MA_API ma_result ma_context_uninit(ma_context* pContext) ma_mutex_uninit(&pContext->deviceEnumLock); ma_mutex_uninit(&pContext->deviceInfoLock); - ma__free_from_callbacks(pContext->pDeviceInfos, &pContext->allocationCallbacks); + ma_free(pContext->pDeviceInfos, &pContext->allocationCallbacks); ma_context_uninit_backend_apis(pContext); if (pContext->pLog == &pContext->log) { @@ -26849,9 +27437,8 @@ static ma_bool32 ma_context_get_devices__enum_callback(ma_context* pContext, ma_ const ma_uint32 totalDeviceInfoCount = pContext->playbackDeviceInfoCount + pContext->captureDeviceInfoCount; if (totalDeviceInfoCount >= pContext->deviceInfoCapacity) { - ma_uint32 oldCapacity = pContext->deviceInfoCapacity; - ma_uint32 newCapacity = oldCapacity + bufferExpansionCount; - ma_device_info* pNewInfos = (ma_device_info*)ma__realloc_from_callbacks(pContext->pDeviceInfos, sizeof(*pContext->pDeviceInfos)*newCapacity, sizeof(*pContext->pDeviceInfos)*oldCapacity, &pContext->allocationCallbacks); + ma_uint32 newCapacity = pContext->deviceInfoCapacity + bufferExpansionCount; + ma_device_info* pNewInfos = (ma_device_info*)ma_realloc(pContext->pDeviceInfos, sizeof(*pContext->pDeviceInfos)*newCapacity, &pContext->allocationCallbacks); if (pNewInfos == NULL) { return MA_FALSE; /* Out of memory. */ } @@ -26933,13 +27520,11 @@ MA_API ma_result ma_context_get_devices(ma_context* pContext, ma_device_info** p return result; } -MA_API ma_result ma_context_get_device_info(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo) +MA_API ma_result ma_context_get_device_info(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) { ma_result result; ma_device_info deviceInfo; - (void)shareMode; /* Unused. This parameter will be removed in version 0.11. */ - /* NOTE: Do not clear pDeviceInfo on entry. The reason is the pDeviceID may actually point to pDeviceInfo->id which will break things. */ if (pContext == NULL || pDeviceInfo == NULL) { return MA_INVALID_ARGS; @@ -26962,81 +27547,6 @@ MA_API ma_result ma_context_get_device_info(ma_context* pContext, ma_device_type } ma_mutex_unlock(&pContext->deviceInfoLock); - /* - If the backend is using the new device info system, do a pass to fill out the old settings for backwards compatibility. This will be removed in - the future when all backends have implemented the new device info system. - */ - if (deviceInfo.nativeDataFormatCount > 0) { - ma_uint32 iNativeFormat; - ma_uint32 iSampleFormat; - - deviceInfo.minChannels = 0xFFFFFFFF; - deviceInfo.maxChannels = 0; - deviceInfo.minSampleRate = 0xFFFFFFFF; - deviceInfo.maxSampleRate = 0; - - for (iNativeFormat = 0; iNativeFormat < deviceInfo.nativeDataFormatCount; iNativeFormat += 1) { - /* Formats. */ - if (deviceInfo.nativeDataFormats[iNativeFormat].format == ma_format_unknown) { - /* All formats are supported. */ - deviceInfo.formats[0] = ma_format_u8; - deviceInfo.formats[1] = ma_format_s16; - deviceInfo.formats[2] = ma_format_s24; - deviceInfo.formats[3] = ma_format_s32; - deviceInfo.formats[4] = ma_format_f32; - deviceInfo.formatCount = 5; - } else { - /* Make sure the format isn't already in the list. If so, skip. */ - ma_bool32 alreadyExists = MA_FALSE; - for (iSampleFormat = 0; iSampleFormat < deviceInfo.formatCount; iSampleFormat += 1) { - if (deviceInfo.formats[iSampleFormat] == deviceInfo.nativeDataFormats[iNativeFormat].format) { - alreadyExists = MA_TRUE; - break; - } - } - - if (!alreadyExists) { - deviceInfo.formats[deviceInfo.formatCount++] = deviceInfo.nativeDataFormats[iNativeFormat].format; - } - } - - /* Channels. */ - if (deviceInfo.nativeDataFormats[iNativeFormat].channels == 0) { - /* All channels supported. */ - deviceInfo.minChannels = MA_MIN_CHANNELS; - deviceInfo.maxChannels = MA_MAX_CHANNELS; - } else { - if (deviceInfo.minChannels > deviceInfo.nativeDataFormats[iNativeFormat].channels) { - deviceInfo.minChannels = deviceInfo.nativeDataFormats[iNativeFormat].channels; - } - if (deviceInfo.maxChannels < deviceInfo.nativeDataFormats[iNativeFormat].channels) { - deviceInfo.maxChannels = deviceInfo.nativeDataFormats[iNativeFormat].channels; - } - } - - /* Sample rate. */ - if (deviceInfo.nativeDataFormats[iNativeFormat].sampleRate == 0) { - /* All sample rates supported. */ - deviceInfo.minSampleRate = (ma_uint32)ma_standard_sample_rate_min; - deviceInfo.maxSampleRate = (ma_uint32)ma_standard_sample_rate_max; - } else { - if (deviceInfo.minSampleRate > deviceInfo.nativeDataFormats[iNativeFormat].sampleRate) { - deviceInfo.minSampleRate = deviceInfo.nativeDataFormats[iNativeFormat].sampleRate; - } - if (deviceInfo.maxSampleRate < deviceInfo.nativeDataFormats[iNativeFormat].sampleRate) { - deviceInfo.maxSampleRate = deviceInfo.nativeDataFormats[iNativeFormat].sampleRate; - } - } - } - } - - - /* Clamp ranges. */ - deviceInfo.minChannels = ma_max(deviceInfo.minChannels, MA_MIN_CHANNELS); - deviceInfo.maxChannels = ma_min(deviceInfo.maxChannels, MA_MAX_CHANNELS); - deviceInfo.minSampleRate = ma_max(deviceInfo.minSampleRate, (ma_uint32)ma_standard_sample_rate_min); - deviceInfo.maxSampleRate = ma_min(deviceInfo.maxSampleRate, (ma_uint32)ma_standard_sample_rate_max); - *pDeviceInfo = deviceInfo; return result; } @@ -27056,11 +27566,7 @@ MA_API ma_device_config ma_device_config_init(ma_device_type deviceType) ma_device_config config; MA_ZERO_OBJECT(&config); config.deviceType = deviceType; - - /* Resampling defaults. We must never use the Speex backend by default because it uses licensed third party code. */ - config.resampling.algorithm = ma_resample_algorithm_linear; - config.resampling.linear.lpfOrder = ma_min(MA_DEFAULT_RESAMPLER_LPF_ORDER, MA_MAX_FILTER_ORDER); - config.resampling.speex.quality = 3; + config.resampling = ma_resampler_config_init(ma_format_unknown, 0, 0, 0, ma_resample_algorithm_linear); /* Format/channels/rate don't matter here. */ return config; } @@ -27077,13 +27583,13 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC } if (pDevice == NULL) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with invalid arguments (pDevice == NULL).", MA_INVALID_ARGS); + return MA_INVALID_ARGS; } MA_ZERO_OBJECT(pDevice); if (pConfig == NULL) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with invalid arguments (pConfig == NULL).", MA_INVALID_ARGS); + return MA_INVALID_ARGS; } @@ -27094,25 +27600,23 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC /* Basic config validation. */ - if (pConfig->deviceType != ma_device_type_playback && pConfig->deviceType != ma_device_type_capture && pConfig->deviceType != ma_device_type_duplex && pConfig->deviceType != ma_device_type_loopback) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with an invalid config. Device type is invalid. Make sure the device type has been set in the config.", MA_INVALID_DEVICE_CONFIG); - } - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { if (pConfig->capture.channels > MA_MAX_CHANNELS) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with an invalid config. Capture channel count cannot exceed 32.", MA_INVALID_DEVICE_CONFIG); + return MA_INVALID_ARGS; } - if (!ma__is_channel_map_valid(pConfig->capture.channelMap, pConfig->capture.channels)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with invalid config. Capture channel map is invalid.", MA_INVALID_DEVICE_CONFIG); + + if (!ma__is_channel_map_valid(pConfig->capture.pChannelMap, pConfig->capture.channels)) { + return MA_INVALID_ARGS; } } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex || pConfig->deviceType == ma_device_type_loopback) { if (pConfig->playback.channels > MA_MAX_CHANNELS) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with an invalid config. Playback channel count cannot exceed 32.", MA_INVALID_DEVICE_CONFIG); + return MA_INVALID_ARGS; } - if (!ma__is_channel_map_valid(pConfig->playback.channelMap, pConfig->playback.channels)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with invalid config. Playback channel map is invalid.", MA_INVALID_DEVICE_CONFIG); + + if (!ma__is_channel_map_valid(pConfig->playback.pChannelMap, pConfig->playback.channels)) { + return MA_INVALID_ARGS; } } @@ -27123,12 +27627,6 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC pDevice->onData = pConfig->dataCallback; pDevice->onStop = pConfig->stopCallback; - if (((ma_uintptr)pDevice % sizeof(pDevice)) != 0) { - if (pContext->logCallback) { - pContext->logCallback(pContext, pDevice, MA_LOG_LEVEL_WARNING, "WARNING: ma_device_init() called for a device that is not properly aligned. Thread safety is not supported."); - } - } - if (pConfig->playback.pDeviceID != NULL) { MA_COPY_MEMORY(&pDevice->playback.id, pConfig->playback.pDeviceID, sizeof(pDevice->playback.id)); } @@ -27137,32 +27635,33 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC MA_COPY_MEMORY(&pDevice->capture.id, pConfig->capture.pDeviceID, sizeof(pDevice->capture.id)); } - pDevice->noPreZeroedOutputBuffer = pConfig->noPreZeroedOutputBuffer; - pDevice->noClip = pConfig->noClip; - pDevice->masterVolumeFactor = 1; + pDevice->noPreSilencedOutputBuffer = pConfig->noPreSilencedOutputBuffer; + pDevice->noClip = pConfig->noClip; + pDevice->masterVolumeFactor = 1; - pDevice->type = pConfig->deviceType; - pDevice->sampleRate = pConfig->sampleRate; - pDevice->resampling.algorithm = pConfig->resampling.algorithm; - pDevice->resampling.linear.lpfOrder = pConfig->resampling.linear.lpfOrder; - pDevice->resampling.speex.quality = pConfig->resampling.speex.quality; + pDevice->type = pConfig->deviceType; + pDevice->sampleRate = pConfig->sampleRate; + pDevice->resampling.algorithm = pConfig->resampling.algorithm; + pDevice->resampling.linear.lpfOrder = pConfig->resampling.linear.lpfOrder; + pDevice->resampling.pBackendVTable = pConfig->resampling.pBackendVTable; + pDevice->resampling.pBackendUserData = pConfig->resampling.pBackendUserData; - pDevice->capture.shareMode = pConfig->capture.shareMode; - pDevice->capture.format = pConfig->capture.format; - pDevice->capture.channels = pConfig->capture.channels; - ma_channel_map_copy(pDevice->capture.channelMap, pConfig->capture.channelMap, pConfig->capture.channels); - pDevice->capture.channelMixMode = pConfig->capture.channelMixMode; + pDevice->capture.shareMode = pConfig->capture.shareMode; + pDevice->capture.format = pConfig->capture.format; + pDevice->capture.channels = pConfig->capture.channels; + ma_channel_map_copy_or_default(pDevice->capture.channelMap, ma_countof(pDevice->capture.channelMap), pConfig->capture.pChannelMap, pConfig->capture.channels); + pDevice->capture.channelMixMode = pConfig->capture.channelMixMode; - pDevice->playback.shareMode = pConfig->playback.shareMode; - pDevice->playback.format = pConfig->playback.format; - pDevice->playback.channels = pConfig->playback.channels; - ma_channel_map_copy(pDevice->playback.channelMap, pConfig->playback.channelMap, pConfig->playback.channels); - pDevice->playback.channelMixMode = pConfig->playback.channelMixMode; + pDevice->playback.shareMode = pConfig->playback.shareMode; + pDevice->playback.format = pConfig->playback.format; + pDevice->playback.channels = pConfig->playback.channels; + ma_channel_map_copy_or_default(pDevice->playback.channelMap, ma_countof(pDevice->playback.channelMap), pConfig->playback.pChannelMap, pConfig->playback.channels); + pDevice->playback.channelMixMode = pConfig->playback.channelMixMode; result = ma_mutex_init(&pDevice->startStopLock); if (result != MA_SUCCESS) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to create mutex.", result); + return result; } /* @@ -27175,14 +27674,14 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC result = ma_event_init(&pDevice->wakeupEvent); if (result != MA_SUCCESS) { ma_mutex_uninit(&pDevice->startStopLock); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to create worker thread wakeup event.", result); + return result; } result = ma_event_init(&pDevice->startEvent); if (result != MA_SUCCESS) { ma_event_uninit(&pDevice->wakeupEvent); ma_mutex_uninit(&pDevice->startStopLock); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to create worker thread start event.", result); + return result; } result = ma_event_init(&pDevice->stopEvent); @@ -27190,7 +27689,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC ma_event_uninit(&pDevice->startEvent); ma_event_uninit(&pDevice->wakeupEvent); ma_mutex_uninit(&pDevice->startStopLock); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to create worker thread stop event.", result); + return result; } @@ -27200,7 +27699,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC descriptorPlayback.format = pConfig->playback.format; descriptorPlayback.channels = pConfig->playback.channels; descriptorPlayback.sampleRate = pConfig->sampleRate; - ma_channel_map_copy(descriptorPlayback.channelMap, pConfig->playback.channelMap, pConfig->playback.channels); + ma_channel_map_copy_or_default(descriptorPlayback.channelMap, ma_countof(descriptorPlayback.channelMap), pConfig->playback.pChannelMap, pConfig->playback.channels); descriptorPlayback.periodSizeInFrames = pConfig->periodSizeInFrames; descriptorPlayback.periodSizeInMilliseconds = pConfig->periodSizeInMilliseconds; descriptorPlayback.periodCount = pConfig->periods; @@ -27216,7 +27715,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC descriptorCapture.format = pConfig->capture.format; descriptorCapture.channels = pConfig->capture.channels; descriptorCapture.sampleRate = pConfig->sampleRate; - ma_channel_map_copy(descriptorCapture.channelMap, pConfig->capture.channelMap, pConfig->capture.channels); + ma_channel_map_copy_or_default(descriptorCapture.channelMap, ma_countof(descriptorCapture.channelMap), pConfig->capture.pChannelMap, pConfig->capture.channels); descriptorCapture.periodSizeInFrames = pConfig->periodSizeInFrames; descriptorCapture.periodSizeInMilliseconds = pConfig->periodSizeInMilliseconds; descriptorCapture.periodCount = pConfig->periods; @@ -27284,7 +27783,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC ma_device_info deviceInfo; if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex || pConfig->deviceType == ma_device_type_loopback) { - result = ma_context_get_device_info(pContext, (pConfig->deviceType == ma_device_type_loopback) ? ma_device_type_playback : ma_device_type_capture, descriptorCapture.pDeviceID, descriptorCapture.shareMode, &deviceInfo); + result = ma_context_get_device_info(pContext, (pConfig->deviceType == ma_device_type_loopback) ? ma_device_type_playback : ma_device_type_capture, descriptorCapture.pDeviceID, &deviceInfo); if (result == MA_SUCCESS) { ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), deviceInfo.name, (size_t)-1); } else { @@ -27298,7 +27797,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - result = ma_context_get_device_info(pContext, ma_device_type_playback, descriptorPlayback.pDeviceID, descriptorPlayback.shareMode, &deviceInfo); + result = ma_context_get_device_info(pContext, ma_device_type_playback, descriptorPlayback.pDeviceID, &deviceInfo); if (result == MA_SUCCESS) { ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), deviceInfo.name, (size_t)-1); } else { @@ -27322,12 +27821,12 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC result = ma_thread_create(&pDevice->thread, pContext->threadPriority, pContext->threadStackSize, ma_worker_thread, pDevice, &pContext->allocationCallbacks); if (result != MA_SUCCESS) { ma_device_uninit(pDevice); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to create worker thread.", result); + return result; } /* Wait for the worker thread to put the device into it's stopped state for real. */ ma_event_wait(&pDevice->stopEvent); - MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_STOPPED); + MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_stopped); } else { /* If the backend is asynchronous and the device is duplex, we'll need an intermediary ring buffer. Note that this needs to be done @@ -27343,7 +27842,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC } } - ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_device__set_state(pDevice, ma_device_state_stopped); } @@ -27375,7 +27874,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Passthrough: %s\n", pDevice->playback.converter.isPassthrough ? "YES" : "NO"); } - MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_STOPPED); + MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_stopped); return MA_SUCCESS; } @@ -27403,7 +27902,7 @@ MA_API ma_result ma_device_init_ex(const ma_backend backends[], ma_uint32 backen } - pContext = (ma_context*)ma__malloc_from_callbacks(sizeof(*pContext), &allocationCallbacks); + pContext = (ma_context*)ma_malloc(sizeof(*pContext), &allocationCallbacks); if (pContext == NULL) { return MA_OUT_OF_MEMORY; } @@ -27434,7 +27933,7 @@ MA_API ma_result ma_device_init_ex(const ma_backend backends[], ma_uint32 backen } if (result != MA_SUCCESS) { - ma__free_from_callbacks(pContext, &allocationCallbacks); + ma_free(pContext, &allocationCallbacks); return result; } @@ -27454,7 +27953,7 @@ MA_API void ma_device_uninit(ma_device* pDevice) } /* Putting the device into an uninitialized state will make the worker thread return. */ - ma_device__set_state(pDevice, MA_STATE_UNINITIALIZED); + ma_device__set_state(pDevice, ma_device_state_uninitialized); /* Wake up the worker thread and wait for it to properly terminate. */ if (!ma_context_is_backend_asynchronous(pDevice->pContext)) { @@ -27478,11 +27977,22 @@ MA_API void ma_device_uninit(ma_device* pDevice) } } + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { + ma_data_converter_uninit(&pDevice->capture.converter, &pDevice->pContext->allocationCallbacks); + } + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ma_data_converter_uninit(&pDevice->playback.converter, &pDevice->pContext->allocationCallbacks); + } + + if (pDevice->playback.pInputCache != NULL) { + ma_free(pDevice->playback.pInputCache, &pDevice->pContext->allocationCallbacks); + } + if (pDevice->isOwnerOfContext) { ma_allocation_callbacks allocationCallbacks = pDevice->pContext->allocationCallbacks; ma_context_uninit(pDevice->pContext); - ma__free_from_callbacks(pDevice->pContext, &allocationCallbacks); + ma_free(pDevice->pContext, &allocationCallbacks); } MA_ZERO_OBJECT(pDevice); @@ -27507,23 +28017,23 @@ MA_API ma_result ma_device_start(ma_device* pDevice) ma_result result; if (pDevice == NULL) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "ma_device_start() called with invalid arguments (pDevice == NULL).", MA_INVALID_ARGS); + return MA_INVALID_ARGS; } - if (ma_device_get_state(pDevice) == MA_STATE_UNINITIALIZED) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "ma_device_start() called for an uninitialized device.", MA_DEVICE_NOT_INITIALIZED); + if (ma_device_get_state(pDevice) == ma_device_state_uninitialized) { + return MA_INVALID_OPERATION; /* Not initialized. */ } - if (ma_device_get_state(pDevice) == MA_STATE_STARTED) { - return ma_post_error(pDevice, MA_LOG_LEVEL_WARNING, "ma_device_start() called when the device is already started.", MA_INVALID_OPERATION); /* Already started. Returning an error to let the application know because it probably means they're doing something wrong. */ + if (ma_device_get_state(pDevice) == ma_device_state_started) { + return MA_INVALID_OPERATION; /* Already started. Returning an error to let the application know because it probably means they're doing something wrong. */ } ma_mutex_lock(&pDevice->startStopLock); { /* Starting and stopping are wrapped in a mutex which means we can assert that the device is in a stopped or paused state. */ - MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_STOPPED); + MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_stopped); - ma_device__set_state(pDevice, MA_STATE_STARTING); + ma_device__set_state(pDevice, ma_device_state_starting); /* Asynchronous backends need to be handled differently. */ if (ma_context_is_backend_asynchronous(pDevice->pContext)) { @@ -27534,7 +28044,7 @@ MA_API ma_result ma_device_start(ma_device* pDevice) } if (result == MA_SUCCESS) { - ma_device__set_state(pDevice, MA_STATE_STARTED); + ma_device__set_state(pDevice, ma_device_state_started); } } else { /* @@ -27553,7 +28063,7 @@ MA_API ma_result ma_device_start(ma_device* pDevice) /* We changed the state from stopped to started, so if we failed, make sure we put the state back to stopped. */ if (result != MA_SUCCESS) { - ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_device__set_state(pDevice, ma_device_state_stopped); } } ma_mutex_unlock(&pDevice->startStopLock); @@ -27566,23 +28076,23 @@ MA_API ma_result ma_device_stop(ma_device* pDevice) ma_result result; if (pDevice == NULL) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "ma_device_stop() called with invalid arguments (pDevice == NULL).", MA_INVALID_ARGS); + return MA_INVALID_ARGS; } - if (ma_device_get_state(pDevice) == MA_STATE_UNINITIALIZED) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "ma_device_stop() called for an uninitialized device.", MA_DEVICE_NOT_INITIALIZED); + if (ma_device_get_state(pDevice) == ma_device_state_uninitialized) { + return MA_INVALID_OPERATION; /* Not initialized. */ } - if (ma_device_get_state(pDevice) == MA_STATE_STOPPED) { - return ma_post_error(pDevice, MA_LOG_LEVEL_WARNING, "ma_device_stop() called when the device is already stopped.", MA_INVALID_OPERATION); /* Already stopped. Returning an error to let the application know because it probably means they're doing something wrong. */ + if (ma_device_get_state(pDevice) == ma_device_state_stopped) { + return MA_INVALID_OPERATION; /* Already stopped. Returning an error to let the application know because it probably means they're doing something wrong. */ } ma_mutex_lock(&pDevice->startStopLock); { /* Starting and stopping are wrapped in a mutex which means we can assert that the device is in a started or paused state. */ - MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_STARTED); + MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_started); - ma_device__set_state(pDevice, MA_STATE_STOPPING); + ma_device__set_state(pDevice, ma_device_state_stopping); /* Asynchronous backends need to be handled differently. */ if (ma_context_is_backend_asynchronous(pDevice->pContext)) { @@ -27593,7 +28103,7 @@ MA_API ma_result ma_device_stop(ma_device* pDevice) result = MA_INVALID_OPERATION; } - ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_device__set_state(pDevice, ma_device_state_stopped); } else { /* Synchronous backends. The stop callback is always called from the worker thread. Do not call the stop callback here. If @@ -27601,7 +28111,7 @@ MA_API ma_result ma_device_stop(ma_device* pDevice) sure the state of the device is *not* playing right now, which it shouldn't be since we set it above. This is super important though, so I'm asserting it here as well for extra safety in case we accidentally change something later. */ - MA_ASSERT(ma_device_get_state(pDevice) != MA_STATE_STARTED); + MA_ASSERT(ma_device_get_state(pDevice) != ma_device_state_started); if (pDevice->pContext->callbacks.onDeviceDataLoopWakeup != NULL) { pDevice->pContext->callbacks.onDeviceDataLoopWakeup(pDevice); @@ -27622,16 +28132,16 @@ MA_API ma_result ma_device_stop(ma_device* pDevice) MA_API ma_bool32 ma_device_is_started(const ma_device* pDevice) { - return ma_device_get_state(pDevice) == MA_STATE_STARTED; + return ma_device_get_state(pDevice) == ma_device_state_started; } -MA_API ma_uint32 ma_device_get_state(const ma_device* pDevice) +MA_API ma_device_state ma_device_get_state(const ma_device* pDevice) { if (pDevice == NULL) { - return MA_STATE_UNINITIALIZED; + return ma_device_state_uninitialized; } - return c89atomic_load_32((ma_uint32*)&pDevice->state); /* Naughty cast to get rid of a const warning. */ + return (ma_device_state)c89atomic_load_i32((ma_int32*)&pDevice->state); /* Naughty cast to get rid of a const warning. */ } MA_API ma_result ma_device_set_master_volume(ma_device* pDevice, float volume) @@ -27640,7 +28150,7 @@ MA_API ma_result ma_device_set_master_volume(ma_device* pDevice, float volume) return MA_INVALID_ARGS; } - if (volume < 0.0f || volume > 1.0f) { + if (volume < 0.0f) { return MA_INVALID_ARGS; } @@ -27665,16 +28175,16 @@ MA_API ma_result ma_device_get_master_volume(ma_device* pDevice, float* pVolume) return MA_SUCCESS; } -MA_API ma_result ma_device_set_master_gain_db(ma_device* pDevice, float gainDB) +MA_API ma_result ma_device_set_master_volume_db(ma_device* pDevice, float gainDB) { if (gainDB > 0) { return MA_INVALID_ARGS; } - return ma_device_set_master_volume(pDevice, ma_gain_db_to_factor(gainDB)); + return ma_device_set_master_volume(pDevice, ma_volume_db_to_linear(gainDB)); } -MA_API ma_result ma_device_get_master_gain_db(ma_device* pDevice, float* pGainDB) +MA_API ma_result ma_device_get_master_volume_db(ma_device* pDevice, float* pGainDB) { float factor; ma_result result; @@ -27689,7 +28199,7 @@ MA_API ma_result ma_device_get_master_gain_db(ma_device* pDevice, float* pGainDB return result; } - *pGainDB = ma_factor_to_gain_db(factor); + *pGainDB = ma_volume_linear_to_db(factor); return MA_SUCCESS; } @@ -27772,11 +28282,6 @@ MA_API ma_uint32 ma_calculate_buffer_size_in_frames_from_descriptor(const ma_dev #endif /* MA_NO_DEVICE_IO */ -MA_API ma_uint32 ma_scale_buffer_size(ma_uint32 baseBufferSize, float scale) -{ - return ma_max(1, (ma_uint32)(baseBufferSize*scale)); -} - MA_API ma_uint32 ma_calculate_buffer_size_in_milliseconds_from_frames(ma_uint32 bufferSizeInFrames, ma_uint32 sampleRate) { /* Prevent a division by zero. */ @@ -27830,13 +28335,89 @@ MA_API const void* ma_offset_pcm_frames_const_ptr(const void* p, ma_uint64 offse } -MA_API void ma_clip_samples_f32(float* p, ma_uint64 sampleCount) +MA_API void ma_clip_samples_u8(ma_uint8* pDst, const ma_int16* pSrc, ma_uint64 count) { - ma_uint32 iSample; + ma_uint64 iSample; - /* TODO: Research a branchless SSE implementation. */ - for (iSample = 0; iSample < sampleCount; iSample += 1) { - p[iSample] = ma_clip_f32(p[iSample]); + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_u8(pSrc[iSample]); + } +} + +MA_API void ma_clip_samples_s16(ma_int16* pDst, const ma_int32* pSrc, ma_uint64 count) +{ + ma_uint64 iSample; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_s16(pSrc[iSample]); + } +} + +MA_API void ma_clip_samples_s24(ma_uint8* pDst, const ma_int64* pSrc, ma_uint64 count) +{ + ma_uint64 iSample; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + for (iSample = 0; iSample < count; iSample += 1) { + ma_int64 s = ma_clip_s24(pSrc[iSample]); + pDst[iSample*3 + 0] = (ma_uint8)((s & 0x000000FF) >> 0); + pDst[iSample*3 + 1] = (ma_uint8)((s & 0x0000FF00) >> 8); + pDst[iSample*3 + 2] = (ma_uint8)((s & 0x00FF0000) >> 16); + } +} + +MA_API void ma_clip_samples_s32(ma_int32* pDst, const ma_int64* pSrc, ma_uint64 count) +{ + ma_uint64 iSample; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_s32(pSrc[iSample]); + } +} + +MA_API void ma_clip_samples_f32(float* pDst, const float* pSrc, ma_uint64 count) +{ + ma_uint64 iSample; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_f32(pSrc[iSample]); + } +} + +MA_API void ma_clip_pcm_frames(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format format, ma_uint32 channels) +{ + ma_uint64 sampleCount; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + sampleCount = frameCount * channels; + + switch (format) { + case ma_format_u8: ma_clip_samples_u8( (ma_uint8*)pDst, (const ma_int16*)pSrc, sampleCount); break; + case ma_format_s16: ma_clip_samples_s16((ma_int16*)pDst, (const ma_int32*)pSrc, sampleCount); break; + case ma_format_s24: ma_clip_samples_s24((ma_uint8*)pDst, (const ma_int64*)pSrc, sampleCount); break; + case ma_format_s32: ma_clip_samples_s32((ma_int32*)pDst, (const ma_int64*)pSrc, sampleCount); break; + case ma_format_f32: ma_clip_samples_f32(( float*)pDst, (const float*)pSrc, sampleCount); break; + + /* Do nothing if we don't know the format. We're including these here to silence a compiler warning about enums not being handled by the switch. */ + case ma_format_unknown: + case ma_format_count: + break; } } @@ -27913,8 +28494,19 @@ MA_API void ma_copy_and_apply_volume_factor_f32(float* pSamplesOut, const float* return; } - for (iSample = 0; iSample < sampleCount; iSample += 1) { - pSamplesOut[iSample] = pSamplesIn[iSample] * factor; + if (factor == 1) { + if (pSamplesOut == pSamplesIn) { + /* In place. No-op. */ + } else { + /* Just a copy. */ + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pSamplesOut[iSample] = pSamplesIn[iSample]; + } + } + } else { + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pSamplesOut[iSample] = pSamplesIn[iSample] * factor; + } } } @@ -27943,85 +28535,502 @@ MA_API void ma_apply_volume_factor_f32(float* pSamples, ma_uint64 sampleCount, f ma_copy_and_apply_volume_factor_f32(pSamples, pSamples, sampleCount, factor); } -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_u8(ma_uint8* pPCMFramesOut, const ma_uint8* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_u8(ma_uint8* pFramesOut, const ma_uint8* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_u8(pPCMFramesOut, pPCMFramesIn, frameCount*channels, factor); + ma_copy_and_apply_volume_factor_u8(pFramesOut, pFramesIn, frameCount*channels, factor); } -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s16(ma_int16* pPCMFramesOut, const ma_int16* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s16(ma_int16* pFramesOut, const ma_int16* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_s16(pPCMFramesOut, pPCMFramesIn, frameCount*channels, factor); + ma_copy_and_apply_volume_factor_s16(pFramesOut, pFramesIn, frameCount*channels, factor); } -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s24(void* pPCMFramesOut, const void* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s24(void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_s24(pPCMFramesOut, pPCMFramesIn, frameCount*channels, factor); + ma_copy_and_apply_volume_factor_s24(pFramesOut, pFramesIn, frameCount*channels, factor); } -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s32(ma_int32* pPCMFramesOut, const ma_int32* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s32(ma_int32* pFramesOut, const ma_int32* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_s32(pPCMFramesOut, pPCMFramesIn, frameCount*channels, factor); + ma_copy_and_apply_volume_factor_s32(pFramesOut, pFramesIn, frameCount*channels, factor); } -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_f32(float* pPCMFramesOut, const float* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_f32(pPCMFramesOut, pPCMFramesIn, frameCount*channels, factor); + ma_copy_and_apply_volume_factor_f32(pFramesOut, pFramesIn, frameCount*channels, factor); } -MA_API void ma_copy_and_apply_volume_factor_pcm_frames(void* pPCMFramesOut, const void* pPCMFramesIn, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float factor) +MA_API void ma_copy_and_apply_volume_factor_pcm_frames(void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float factor) { switch (format) { - case ma_format_u8: ma_copy_and_apply_volume_factor_pcm_frames_u8 ((ma_uint8*)pPCMFramesOut, (const ma_uint8*)pPCMFramesIn, frameCount, channels, factor); return; - case ma_format_s16: ma_copy_and_apply_volume_factor_pcm_frames_s16((ma_int16*)pPCMFramesOut, (const ma_int16*)pPCMFramesIn, frameCount, channels, factor); return; - case ma_format_s24: ma_copy_and_apply_volume_factor_pcm_frames_s24( pPCMFramesOut, pPCMFramesIn, frameCount, channels, factor); return; - case ma_format_s32: ma_copy_and_apply_volume_factor_pcm_frames_s32((ma_int32*)pPCMFramesOut, (const ma_int32*)pPCMFramesIn, frameCount, channels, factor); return; - case ma_format_f32: ma_copy_and_apply_volume_factor_pcm_frames_f32( (float*)pPCMFramesOut, (const float*)pPCMFramesIn, frameCount, channels, factor); return; + case ma_format_u8: ma_copy_and_apply_volume_factor_pcm_frames_u8 ((ma_uint8*)pFramesOut, (const ma_uint8*)pFramesIn, frameCount, channels, factor); return; + case ma_format_s16: ma_copy_and_apply_volume_factor_pcm_frames_s16((ma_int16*)pFramesOut, (const ma_int16*)pFramesIn, frameCount, channels, factor); return; + case ma_format_s24: ma_copy_and_apply_volume_factor_pcm_frames_s24( pFramesOut, pFramesIn, frameCount, channels, factor); return; + case ma_format_s32: ma_copy_and_apply_volume_factor_pcm_frames_s32((ma_int32*)pFramesOut, (const ma_int32*)pFramesIn, frameCount, channels, factor); return; + case ma_format_f32: ma_copy_and_apply_volume_factor_pcm_frames_f32( (float*)pFramesOut, (const float*)pFramesIn, frameCount, channels, factor); return; default: return; /* Do nothing. */ } } -MA_API void ma_apply_volume_factor_pcm_frames_u8(ma_uint8* pPCMFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_apply_volume_factor_pcm_frames_u8(ma_uint8* pFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_pcm_frames_u8(pPCMFrames, pPCMFrames, frameCount, channels, factor); + ma_copy_and_apply_volume_factor_pcm_frames_u8(pFrames, pFrames, frameCount, channels, factor); } -MA_API void ma_apply_volume_factor_pcm_frames_s16(ma_int16* pPCMFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_apply_volume_factor_pcm_frames_s16(ma_int16* pFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_pcm_frames_s16(pPCMFrames, pPCMFrames, frameCount, channels, factor); + ma_copy_and_apply_volume_factor_pcm_frames_s16(pFrames, pFrames, frameCount, channels, factor); } -MA_API void ma_apply_volume_factor_pcm_frames_s24(void* pPCMFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_apply_volume_factor_pcm_frames_s24(void* pFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_pcm_frames_s24(pPCMFrames, pPCMFrames, frameCount, channels, factor); + ma_copy_and_apply_volume_factor_pcm_frames_s24(pFrames, pFrames, frameCount, channels, factor); } -MA_API void ma_apply_volume_factor_pcm_frames_s32(ma_int32* pPCMFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_apply_volume_factor_pcm_frames_s32(ma_int32* pFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_pcm_frames_s32(pPCMFrames, pPCMFrames, frameCount, channels, factor); + ma_copy_and_apply_volume_factor_pcm_frames_s32(pFrames, pFrames, frameCount, channels, factor); } -MA_API void ma_apply_volume_factor_pcm_frames_f32(float* pPCMFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_apply_volume_factor_pcm_frames_f32(float* pFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_pcm_frames_f32(pPCMFrames, pPCMFrames, frameCount, channels, factor); + ma_copy_and_apply_volume_factor_pcm_frames_f32(pFrames, pFrames, frameCount, channels, factor); } -MA_API void ma_apply_volume_factor_pcm_frames(void* pPCMFrames, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float factor) +MA_API void ma_apply_volume_factor_pcm_frames(void* pFramesOut, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_pcm_frames(pPCMFrames, pPCMFrames, frameCount, format, channels, factor); + ma_copy_and_apply_volume_factor_pcm_frames(pFramesOut, pFramesOut, frameCount, format, channels, factor); } -MA_API float ma_factor_to_gain_db(float factor) +MA_API void ma_copy_and_apply_volume_factor_per_channel_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float* pChannelGains) { - return (float)(20*ma_log10f(factor)); + ma_uint64 iFrame; + + if (channels == 2) { + /* TODO: Do an optimized implementation for stereo and mono. Can do a SIMD optimized implementation as well. */ + } + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + pFramesOut[iFrame * channels + iChannel] = pFramesIn[iFrame * channels + iChannel] * pChannelGains[iChannel]; + } + } } -MA_API float ma_gain_db_to_factor(float gain) + + +static MA_INLINE ma_int16 ma_apply_volume_unclipped_u8(ma_int16 x, ma_int16 volume) { - return (float)ma_powf(10, gain/20.0f); + return (ma_int16)(((ma_int32)x * (ma_int32)volume) >> 8); } +static MA_INLINE ma_int32 ma_apply_volume_unclipped_s16(ma_int32 x, ma_int16 volume) +{ + return (ma_int32)((x * volume) >> 8); +} + +static MA_INLINE ma_int64 ma_apply_volume_unclipped_s24(ma_int64 x, ma_int16 volume) +{ + return (ma_int64)((x * volume) >> 8); +} + +static MA_INLINE ma_int64 ma_apply_volume_unclipped_s32(ma_int64 x, ma_int16 volume) +{ + return (ma_int64)((x * volume) >> 8); +} + +static MA_INLINE float ma_apply_volume_unclipped_f32(float x, float volume) +{ + return x * volume; +} + + +MA_API void ma_copy_and_apply_volume_and_clip_samples_u8(ma_uint8* pDst, const ma_int16* pSrc, ma_uint64 count, float volume) +{ + ma_uint64 iSample; + ma_int16 volumeFixed; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + volumeFixed = ma_float_to_fixed_16(volume); + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_u8(ma_apply_volume_unclipped_u8(pSrc[iSample], volumeFixed)); + } +} + +MA_API void ma_copy_and_apply_volume_and_clip_samples_s16(ma_int16* pDst, const ma_int32* pSrc, ma_uint64 count, float volume) +{ + ma_uint64 iSample; + ma_int16 volumeFixed; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + volumeFixed = ma_float_to_fixed_16(volume); + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_s16(ma_apply_volume_unclipped_s16(pSrc[iSample], volumeFixed)); + } +} + +MA_API void ma_copy_and_apply_volume_and_clip_samples_s24(ma_uint8* pDst, const ma_int64* pSrc, ma_uint64 count, float volume) +{ + ma_uint64 iSample; + ma_int16 volumeFixed; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + volumeFixed = ma_float_to_fixed_16(volume); + + for (iSample = 0; iSample < count; iSample += 1) { + ma_int64 s = ma_clip_s24(ma_apply_volume_unclipped_s24(pSrc[iSample], volumeFixed)); + pDst[iSample*3 + 0] = (ma_uint8)((s & 0x000000FF) >> 0); + pDst[iSample*3 + 1] = (ma_uint8)((s & 0x0000FF00) >> 8); + pDst[iSample*3 + 2] = (ma_uint8)((s & 0x00FF0000) >> 16); + } +} + +MA_API void ma_copy_and_apply_volume_and_clip_samples_s32(ma_int32* pDst, const ma_int64* pSrc, ma_uint64 count, float volume) +{ + ma_uint64 iSample; + ma_int16 volumeFixed; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + volumeFixed = ma_float_to_fixed_16(volume); + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_s32(ma_apply_volume_unclipped_s32(pSrc[iSample], volumeFixed)); + } +} + +MA_API void ma_copy_and_apply_volume_and_clip_samples_f32(float* pDst, const float* pSrc, ma_uint64 count, float volume) +{ + ma_uint64 iSample; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + /* For the f32 case we need to make sure this supports in-place processing where the input and output buffers are the same. */ + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_f32(ma_apply_volume_unclipped_f32(pSrc[iSample], volume)); + } +} + +MA_API void ma_copy_and_apply_volume_and_clip_pcm_frames(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float volume) +{ + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + if (volume == 1) { + ma_clip_pcm_frames(pDst, pSrc, frameCount, format, channels); /* Optimized case for volume = 1. */ + } else if (volume == 0) { + ma_silence_pcm_frames(pDst, frameCount, format, channels); /* Optimized case for volume = 0. */ + } else { + ma_uint64 sampleCount = frameCount * channels; + + switch (format) { + case ma_format_u8: ma_copy_and_apply_volume_and_clip_samples_u8( (ma_uint8*)pDst, (const ma_int16*)pSrc, sampleCount, volume); break; + case ma_format_s16: ma_copy_and_apply_volume_and_clip_samples_s16((ma_int16*)pDst, (const ma_int32*)pSrc, sampleCount, volume); break; + case ma_format_s24: ma_copy_and_apply_volume_and_clip_samples_s24((ma_uint8*)pDst, (const ma_int64*)pSrc, sampleCount, volume); break; + case ma_format_s32: ma_copy_and_apply_volume_and_clip_samples_s32((ma_int32*)pDst, (const ma_int64*)pSrc, sampleCount, volume); break; + case ma_format_f32: ma_copy_and_apply_volume_and_clip_samples_f32(( float*)pDst, (const float*)pSrc, sampleCount, volume); break; + + /* Do nothing if we don't know the format. We're including these here to silence a compiler warning about enums not being handled by the switch. */ + case ma_format_unknown: + case ma_format_count: + break; + } + } +} + + + +MA_API float ma_volume_linear_to_db(float factor) +{ + return 20*ma_log10f(factor); +} + +MA_API float ma_volume_db_to_linear(float gain) +{ + return ma_powf(10, gain/20.0f); +} + + + +MA_API ma_slot_allocator_config ma_slot_allocator_config_init(ma_uint32 capacity) +{ + ma_slot_allocator_config config; + + MA_ZERO_OBJECT(&config); + config.capacity = capacity; + + return config; +} + + +static MA_INLINE ma_uint32 ma_slot_allocator_calculate_group_capacity(ma_uint32 slotCapacity) +{ + ma_uint32 cap = slotCapacity / 32; + if ((slotCapacity % 32) != 0) { + cap += 1; + } + + return cap; +} + +static MA_INLINE ma_uint32 ma_slot_allocator_group_capacity(const ma_slot_allocator* pAllocator) +{ + return ma_slot_allocator_calculate_group_capacity(pAllocator->capacity); +} + + +typedef struct +{ + size_t sizeInBytes; + size_t groupsOffset; + size_t slotsOffset; +} ma_slot_allocator_heap_layout; + +static ma_result ma_slot_allocator_get_heap_layout(const ma_slot_allocator_config* pConfig, ma_slot_allocator_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->capacity == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* Groups. */ + pHeapLayout->groupsOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(ma_slot_allocator_calculate_group_capacity(pConfig->capacity) * sizeof(ma_slot_allocator_group)); + + /* Slots. */ + pHeapLayout->slotsOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(pConfig->capacity * sizeof(ma_uint32)); + + return MA_SUCCESS; +} + +MA_API ma_result ma_slot_allocator_get_heap_size(const ma_slot_allocator_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_slot_allocator_heap_layout layout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_slot_allocator_get_heap_layout(pConfig, &layout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = layout.sizeInBytes; + + return result; +} + +MA_API ma_result ma_slot_allocator_init_preallocated(const ma_slot_allocator_config* pConfig, void* pHeap, ma_slot_allocator* pAllocator) +{ + ma_result result; + ma_slot_allocator_heap_layout heapLayout; + + if (pAllocator == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pAllocator); + + if (pHeap == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_slot_allocator_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pAllocator->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pAllocator->pGroups = (ma_slot_allocator_group*)ma_offset_ptr(pHeap, heapLayout.groupsOffset); + pAllocator->pSlots = (ma_uint32*)ma_offset_ptr(pHeap, heapLayout.slotsOffset); + pAllocator->capacity = pConfig->capacity; + + return MA_SUCCESS; +} + +MA_API ma_result ma_slot_allocator_init(const ma_slot_allocator_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_slot_allocator* pAllocator) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_slot_allocator_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the size of the heap allocation. */ + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_slot_allocator_init_preallocated(pConfig, pHeap, pAllocator); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pAllocator->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_slot_allocator_uninit(ma_slot_allocator* pAllocator, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocator == NULL) { + return; + } + + if (pAllocator->_ownsHeap) { + ma_free(pAllocator->_pHeap, pAllocationCallbacks); + } +} + +MA_API ma_result ma_slot_allocator_alloc(ma_slot_allocator* pAllocator, ma_uint64* pSlot) +{ + ma_uint32 iAttempt; + const ma_uint32 maxAttempts = 2; /* The number of iterations to perform until returning MA_OUT_OF_MEMORY if no slots can be found. */ + + if (pAllocator == NULL || pSlot == NULL) { + return MA_INVALID_ARGS; + } + + for (iAttempt = 0; iAttempt < maxAttempts; iAttempt += 1) { + /* We need to acquire a suitable bitfield first. This is a bitfield that's got an available slot within it. */ + ma_uint32 iGroup; + for (iGroup = 0; iGroup < ma_slot_allocator_group_capacity(pAllocator); iGroup += 1) { + /* CAS */ + for (;;) { + ma_uint32 oldBitfield; + ma_uint32 newBitfield; + ma_uint32 bitOffset; + + oldBitfield = c89atomic_load_32(&pAllocator->pGroups[iGroup].bitfield); /* <-- This copy must happen. The compiler must not optimize this away. */ + + /* Fast check to see if anything is available. */ + if (oldBitfield == 0xFFFFFFFF) { + break; /* No available bits in this bitfield. */ + } + + bitOffset = ma_ffs_32(~oldBitfield); + MA_ASSERT(bitOffset < 32); + + newBitfield = oldBitfield | (1 << bitOffset); + + if (c89atomic_compare_and_swap_32(&pAllocator->pGroups[iGroup].bitfield, oldBitfield, newBitfield) == oldBitfield) { + ma_uint32 slotIndex; + + /* Increment the counter as soon as possible to have other threads report out-of-memory sooner than later. */ + c89atomic_fetch_add_32(&pAllocator->count, 1); + + /* The slot index is required for constructing the output value. */ + slotIndex = (iGroup << 5) + bitOffset; /* iGroup << 5 = iGroup * 32 */ + if (slotIndex >= pAllocator->capacity) { + return MA_OUT_OF_MEMORY; + } + + /* Increment the reference count before constructing the output value. */ + pAllocator->pSlots[slotIndex] += 1; + + /* Construct the output value. */ + *pSlot = (((ma_uint64)pAllocator->pSlots[slotIndex] << 32) | slotIndex); + + return MA_SUCCESS; + } + } + } + + /* We weren't able to find a slot. If it's because we've reached our capacity we need to return MA_OUT_OF_MEMORY. Otherwise we need to do another iteration and try again. */ + if (pAllocator->count < pAllocator->capacity) { + ma_yield(); + } else { + return MA_OUT_OF_MEMORY; + } + } + + /* We couldn't find a slot within the maximum number of attempts. */ + return MA_OUT_OF_MEMORY; +} + +MA_API ma_result ma_slot_allocator_free(ma_slot_allocator* pAllocator, ma_uint64 slot) +{ + ma_uint32 iGroup; + ma_uint32 iBit; + + if (pAllocator == NULL) { + return MA_INVALID_ARGS; + } + + iGroup = (ma_uint32)((slot & 0xFFFFFFFF) >> 5); /* slot / 32 */ + iBit = (ma_uint32)((slot & 0xFFFFFFFF) & 31); /* slot % 32 */ + + if (iGroup >= ma_slot_allocator_group_capacity(pAllocator)) { + return MA_INVALID_ARGS; + } + + MA_ASSERT(iBit < 32); /* This must be true due to the logic we used to actually calculate it. */ + + while (c89atomic_load_32(&pAllocator->count) > 0) { + /* CAS */ + ma_uint32 oldBitfield; + ma_uint32 newBitfield; + + oldBitfield = c89atomic_load_32(&pAllocator->pGroups[iGroup].bitfield); /* <-- This copy must happen. The compiler must not optimize this away. */ + newBitfield = oldBitfield & ~(1 << iBit); + + /* Debugging for checking for double-frees. */ + #if defined(MA_DEBUG_OUTPUT) + { + if ((oldBitfield & (1 << iBit)) == 0) { + MA_ASSERT(MA_FALSE); /* Double free detected.*/ + } + } + #endif + + if (c89atomic_compare_and_swap_32(&pAllocator->pGroups[iGroup].bitfield, oldBitfield, newBitfield) == oldBitfield) { + c89atomic_fetch_sub_32(&pAllocator->count, 1); + return MA_SUCCESS; + } + } + + /* Getting here means there are no allocations available for freeing. */ + return MA_INVALID_OPERATION; +} + + /************************************************************************************************************************************************************** @@ -28052,33 +29061,6 @@ static MA_INLINE void ma_pcm_sample_s32_to_s24_no_scale(ma_int64 x, ma_uint8* s2 } -static MA_INLINE ma_uint8 ma_clip_u8(ma_int16 x) -{ - return (ma_uint8)(ma_clamp(x, -128, 127) + 128); -} - -static MA_INLINE ma_int16 ma_clip_s16(ma_int32 x) -{ - return (ma_int16)ma_clamp(x, -32768, 32767); -} - -static MA_INLINE ma_int64 ma_clip_s24(ma_int64 x) -{ - return (ma_int64)ma_clamp(x, -8388608, 8388607); -} - -static MA_INLINE ma_int32 ma_clip_s32(ma_int64 x) -{ - /* This dance is to silence warnings with -std=c89. A good compiler should be able to optimize this away. */ - ma_int64 clipMin; - ma_int64 clipMax; - clipMin = -((ma_int64)2147483647 + 1); - clipMax = (ma_int64)2147483647; - - return (ma_int32)ma_clamp(x, clipMin, clipMax); -} - - /* u8 */ MA_API void ma_pcm_u8_to_u8(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) { @@ -30457,25 +31439,131 @@ MA_API ma_biquad_config ma_biquad_config_init(ma_format format, ma_uint32 channe return config; } -MA_API ma_result ma_biquad_init(const ma_biquad_config* pConfig, ma_biquad* pBQ) + +typedef struct { + size_t sizeInBytes; + size_t r1Offset; + size_t r2Offset; +} ma_biquad_heap_layout; + +static ma_result ma_biquad_get_heap_layout(const ma_biquad_config* pConfig, ma_biquad_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channels == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* R0 */ + pHeapLayout->r1Offset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(ma_biquad_coefficient) * pConfig->channels; + + /* R1 */ + pHeapLayout->r2Offset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(ma_biquad_coefficient) * pConfig->channels; + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_biquad_get_heap_size(const ma_biquad_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_biquad_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_biquad_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_biquad_init_preallocated(const ma_biquad_config* pConfig, void* pHeap, ma_biquad* pBQ) +{ + ma_result result; + ma_biquad_heap_layout heapLayout; + if (pBQ == NULL) { return MA_INVALID_ARGS; } MA_ZERO_OBJECT(pBQ); - if (pConfig == NULL) { - return MA_INVALID_ARGS; + result = ma_biquad_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; } - if (pConfig->channels < MA_MIN_CHANNELS || pConfig->channels > MA_MAX_CHANNELS) { - return MA_INVALID_ARGS; - } + pBQ->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pBQ->pR1 = (ma_biquad_coefficient*)ma_offset_ptr(pHeap, heapLayout.r1Offset); + pBQ->pR2 = (ma_biquad_coefficient*)ma_offset_ptr(pHeap, heapLayout.r2Offset); return ma_biquad_reinit(pConfig, pBQ); } +MA_API ma_result ma_biquad_init(const ma_biquad_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_biquad* pBQ) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_biquad_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_biquad_init_preallocated(pConfig, pHeap, pBQ); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pBQ->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_biquad_uninit(ma_biquad* pBQ, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pBQ == NULL) { + return; + } + + if (pBQ->_ownsHeap) { + ma_free(pBQ->_pHeap, pAllocationCallbacks); + } +} + MA_API ma_result ma_biquad_reinit(const ma_biquad_config* pConfig, ma_biquad* pBQ) { if (pBQ == NULL || pConfig == NULL) { @@ -30533,10 +31621,10 @@ static MA_INLINE void ma_biquad_process_pcm_frame_f32__direct_form_2_transposed( const float a1 = pBQ->a1.f32; const float a2 = pBQ->a2.f32; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { - float r1 = pBQ->r1[c].f32; - float r2 = pBQ->r2[c].f32; + float r1 = pBQ->pR1[c].f32; + float r2 = pBQ->pR2[c].f32; float x = pX[c]; float y; @@ -30544,9 +31632,9 @@ static MA_INLINE void ma_biquad_process_pcm_frame_f32__direct_form_2_transposed( r1 = b1*x - a1*y + r2; r2 = b2*x - a2*y; - pY[c] = y; - pBQ->r1[c].f32 = r1; - pBQ->r2[c].f32 = r2; + pY[c] = y; + pBQ->pR1[c].f32 = r1; + pBQ->pR2[c].f32 = r2; } } @@ -30565,10 +31653,10 @@ static MA_INLINE void ma_biquad_process_pcm_frame_s16__direct_form_2_transposed( const ma_int32 a1 = pBQ->a1.s32; const ma_int32 a2 = pBQ->a2.s32; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { - ma_int32 r1 = pBQ->r1[c].s32; - ma_int32 r2 = pBQ->r2[c].s32; + ma_int32 r1 = pBQ->pR1[c].s32; + ma_int32 r2 = pBQ->pR2[c].s32; ma_int32 x = pX[c]; ma_int32 y; @@ -30576,9 +31664,9 @@ static MA_INLINE void ma_biquad_process_pcm_frame_s16__direct_form_2_transposed( r1 = (b1*x - a1*y + r2); r2 = (b2*x - a2*y); - pY[c] = (ma_int16)ma_clamp(y, -32768, 32767); - pBQ->r1[c].s32 = r1; - pBQ->r2[c].s32 = r2; + pY[c] = (ma_int16)ma_clamp(y, -32768, 32767); + pBQ->pR1[c].s32 = r1; + pBQ->pR2[c].s32 = r2; } } @@ -30672,25 +31760,122 @@ MA_API ma_lpf2_config ma_lpf2_config_init(ma_format format, ma_uint32 channels, } -MA_API ma_result ma_lpf1_init(const ma_lpf1_config* pConfig, ma_lpf1* pLPF) +typedef struct { + size_t sizeInBytes; + size_t r1Offset; +} ma_lpf1_heap_layout; + +static ma_result ma_lpf1_get_heap_layout(const ma_lpf1_config* pConfig, ma_lpf1_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channels == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* R1 */ + pHeapLayout->r1Offset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(ma_biquad_coefficient) * pConfig->channels; + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_lpf1_get_heap_size(const ma_lpf1_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_lpf1_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_lpf1_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_lpf1_init_preallocated(const ma_lpf1_config* pConfig, void* pHeap, ma_lpf1* pLPF) +{ + ma_result result; + ma_lpf1_heap_layout heapLayout; + if (pLPF == NULL) { return MA_INVALID_ARGS; } MA_ZERO_OBJECT(pLPF); - if (pConfig == NULL) { - return MA_INVALID_ARGS; + result = ma_lpf1_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; } - if (pConfig->channels < MA_MIN_CHANNELS || pConfig->channels > MA_MAX_CHANNELS) { - return MA_INVALID_ARGS; - } + pLPF->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pLPF->pR1 = (ma_biquad_coefficient*)ma_offset_ptr(pHeap, heapLayout.r1Offset); return ma_lpf1_reinit(pConfig, pLPF); } +MA_API ma_result ma_lpf1_init(const ma_lpf1_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf1* pLPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_lpf1_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_lpf1_init_preallocated(pConfig, pHeap, pLPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pLPF->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_lpf1_uninit(ma_lpf1* pLPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pLPF == NULL) { + return; + } + + if (pLPF->_ownsHeap) { + ma_free(pLPF->_pHeap, pAllocationCallbacks); + } +} + MA_API ma_result ma_lpf1_reinit(const ma_lpf1_config* pConfig, ma_lpf1* pLPF) { double a; @@ -30734,16 +31919,16 @@ static MA_INLINE void ma_lpf1_process_pcm_frame_f32(ma_lpf1* pLPF, float* pY, co const float a = pLPF->a.f32; const float b = 1 - a; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { - float r1 = pLPF->r1[c].f32; + float r1 = pLPF->pR1[c].f32; float x = pX[c]; float y; y = b*x + a*r1; pY[c] = y; - pLPF->r1[c].f32 = y; + pLPF->pR1[c].f32 = y; } } @@ -30754,16 +31939,16 @@ static MA_INLINE void ma_lpf1_process_pcm_frame_s16(ma_lpf1* pLPF, ma_int16* pY, const ma_int32 a = pLPF->a.s32; const ma_int32 b = ((1 << MA_BIQUAD_FIXED_POINT_SHIFT) - a); - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { - ma_int32 r1 = pLPF->r1[c].s32; + ma_int32 r1 = pLPF->pR1[c].s32; ma_int32 x = pX[c]; ma_int32 y; y = (b*x + a*r1) >> MA_BIQUAD_FIXED_POINT_SHIFT; - pY[c] = (ma_int16)y; - pLPF->r1[c].s32 = (ma_int32)y; + pY[c] = (ma_int16)y; + pLPF->pR1[c].s32 = (ma_int32)y; } } @@ -30843,7 +32028,15 @@ static MA_INLINE ma_biquad_config ma_lpf2__get_biquad_config(const ma_lpf2_confi return bqConfig; } -MA_API ma_result ma_lpf2_init(const ma_lpf2_config* pConfig, ma_lpf2* pLPF) +MA_API ma_result ma_lpf2_get_heap_size(const ma_lpf2_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_biquad_config bqConfig; + bqConfig = ma_lpf2__get_biquad_config(pConfig); + + return ma_biquad_get_heap_size(&bqConfig, pHeapSizeInBytes); +} + +MA_API ma_result ma_lpf2_init_preallocated(const ma_lpf2_config* pConfig, void* pHeap, ma_lpf2* pLPF) { ma_result result; ma_biquad_config bqConfig; @@ -30859,7 +32052,7 @@ MA_API ma_result ma_lpf2_init(const ma_lpf2_config* pConfig, ma_lpf2* pLPF) } bqConfig = ma_lpf2__get_biquad_config(pConfig); - result = ma_biquad_init(&bqConfig, &pLPF->bq); + result = ma_biquad_init_preallocated(&bqConfig, pHeap, &pLPF->bq); if (result != MA_SUCCESS) { return result; } @@ -30867,6 +32060,45 @@ MA_API ma_result ma_lpf2_init(const ma_lpf2_config* pConfig, ma_lpf2* pLPF) return MA_SUCCESS; } +MA_API ma_result ma_lpf2_init(const ma_lpf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf2* pLPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_lpf2_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_lpf2_init_preallocated(pConfig, pHeap, pLPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pLPF->bq._ownsHeap = MA_TRUE; /* <-- This will cause the biquad to take ownership of the heap and free it when it's uninitialized. */ + return MA_SUCCESS; +} + +MA_API void ma_lpf2_uninit(ma_lpf2* pLPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pLPF == NULL) { + return; + } + + ma_biquad_uninit(&pLPF->bq, pAllocationCallbacks); /* <-- This will free the heap allocation. */ +} + MA_API ma_result ma_lpf2_reinit(const ma_lpf2_config* pConfig, ma_lpf2* pLPF) { ma_result result; @@ -30928,7 +32160,24 @@ MA_API ma_lpf_config ma_lpf_config_init(ma_format format, ma_uint32 channels, ma return config; } -static ma_result ma_lpf_reinit__internal(const ma_lpf_config* pConfig, ma_lpf* pLPF, ma_bool32 isNew) + +typedef struct +{ + size_t sizeInBytes; + size_t lpf1Offset; + size_t lpf2Offset; /* Offset of the first second order filter. Subsequent filters will come straight after, and will each have the same heap size. */ +} ma_lpf_heap_layout; + +static void ma_lpf_calculate_sub_lpf_counts(ma_uint32 order, ma_uint32* pLPF1Count, ma_uint32* pLPF2Count) +{ + MA_ASSERT(pLPF1Count != NULL); + MA_ASSERT(pLPF2Count != NULL); + + *pLPF1Count = order % 2; + *pLPF2Count = order / 2; +} + +static ma_result ma_lpf_get_heap_layout(const ma_lpf_config* pConfig, ma_lpf_heap_layout* pHeapLayout) { ma_result result; ma_uint32 lpf1Count; @@ -30936,6 +32185,69 @@ static ma_result ma_lpf_reinit__internal(const ma_lpf_config* pConfig, ma_lpf* p ma_uint32 ilpf1; ma_uint32 ilpf2; + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channels == 0) { + return MA_INVALID_ARGS; + } + + if (pConfig->order > MA_MAX_FILTER_ORDER) { + return MA_INVALID_ARGS; + } + + ma_lpf_calculate_sub_lpf_counts(pConfig->order, &lpf1Count, &lpf2Count); + + pHeapLayout->sizeInBytes = 0; + + /* LPF 1 */ + pHeapLayout->lpf1Offset = pHeapLayout->sizeInBytes; + for (ilpf1 = 0; ilpf1 < lpf1Count; ilpf1 += 1) { + size_t lpf1HeapSizeInBytes; + ma_lpf1_config lpf1Config = ma_lpf1_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency); + + result = ma_lpf1_get_heap_size(&lpf1Config, &lpf1HeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += sizeof(ma_lpf1) + lpf1HeapSizeInBytes; + } + + /* LPF 2*/ + pHeapLayout->lpf2Offset = pHeapLayout->sizeInBytes; + for (ilpf2 = 0; ilpf2 < lpf2Count; ilpf2 += 1) { + size_t lpf2HeapSizeInBytes; + ma_lpf2_config lpf2Config = ma_lpf2_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency, 0.707107); /* <-- The "q" parameter does not matter for the purpose of calculating the heap size. */ + + result = ma_lpf2_get_heap_size(&lpf2Config, &lpf2HeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += sizeof(ma_lpf2) + lpf2HeapSizeInBytes; + } + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +static ma_result ma_lpf_reinit__internal(const ma_lpf_config* pConfig, void* pHeap, ma_lpf* pLPF, ma_bool32 isNew) +{ + ma_result result; + ma_uint32 lpf1Count; + ma_uint32 lpf2Count; + ma_uint32 ilpf1; + ma_uint32 ilpf2; + ma_lpf_heap_layout heapLayout; /* Only used if isNew is true. */ + if (pLPF == NULL || pConfig == NULL) { return MA_INVALID_ARGS; } @@ -30959,11 +32271,7 @@ static ma_result ma_lpf_reinit__internal(const ma_lpf_config* pConfig, ma_lpf* p return MA_INVALID_ARGS; } - lpf1Count = pConfig->order % 2; - lpf2Count = pConfig->order / 2; - - MA_ASSERT(lpf1Count <= ma_countof(pLPF->lpf1)); - MA_ASSERT(lpf2Count <= ma_countof(pLPF->lpf2)); + ma_lpf_calculate_sub_lpf_counts(pConfig->order, &lpf1Count, &lpf2Count); /* The filter order can't change between reinits. */ if (!isNew) { @@ -30972,16 +32280,42 @@ static ma_result ma_lpf_reinit__internal(const ma_lpf_config* pConfig, ma_lpf* p } } + if (isNew) { + result = ma_lpf_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pLPF->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pLPF->pLPF1 = (ma_lpf1*)ma_offset_ptr(pHeap, heapLayout.lpf1Offset); + pLPF->pLPF2 = (ma_lpf2*)ma_offset_ptr(pHeap, heapLayout.lpf2Offset); + } else { + MA_ZERO_OBJECT(&heapLayout); /* To silence a compiler warning. */ + } + for (ilpf1 = 0; ilpf1 < lpf1Count; ilpf1 += 1) { ma_lpf1_config lpf1Config = ma_lpf1_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency); if (isNew) { - result = ma_lpf1_init(&lpf1Config, &pLPF->lpf1[ilpf1]); + size_t lpf1HeapSizeInBytes; + + result = ma_lpf1_get_heap_size(&lpf1Config, &lpf1HeapSizeInBytes); + if (result == MA_SUCCESS) { + result = ma_lpf1_init_preallocated(&lpf1Config, ma_offset_ptr(pHeap, heapLayout.lpf1Offset + (sizeof(ma_lpf1) * lpf1Count) + (ilpf1 * lpf1HeapSizeInBytes)), &pLPF->pLPF1[ilpf1]); + } } else { - result = ma_lpf1_reinit(&lpf1Config, &pLPF->lpf1[ilpf1]); + result = ma_lpf1_reinit(&lpf1Config, &pLPF->pLPF1[ilpf1]); } if (result != MA_SUCCESS) { + ma_uint32 jlpf1; + + for (jlpf1 = 0; jlpf1 < ilpf1; jlpf1 += 1) { + ma_lpf1_uninit(&pLPF->pLPF1[jlpf1], NULL); /* No need for allocation callbacks here since we used a preallocated heap allocation. */ + } + return result; } } @@ -31002,12 +32336,28 @@ static ma_result ma_lpf_reinit__internal(const ma_lpf_config* pConfig, ma_lpf* p lpf2Config = ma_lpf2_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency, q); if (isNew) { - result = ma_lpf2_init(&lpf2Config, &pLPF->lpf2[ilpf2]); + size_t lpf2HeapSizeInBytes; + + result = ma_lpf2_get_heap_size(&lpf2Config, &lpf2HeapSizeInBytes); + if (result == MA_SUCCESS) { + result = ma_lpf2_init_preallocated(&lpf2Config, ma_offset_ptr(pHeap, heapLayout.lpf2Offset + (sizeof(ma_lpf2) * lpf2Count) + (ilpf2 * lpf2HeapSizeInBytes)), &pLPF->pLPF2[ilpf2]); + } } else { - result = ma_lpf2_reinit(&lpf2Config, &pLPF->lpf2[ilpf2]); + result = ma_lpf2_reinit(&lpf2Config, &pLPF->pLPF2[ilpf2]); } if (result != MA_SUCCESS) { + ma_uint32 jlpf1; + ma_uint32 jlpf2; + + for (jlpf1 = 0; jlpf1 < lpf1Count; jlpf1 += 1) { + ma_lpf1_uninit(&pLPF->pLPF1[jlpf1], NULL); /* No need for allocation callbacks here since we used a preallocated heap allocation. */ + } + + for (jlpf2 = 0; jlpf2 < ilpf2; jlpf2 += 1) { + ma_lpf2_uninit(&pLPF->pLPF2[jlpf2], NULL); /* No need for allocation callbacks here since we used a preallocated heap allocation. */ + } + return result; } } @@ -31021,7 +32371,28 @@ static ma_result ma_lpf_reinit__internal(const ma_lpf_config* pConfig, ma_lpf* p return MA_SUCCESS; } -MA_API ma_result ma_lpf_init(const ma_lpf_config* pConfig, ma_lpf* pLPF) +MA_API ma_result ma_lpf_get_heap_size(const ma_lpf_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_lpf_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_lpf_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return result; +} + +MA_API ma_result ma_lpf_init_preallocated(const ma_lpf_config* pConfig, void* pHeap, ma_lpf* pLPF) { if (pLPF == NULL) { return MA_INVALID_ARGS; @@ -31029,16 +32400,60 @@ MA_API ma_result ma_lpf_init(const ma_lpf_config* pConfig, ma_lpf* pLPF) MA_ZERO_OBJECT(pLPF); - if (pConfig == NULL) { - return MA_INVALID_ARGS; + return ma_lpf_reinit__internal(pConfig, pHeap, pLPF, /*isNew*/MA_TRUE); +} + +MA_API ma_result ma_lpf_init(const ma_lpf_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf* pLPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_lpf_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; } - return ma_lpf_reinit__internal(pConfig, pLPF, /*isNew*/MA_TRUE); + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_lpf_init_preallocated(pConfig, pHeap, pLPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pLPF->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_lpf_uninit(ma_lpf* pLPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_uint32 ilpf1; + ma_uint32 ilpf2; + + if (pLPF == NULL) { + return; + } + + for (ilpf1 = 0; ilpf1 < pLPF->lpf1Count; ilpf1 += 1) { + ma_lpf1_uninit(&pLPF->pLPF1[ilpf1], pAllocationCallbacks); + } + + for (ilpf2 = 0; ilpf2 < pLPF->lpf2Count; ilpf2 += 1) { + ma_lpf2_uninit(&pLPF->pLPF2[ilpf2], pAllocationCallbacks); + } } MA_API ma_result ma_lpf_reinit(const ma_lpf_config* pConfig, ma_lpf* pLPF) { - return ma_lpf_reinit__internal(pConfig, pLPF, /*isNew*/MA_FALSE); + return ma_lpf_reinit__internal(pConfig, NULL, pLPF, /*isNew*/MA_FALSE); } static MA_INLINE void ma_lpf_process_pcm_frame_f32(ma_lpf* pLPF, float* pY, const void* pX) @@ -31051,11 +32466,11 @@ static MA_INLINE void ma_lpf_process_pcm_frame_f32(ma_lpf* pLPF, float* pY, cons MA_COPY_MEMORY(pY, pX, ma_get_bytes_per_frame(pLPF->format, pLPF->channels)); for (ilpf1 = 0; ilpf1 < pLPF->lpf1Count; ilpf1 += 1) { - ma_lpf1_process_pcm_frame_f32(&pLPF->lpf1[ilpf1], pY, pY); + ma_lpf1_process_pcm_frame_f32(&pLPF->pLPF1[ilpf1], pY, pY); } for (ilpf2 = 0; ilpf2 < pLPF->lpf2Count; ilpf2 += 1) { - ma_lpf2_process_pcm_frame_f32(&pLPF->lpf2[ilpf2], pY, pY); + ma_lpf2_process_pcm_frame_f32(&pLPF->pLPF2[ilpf2], pY, pY); } } @@ -31069,11 +32484,11 @@ static MA_INLINE void ma_lpf_process_pcm_frame_s16(ma_lpf* pLPF, ma_int16* pY, c MA_COPY_MEMORY(pY, pX, ma_get_bytes_per_frame(pLPF->format, pLPF->channels)); for (ilpf1 = 0; ilpf1 < pLPF->lpf1Count; ilpf1 += 1) { - ma_lpf1_process_pcm_frame_s16(&pLPF->lpf1[ilpf1], pY, pY); + ma_lpf1_process_pcm_frame_s16(&pLPF->pLPF1[ilpf1], pY, pY); } for (ilpf2 = 0; ilpf2 < pLPF->lpf2Count; ilpf2 += 1) { - ma_lpf2_process_pcm_frame_s16(&pLPF->lpf2[ilpf2], pY, pY); + ma_lpf2_process_pcm_frame_s16(&pLPF->pLPF2[ilpf2], pY, pY); } } @@ -31090,14 +32505,14 @@ MA_API ma_result ma_lpf_process_pcm_frames(ma_lpf* pLPF, void* pFramesOut, const /* Faster path for in-place. */ if (pFramesOut == pFramesIn) { for (ilpf1 = 0; ilpf1 < pLPF->lpf1Count; ilpf1 += 1) { - result = ma_lpf1_process_pcm_frames(&pLPF->lpf1[ilpf1], pFramesOut, pFramesOut, frameCount); + result = ma_lpf1_process_pcm_frames(&pLPF->pLPF1[ilpf1], pFramesOut, pFramesOut, frameCount); if (result != MA_SUCCESS) { return result; } } for (ilpf2 = 0; ilpf2 < pLPF->lpf2Count; ilpf2 += 1) { - result = ma_lpf2_process_pcm_frames(&pLPF->lpf2[ilpf2], pFramesOut, pFramesOut, frameCount); + result = ma_lpf2_process_pcm_frames(&pLPF->pLPF2[ilpf2], pFramesOut, pFramesOut, frameCount); if (result != MA_SUCCESS) { return result; } @@ -31183,23 +32598,120 @@ MA_API ma_hpf2_config ma_hpf2_config_init(ma_format format, ma_uint32 channels, } -MA_API ma_result ma_hpf1_init(const ma_hpf1_config* pConfig, ma_hpf1* pHPF) +typedef struct { - if (pHPF == NULL) { - return MA_INVALID_ARGS; - } + size_t sizeInBytes; + size_t r1Offset; +} ma_hpf1_heap_layout; - MA_ZERO_OBJECT(pHPF); +static ma_result ma_hpf1_get_heap_layout(const ma_hpf1_config* pConfig, ma_hpf1_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); if (pConfig == NULL) { return MA_INVALID_ARGS; } - if (pConfig->channels < MA_MIN_CHANNELS || pConfig->channels > MA_MAX_CHANNELS) { + if (pConfig->channels == 0) { return MA_INVALID_ARGS; } - return ma_hpf1_reinit(pConfig, pHPF); + pHeapLayout->sizeInBytes = 0; + + /* R1 */ + pHeapLayout->r1Offset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(ma_biquad_coefficient) * pConfig->channels; + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_hpf1_get_heap_size(const ma_hpf1_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_hpf1_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_hpf1_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_hpf1_init_preallocated(const ma_hpf1_config* pConfig, void* pHeap, ma_hpf1* pLPF) +{ + ma_result result; + ma_hpf1_heap_layout heapLayout; + + if (pLPF == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pLPF); + + result = ma_hpf1_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pLPF->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pLPF->pR1 = (ma_biquad_coefficient*)ma_offset_ptr(pHeap, heapLayout.r1Offset); + + return ma_hpf1_reinit(pConfig, pLPF); +} + +MA_API ma_result ma_hpf1_init(const ma_hpf1_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf1* pLPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_hpf1_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_hpf1_init_preallocated(pConfig, pHeap, pLPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pLPF->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_hpf1_uninit(ma_hpf1* pHPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pHPF == NULL) { + return; + } + + if (pHPF->_ownsHeap) { + ma_free(pHPF->_pHeap, pAllocationCallbacks); + } } MA_API ma_result ma_hpf1_reinit(const ma_hpf1_config* pConfig, ma_hpf1* pHPF) @@ -31245,16 +32757,16 @@ static MA_INLINE void ma_hpf1_process_pcm_frame_f32(ma_hpf1* pHPF, float* pY, co const float a = 1 - pHPF->a.f32; const float b = 1 - a; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { - float r1 = pHPF->r1[c].f32; + float r1 = pHPF->pR1[c].f32; float x = pX[c]; float y; y = b*x - a*r1; - pY[c] = y; - pHPF->r1[c].f32 = y; + pY[c] = y; + pHPF->pR1[c].f32 = y; } } @@ -31265,16 +32777,16 @@ static MA_INLINE void ma_hpf1_process_pcm_frame_s16(ma_hpf1* pHPF, ma_int16* pY, const ma_int32 a = ((1 << MA_BIQUAD_FIXED_POINT_SHIFT) - pHPF->a.s32); const ma_int32 b = ((1 << MA_BIQUAD_FIXED_POINT_SHIFT) - a); - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { - ma_int32 r1 = pHPF->r1[c].s32; + ma_int32 r1 = pHPF->pR1[c].s32; ma_int32 x = pX[c]; ma_int32 y; y = (b*x - a*r1) >> MA_BIQUAD_FIXED_POINT_SHIFT; - pY[c] = (ma_int16)y; - pHPF->r1[c].s32 = (ma_int32)y; + pY[c] = (ma_int16)y; + pHPF->pR1[c].s32 = (ma_int32)y; } } @@ -31354,7 +32866,15 @@ static MA_INLINE ma_biquad_config ma_hpf2__get_biquad_config(const ma_hpf2_confi return bqConfig; } -MA_API ma_result ma_hpf2_init(const ma_hpf2_config* pConfig, ma_hpf2* pHPF) +MA_API ma_result ma_hpf2_get_heap_size(const ma_hpf2_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_biquad_config bqConfig; + bqConfig = ma_hpf2__get_biquad_config(pConfig); + + return ma_biquad_get_heap_size(&bqConfig, pHeapSizeInBytes); +} + +MA_API ma_result ma_hpf2_init_preallocated(const ma_hpf2_config* pConfig, void* pHeap, ma_hpf2* pHPF) { ma_result result; ma_biquad_config bqConfig; @@ -31370,7 +32890,7 @@ MA_API ma_result ma_hpf2_init(const ma_hpf2_config* pConfig, ma_hpf2* pHPF) } bqConfig = ma_hpf2__get_biquad_config(pConfig); - result = ma_biquad_init(&bqConfig, &pHPF->bq); + result = ma_biquad_init_preallocated(&bqConfig, pHeap, &pHPF->bq); if (result != MA_SUCCESS) { return result; } @@ -31378,6 +32898,45 @@ MA_API ma_result ma_hpf2_init(const ma_hpf2_config* pConfig, ma_hpf2* pHPF) return MA_SUCCESS; } +MA_API ma_result ma_hpf2_init(const ma_hpf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf2* pHPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_hpf2_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_hpf2_init_preallocated(pConfig, pHeap, pHPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pHPF->bq._ownsHeap = MA_TRUE; /* <-- This will cause the biquad to take ownership of the heap and free it when it's uninitialized. */ + return MA_SUCCESS; +} + +MA_API void ma_hpf2_uninit(ma_hpf2* pHPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pHPF == NULL) { + return; + } + + ma_biquad_uninit(&pHPF->bq, pAllocationCallbacks); /* <-- This will free the heap allocation. */ +} + MA_API ma_result ma_hpf2_reinit(const ma_hpf2_config* pConfig, ma_hpf2* pHPF) { ma_result result; @@ -31439,7 +32998,24 @@ MA_API ma_hpf_config ma_hpf_config_init(ma_format format, ma_uint32 channels, ma return config; } -static ma_result ma_hpf_reinit__internal(const ma_hpf_config* pConfig, ma_hpf* pHPF, ma_bool32 isNew) + +typedef struct +{ + size_t sizeInBytes; + size_t hpf1Offset; + size_t hpf2Offset; /* Offset of the first second order filter. Subsequent filters will come straight after, and will each have the same heap size. */ +} ma_hpf_heap_layout; + +static void ma_hpf_calculate_sub_hpf_counts(ma_uint32 order, ma_uint32* pHPF1Count, ma_uint32* pHPF2Count) +{ + MA_ASSERT(pHPF1Count != NULL); + MA_ASSERT(pHPF2Count != NULL); + + *pHPF1Count = order % 2; + *pHPF2Count = order / 2; +} + +static ma_result ma_hpf_get_heap_layout(const ma_hpf_config* pConfig, ma_hpf_heap_layout* pHeapLayout) { ma_result result; ma_uint32 hpf1Count; @@ -31447,6 +33023,69 @@ static ma_result ma_hpf_reinit__internal(const ma_hpf_config* pConfig, ma_hpf* p ma_uint32 ihpf1; ma_uint32 ihpf2; + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channels == 0) { + return MA_INVALID_ARGS; + } + + if (pConfig->order > MA_MAX_FILTER_ORDER) { + return MA_INVALID_ARGS; + } + + ma_hpf_calculate_sub_hpf_counts(pConfig->order, &hpf1Count, &hpf2Count); + + pHeapLayout->sizeInBytes = 0; + + /* LPF 1 */ + pHeapLayout->hpf1Offset = pHeapLayout->sizeInBytes; + for (ihpf1 = 0; ihpf1 < hpf1Count; ihpf1 += 1) { + size_t hpf1HeapSizeInBytes; + ma_hpf1_config hpf1Config = ma_hpf1_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency); + + result = ma_hpf1_get_heap_size(&hpf1Config, &hpf1HeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += sizeof(ma_hpf1) + hpf1HeapSizeInBytes; + } + + /* LPF 2*/ + pHeapLayout->hpf2Offset = pHeapLayout->sizeInBytes; + for (ihpf2 = 0; ihpf2 < hpf2Count; ihpf2 += 1) { + size_t hpf2HeapSizeInBytes; + ma_hpf2_config hpf2Config = ma_hpf2_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency, 0.707107); /* <-- The "q" parameter does not matter for the purpose of calculating the heap size. */ + + result = ma_hpf2_get_heap_size(&hpf2Config, &hpf2HeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += sizeof(ma_hpf2) + hpf2HeapSizeInBytes; + } + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +static ma_result ma_hpf_reinit__internal(const ma_hpf_config* pConfig, void* pHeap, ma_hpf* pHPF, ma_bool32 isNew) +{ + ma_result result; + ma_uint32 hpf1Count; + ma_uint32 hpf2Count; + ma_uint32 ihpf1; + ma_uint32 ihpf2; + ma_hpf_heap_layout heapLayout; /* Only used if isNew is true. */ + if (pHPF == NULL || pConfig == NULL) { return MA_INVALID_ARGS; } @@ -31470,11 +33109,7 @@ static ma_result ma_hpf_reinit__internal(const ma_hpf_config* pConfig, ma_hpf* p return MA_INVALID_ARGS; } - hpf1Count = pConfig->order % 2; - hpf2Count = pConfig->order / 2; - - MA_ASSERT(hpf1Count <= ma_countof(pHPF->hpf1)); - MA_ASSERT(hpf2Count <= ma_countof(pHPF->hpf2)); + ma_hpf_calculate_sub_hpf_counts(pConfig->order, &hpf1Count, &hpf2Count); /* The filter order can't change between reinits. */ if (!isNew) { @@ -31483,16 +33118,42 @@ static ma_result ma_hpf_reinit__internal(const ma_hpf_config* pConfig, ma_hpf* p } } + if (isNew) { + result = ma_hpf_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pHPF->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pHPF->pHPF1 = (ma_hpf1*)ma_offset_ptr(pHeap, heapLayout.hpf1Offset); + pHPF->pHPF2 = (ma_hpf2*)ma_offset_ptr(pHeap, heapLayout.hpf2Offset); + } else { + MA_ZERO_OBJECT(&heapLayout); /* To silence a compiler warning. */ + } + for (ihpf1 = 0; ihpf1 < hpf1Count; ihpf1 += 1) { ma_hpf1_config hpf1Config = ma_hpf1_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency); if (isNew) { - result = ma_hpf1_init(&hpf1Config, &pHPF->hpf1[ihpf1]); + size_t hpf1HeapSizeInBytes; + + result = ma_hpf1_get_heap_size(&hpf1Config, &hpf1HeapSizeInBytes); + if (result == MA_SUCCESS) { + result = ma_hpf1_init_preallocated(&hpf1Config, ma_offset_ptr(pHeap, heapLayout.hpf1Offset + (ihpf1 * (sizeof(ma_hpf1) + hpf1HeapSizeInBytes)) + sizeof(ma_hpf1)), &pHPF->pHPF1[ihpf1]); + } } else { - result = ma_hpf1_reinit(&hpf1Config, &pHPF->hpf1[ihpf1]); + result = ma_hpf1_reinit(&hpf1Config, &pHPF->pHPF1[ihpf1]); } if (result != MA_SUCCESS) { + ma_uint32 jhpf1; + + for (jhpf1 = 0; jhpf1 < ihpf1; jhpf1 += 1) { + ma_hpf1_uninit(&pHPF->pHPF1[jhpf1], NULL); /* No need for allocation callbacks here since we used a preallocated heap allocation. */ + } + return result; } } @@ -31513,12 +33174,28 @@ static ma_result ma_hpf_reinit__internal(const ma_hpf_config* pConfig, ma_hpf* p hpf2Config = ma_hpf2_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency, q); if (isNew) { - result = ma_hpf2_init(&hpf2Config, &pHPF->hpf2[ihpf2]); + size_t hpf2HeapSizeInBytes; + + result = ma_hpf2_get_heap_size(&hpf2Config, &hpf2HeapSizeInBytes); + if (result == MA_SUCCESS) { + result = ma_hpf2_init_preallocated(&hpf2Config, ma_offset_ptr(pHeap, heapLayout.hpf2Offset + (ihpf2 * (sizeof(ma_hpf2) + hpf2HeapSizeInBytes)) + sizeof(ma_hpf2)), &pHPF->pHPF2[ihpf2]); + } } else { - result = ma_hpf2_reinit(&hpf2Config, &pHPF->hpf2[ihpf2]); + result = ma_hpf2_reinit(&hpf2Config, &pHPF->pHPF2[ihpf2]); } if (result != MA_SUCCESS) { + ma_uint32 jhpf1; + ma_uint32 jhpf2; + + for (jhpf1 = 0; jhpf1 < hpf1Count; jhpf1 += 1) { + ma_hpf1_uninit(&pHPF->pHPF1[jhpf1], NULL); /* No need for allocation callbacks here since we used a preallocated heap allocation. */ + } + + for (jhpf2 = 0; jhpf2 < ihpf2; jhpf2 += 1) { + ma_hpf2_uninit(&pHPF->pHPF2[jhpf2], NULL); /* No need for allocation callbacks here since we used a preallocated heap allocation. */ + } + return result; } } @@ -31532,24 +33209,89 @@ static ma_result ma_hpf_reinit__internal(const ma_hpf_config* pConfig, ma_hpf* p return MA_SUCCESS; } -MA_API ma_result ma_hpf_init(const ma_hpf_config* pConfig, ma_hpf* pHPF) +MA_API ma_result ma_hpf_get_heap_size(const ma_hpf_config* pConfig, size_t* pHeapSizeInBytes) { + ma_result result; + ma_hpf_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_hpf_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return result; +} + +MA_API ma_result ma_hpf_init_preallocated(const ma_hpf_config* pConfig, void* pHeap, ma_hpf* pLPF) +{ + if (pLPF == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pLPF); + + return ma_hpf_reinit__internal(pConfig, pHeap, pLPF, /*isNew*/MA_TRUE); +} + +MA_API ma_result ma_hpf_init(const ma_hpf_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf* pHPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_hpf_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_hpf_init_preallocated(pConfig, pHeap, pHPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pHPF->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_hpf_uninit(ma_hpf* pHPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_uint32 ihpf1; + ma_uint32 ihpf2; + if (pHPF == NULL) { - return MA_INVALID_ARGS; + return; } - MA_ZERO_OBJECT(pHPF); - - if (pConfig == NULL) { - return MA_INVALID_ARGS; + for (ihpf1 = 0; ihpf1 < pHPF->hpf1Count; ihpf1 += 1) { + ma_hpf1_uninit(&pHPF->pHPF1[ihpf1], pAllocationCallbacks); } - return ma_hpf_reinit__internal(pConfig, pHPF, /*isNew*/MA_TRUE); + for (ihpf2 = 0; ihpf2 < pHPF->hpf2Count; ihpf2 += 1) { + ma_hpf2_uninit(&pHPF->pHPF2[ihpf2], pAllocationCallbacks); + } } MA_API ma_result ma_hpf_reinit(const ma_hpf_config* pConfig, ma_hpf* pHPF) { - return ma_hpf_reinit__internal(pConfig, pHPF, /*isNew*/MA_FALSE); + return ma_hpf_reinit__internal(pConfig, NULL, pHPF, /*isNew*/MA_FALSE); } MA_API ma_result ma_hpf_process_pcm_frames(ma_hpf* pHPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) @@ -31565,14 +33307,14 @@ MA_API ma_result ma_hpf_process_pcm_frames(ma_hpf* pHPF, void* pFramesOut, const /* Faster path for in-place. */ if (pFramesOut == pFramesIn) { for (ihpf1 = 0; ihpf1 < pHPF->hpf1Count; ihpf1 += 1) { - result = ma_hpf1_process_pcm_frames(&pHPF->hpf1[ihpf1], pFramesOut, pFramesOut, frameCount); + result = ma_hpf1_process_pcm_frames(&pHPF->pHPF1[ihpf1], pFramesOut, pFramesOut, frameCount); if (result != MA_SUCCESS) { return result; } } for (ihpf2 = 0; ihpf2 < pHPF->hpf2Count; ihpf2 += 1) { - result = ma_hpf2_process_pcm_frames(&pHPF->hpf2[ihpf2], pFramesOut, pFramesOut, frameCount); + result = ma_hpf2_process_pcm_frames(&pHPF->pHPF2[ihpf2], pFramesOut, pFramesOut, frameCount); if (result != MA_SUCCESS) { return result; } @@ -31591,11 +33333,11 @@ MA_API ma_result ma_hpf_process_pcm_frames(ma_hpf* pHPF, void* pFramesOut, const MA_COPY_MEMORY(pFramesOutF32, pFramesInF32, ma_get_bytes_per_frame(pHPF->format, pHPF->channels)); for (ihpf1 = 0; ihpf1 < pHPF->hpf1Count; ihpf1 += 1) { - ma_hpf1_process_pcm_frame_f32(&pHPF->hpf1[ihpf1], pFramesOutF32, pFramesOutF32); + ma_hpf1_process_pcm_frame_f32(&pHPF->pHPF1[ihpf1], pFramesOutF32, pFramesOutF32); } for (ihpf2 = 0; ihpf2 < pHPF->hpf2Count; ihpf2 += 1) { - ma_hpf2_process_pcm_frame_f32(&pHPF->hpf2[ihpf2], pFramesOutF32, pFramesOutF32); + ma_hpf2_process_pcm_frame_f32(&pHPF->pHPF2[ihpf2], pFramesOutF32, pFramesOutF32); } pFramesOutF32 += pHPF->channels; @@ -31609,11 +33351,11 @@ MA_API ma_result ma_hpf_process_pcm_frames(ma_hpf* pHPF, void* pFramesOut, const MA_COPY_MEMORY(pFramesOutS16, pFramesInS16, ma_get_bytes_per_frame(pHPF->format, pHPF->channels)); for (ihpf1 = 0; ihpf1 < pHPF->hpf1Count; ihpf1 += 1) { - ma_hpf1_process_pcm_frame_s16(&pHPF->hpf1[ihpf1], pFramesOutS16, pFramesOutS16); + ma_hpf1_process_pcm_frame_s16(&pHPF->pHPF1[ihpf1], pFramesOutS16, pFramesOutS16); } for (ihpf2 = 0; ihpf2 < pHPF->hpf2Count; ihpf2 += 1) { - ma_hpf2_process_pcm_frame_s16(&pHPF->hpf2[ihpf2], pFramesOutS16, pFramesOutS16); + ma_hpf2_process_pcm_frame_s16(&pHPF->pHPF2[ihpf2], pFramesOutS16, pFramesOutS16); } pFramesOutS16 += pHPF->channels; @@ -31693,7 +33435,15 @@ static MA_INLINE ma_biquad_config ma_bpf2__get_biquad_config(const ma_bpf2_confi return bqConfig; } -MA_API ma_result ma_bpf2_init(const ma_bpf2_config* pConfig, ma_bpf2* pBPF) +MA_API ma_result ma_bpf2_get_heap_size(const ma_bpf2_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_biquad_config bqConfig; + bqConfig = ma_bpf2__get_biquad_config(pConfig); + + return ma_biquad_get_heap_size(&bqConfig, pHeapSizeInBytes); +} + +MA_API ma_result ma_bpf2_init_preallocated(const ma_bpf2_config* pConfig, void* pHeap, ma_bpf2* pBPF) { ma_result result; ma_biquad_config bqConfig; @@ -31709,7 +33459,7 @@ MA_API ma_result ma_bpf2_init(const ma_bpf2_config* pConfig, ma_bpf2* pBPF) } bqConfig = ma_bpf2__get_biquad_config(pConfig); - result = ma_biquad_init(&bqConfig, &pBPF->bq); + result = ma_biquad_init_preallocated(&bqConfig, pHeap, &pBPF->bq); if (result != MA_SUCCESS) { return result; } @@ -31717,6 +33467,45 @@ MA_API ma_result ma_bpf2_init(const ma_bpf2_config* pConfig, ma_bpf2* pBPF) return MA_SUCCESS; } +MA_API ma_result ma_bpf2_init(const ma_bpf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_bpf2* pBPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_bpf2_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_bpf2_init_preallocated(pConfig, pHeap, pBPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pBPF->bq._ownsHeap = MA_TRUE; /* <-- This will cause the biquad to take ownership of the heap and free it when it's uninitialized. */ + return MA_SUCCESS; +} + +MA_API void ma_bpf2_uninit(ma_bpf2* pBPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pBPF == NULL) { + return; + } + + ma_biquad_uninit(&pBPF->bq, pAllocationCallbacks); /* <-- This will free the heap allocation. */ +} + MA_API ma_result ma_bpf2_reinit(const ma_bpf2_config* pConfig, ma_bpf2* pBPF) { ma_result result; @@ -31778,12 +33567,67 @@ MA_API ma_bpf_config ma_bpf_config_init(ma_format format, ma_uint32 channels, ma return config; } -static ma_result ma_bpf_reinit__internal(const ma_bpf_config* pConfig, ma_bpf* pBPF, ma_bool32 isNew) + +typedef struct +{ + size_t sizeInBytes; + size_t bpf2Offset; +} ma_bpf_heap_layout; + +static ma_result ma_bpf_get_heap_layout(const ma_bpf_config* pConfig, ma_bpf_heap_layout* pHeapLayout) { ma_result result; ma_uint32 bpf2Count; ma_uint32 ibpf2; + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->order > MA_MAX_FILTER_ORDER) { + return MA_INVALID_ARGS; + } + + /* We must have an even number of order. */ + if ((pConfig->order & 0x1) != 0) { + return MA_INVALID_ARGS; + } + + bpf2Count = pConfig->channels / 2; + + pHeapLayout->sizeInBytes = 0; + + /* BPF 2 */ + pHeapLayout->bpf2Offset = pHeapLayout->sizeInBytes; + for (ibpf2 = 0; ibpf2 < bpf2Count; ibpf2 += 1) { + size_t bpf2HeapSizeInBytes; + ma_bpf2_config bpf2Config = ma_bpf2_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency, 0.707107); /* <-- The "q" parameter does not matter for the purpose of calculating the heap size. */ + + result = ma_bpf2_get_heap_size(&bpf2Config, &bpf2HeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += sizeof(ma_bpf2) + bpf2HeapSizeInBytes; + } + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +static ma_result ma_bpf_reinit__internal(const ma_bpf_config* pConfig, void* pHeap, ma_bpf* pBPF, ma_bool32 isNew) +{ + ma_result result; + ma_uint32 bpf2Count; + ma_uint32 ibpf2; + ma_bpf_heap_layout heapLayout; /* Only used if isNew is true. */ + if (pBPF == NULL || pConfig == NULL) { return MA_INVALID_ARGS; } @@ -31814,8 +33658,6 @@ static ma_result ma_bpf_reinit__internal(const ma_bpf_config* pConfig, ma_bpf* p bpf2Count = pConfig->order / 2; - MA_ASSERT(bpf2Count <= ma_countof(pBPF->bpf2)); - /* The filter order can't change between reinits. */ if (!isNew) { if (pBPF->bpf2Count != bpf2Count) { @@ -31823,6 +33665,20 @@ static ma_result ma_bpf_reinit__internal(const ma_bpf_config* pConfig, ma_bpf* p } } + if (isNew) { + result = ma_bpf_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pBPF->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pBPF->pBPF2 = (ma_bpf2*)ma_offset_ptr(pHeap, heapLayout.bpf2Offset); + } else { + MA_ZERO_OBJECT(&heapLayout); + } + for (ibpf2 = 0; ibpf2 < bpf2Count; ibpf2 += 1) { ma_bpf2_config bpf2Config; double q; @@ -31833,9 +33689,14 @@ static ma_result ma_bpf_reinit__internal(const ma_bpf_config* pConfig, ma_bpf* p bpf2Config = ma_bpf2_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency, q); if (isNew) { - result = ma_bpf2_init(&bpf2Config, &pBPF->bpf2[ibpf2]); + size_t bpf2HeapSizeInBytes; + + result = ma_bpf2_get_heap_size(&bpf2Config, &bpf2HeapSizeInBytes); + if (result == MA_SUCCESS) { + result = ma_bpf2_init_preallocated(&bpf2Config, ma_offset_ptr(pHeap, heapLayout.bpf2Offset + (ibpf2 * (sizeof(ma_bpf2) + bpf2HeapSizeInBytes)) + sizeof(ma_bpf2)), &pBPF->pBPF2[ibpf2]); + } } else { - result = ma_bpf2_reinit(&bpf2Config, &pBPF->bpf2[ibpf2]); + result = ma_bpf2_reinit(&bpf2Config, &pBPF->pBPF2[ibpf2]); } if (result != MA_SUCCESS) { @@ -31850,7 +33711,29 @@ static ma_result ma_bpf_reinit__internal(const ma_bpf_config* pConfig, ma_bpf* p return MA_SUCCESS; } -MA_API ma_result ma_bpf_init(const ma_bpf_config* pConfig, ma_bpf* pBPF) + +MA_API ma_result ma_bpf_get_heap_size(const ma_bpf_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_bpf_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_bpf_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_bpf_init_preallocated(const ma_bpf_config* pConfig, void* pHeap, ma_bpf* pBPF) { if (pBPF == NULL) { return MA_INVALID_ARGS; @@ -31858,16 +33741,55 @@ MA_API ma_result ma_bpf_init(const ma_bpf_config* pConfig, ma_bpf* pBPF) MA_ZERO_OBJECT(pBPF); - if (pConfig == NULL) { - return MA_INVALID_ARGS; + return ma_bpf_reinit__internal(pConfig, pHeap, pBPF, /*isNew*/MA_TRUE); +} + +MA_API ma_result ma_bpf_init(const ma_bpf_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_bpf* pBPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_bpf_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; } - return ma_bpf_reinit__internal(pConfig, pBPF, /*isNew*/MA_TRUE); + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_bpf_init_preallocated(pConfig, pHeap, pBPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pBPF->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_bpf_uninit(ma_bpf* pBPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_uint32 ibpf2; + + if (pBPF == NULL) { + return; + } + + for (ibpf2 = 0; ibpf2 < pBPF->bpf2Count; ibpf2 += 1) { + ma_bpf2_uninit(&pBPF->pBPF2[ibpf2], pAllocationCallbacks); + } } MA_API ma_result ma_bpf_reinit(const ma_bpf_config* pConfig, ma_bpf* pBPF) { - return ma_bpf_reinit__internal(pConfig, pBPF, /*isNew*/MA_FALSE); + return ma_bpf_reinit__internal(pConfig, NULL, pBPF, /*isNew*/MA_FALSE); } MA_API ma_result ma_bpf_process_pcm_frames(ma_bpf* pBPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) @@ -31882,7 +33804,7 @@ MA_API ma_result ma_bpf_process_pcm_frames(ma_bpf* pBPF, void* pFramesOut, const /* Faster path for in-place. */ if (pFramesOut == pFramesIn) { for (ibpf2 = 0; ibpf2 < pBPF->bpf2Count; ibpf2 += 1) { - result = ma_bpf2_process_pcm_frames(&pBPF->bpf2[ibpf2], pFramesOut, pFramesOut, frameCount); + result = ma_bpf2_process_pcm_frames(&pBPF->pBPF2[ibpf2], pFramesOut, pFramesOut, frameCount); if (result != MA_SUCCESS) { return result; } @@ -31901,7 +33823,7 @@ MA_API ma_result ma_bpf_process_pcm_frames(ma_bpf* pBPF, void* pFramesOut, const MA_COPY_MEMORY(pFramesOutF32, pFramesInF32, ma_get_bytes_per_frame(pBPF->format, pBPF->channels)); for (ibpf2 = 0; ibpf2 < pBPF->bpf2Count; ibpf2 += 1) { - ma_bpf2_process_pcm_frame_f32(&pBPF->bpf2[ibpf2], pFramesOutF32, pFramesOutF32); + ma_bpf2_process_pcm_frame_f32(&pBPF->pBPF2[ibpf2], pFramesOutF32, pFramesOutF32); } pFramesOutF32 += pBPF->channels; @@ -31915,7 +33837,7 @@ MA_API ma_result ma_bpf_process_pcm_frames(ma_bpf* pBPF, void* pFramesOut, const MA_COPY_MEMORY(pFramesOutS16, pFramesInS16, ma_get_bytes_per_frame(pBPF->format, pBPF->channels)); for (ibpf2 = 0; ibpf2 < pBPF->bpf2Count; ibpf2 += 1) { - ma_bpf2_process_pcm_frame_s16(&pBPF->bpf2[ibpf2], pFramesOutS16, pFramesOutS16); + ma_bpf2_process_pcm_frame_s16(&pBPF->pBPF2[ibpf2], pFramesOutS16, pFramesOutS16); } pFramesOutS16 += pBPF->channels; @@ -31994,7 +33916,15 @@ static MA_INLINE ma_biquad_config ma_notch2__get_biquad_config(const ma_notch2_c return bqConfig; } -MA_API ma_result ma_notch2_init(const ma_notch2_config* pConfig, ma_notch2* pFilter) +MA_API ma_result ma_notch2_get_heap_size(const ma_notch2_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_biquad_config bqConfig; + bqConfig = ma_notch2__get_biquad_config(pConfig); + + return ma_biquad_get_heap_size(&bqConfig, pHeapSizeInBytes); +} + +MA_API ma_result ma_notch2_init_preallocated(const ma_notch2_config* pConfig, void* pHeap, ma_notch2* pFilter) { ma_result result; ma_biquad_config bqConfig; @@ -32010,7 +33940,7 @@ MA_API ma_result ma_notch2_init(const ma_notch2_config* pConfig, ma_notch2* pFil } bqConfig = ma_notch2__get_biquad_config(pConfig); - result = ma_biquad_init(&bqConfig, &pFilter->bq); + result = ma_biquad_init_preallocated(&bqConfig, pHeap, &pFilter->bq); if (result != MA_SUCCESS) { return result; } @@ -32018,6 +33948,45 @@ MA_API ma_result ma_notch2_init(const ma_notch2_config* pConfig, ma_notch2* pFil return MA_SUCCESS; } +MA_API ma_result ma_notch2_init(const ma_notch2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_notch2* pFilter) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_notch2_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_notch2_init_preallocated(pConfig, pHeap, pFilter); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pFilter->bq._ownsHeap = MA_TRUE; /* <-- This will cause the biquad to take ownership of the heap and free it when it's uninitialized. */ + return MA_SUCCESS; +} + +MA_API void ma_notch2_uninit(ma_notch2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pFilter == NULL) { + return; + } + + ma_biquad_uninit(&pFilter->bq, pAllocationCallbacks); /* <-- This will free the heap allocation. */ +} + MA_API ma_result ma_notch2_reinit(const ma_notch2_config* pConfig, ma_notch2* pFilter) { ma_result result; @@ -32123,7 +34092,15 @@ static MA_INLINE ma_biquad_config ma_peak2__get_biquad_config(const ma_peak2_con return bqConfig; } -MA_API ma_result ma_peak2_init(const ma_peak2_config* pConfig, ma_peak2* pFilter) +MA_API ma_result ma_peak2_get_heap_size(const ma_peak2_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_biquad_config bqConfig; + bqConfig = ma_peak2__get_biquad_config(pConfig); + + return ma_biquad_get_heap_size(&bqConfig, pHeapSizeInBytes); +} + +MA_API ma_result ma_peak2_init_preallocated(const ma_peak2_config* pConfig, void* pHeap, ma_peak2* pFilter) { ma_result result; ma_biquad_config bqConfig; @@ -32139,7 +34116,7 @@ MA_API ma_result ma_peak2_init(const ma_peak2_config* pConfig, ma_peak2* pFilter } bqConfig = ma_peak2__get_biquad_config(pConfig); - result = ma_biquad_init(&bqConfig, &pFilter->bq); + result = ma_biquad_init_preallocated(&bqConfig, pHeap, &pFilter->bq); if (result != MA_SUCCESS) { return result; } @@ -32147,6 +34124,45 @@ MA_API ma_result ma_peak2_init(const ma_peak2_config* pConfig, ma_peak2* pFilter return MA_SUCCESS; } +MA_API ma_result ma_peak2_init(const ma_peak2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_peak2* pFilter) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_peak2_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_peak2_init_preallocated(pConfig, pHeap, pFilter); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pFilter->bq._ownsHeap = MA_TRUE; /* <-- This will cause the biquad to take ownership of the heap and free it when it's uninitialized. */ + return MA_SUCCESS; +} + +MA_API void ma_peak2_uninit(ma_peak2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pFilter == NULL) { + return; + } + + ma_biquad_uninit(&pFilter->bq, pAllocationCallbacks); /* <-- This will free the heap allocation. */ +} + MA_API ma_result ma_peak2_reinit(const ma_peak2_config* pConfig, ma_peak2* pFilter) { ma_result result; @@ -32249,7 +34265,15 @@ static MA_INLINE ma_biquad_config ma_loshelf2__get_biquad_config(const ma_loshel return bqConfig; } -MA_API ma_result ma_loshelf2_init(const ma_loshelf2_config* pConfig, ma_loshelf2* pFilter) +MA_API ma_result ma_loshelf2_get_heap_size(const ma_loshelf2_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_biquad_config bqConfig; + bqConfig = ma_loshelf2__get_biquad_config(pConfig); + + return ma_biquad_get_heap_size(&bqConfig, pHeapSizeInBytes); +} + +MA_API ma_result ma_loshelf2_init_preallocated(const ma_loshelf2_config* pConfig, void* pHeap, ma_loshelf2* pFilter) { ma_result result; ma_biquad_config bqConfig; @@ -32265,7 +34289,7 @@ MA_API ma_result ma_loshelf2_init(const ma_loshelf2_config* pConfig, ma_loshelf2 } bqConfig = ma_loshelf2__get_biquad_config(pConfig); - result = ma_biquad_init(&bqConfig, &pFilter->bq); + result = ma_biquad_init_preallocated(&bqConfig, pHeap, &pFilter->bq); if (result != MA_SUCCESS) { return result; } @@ -32273,6 +34297,45 @@ MA_API ma_result ma_loshelf2_init(const ma_loshelf2_config* pConfig, ma_loshelf2 return MA_SUCCESS; } +MA_API ma_result ma_loshelf2_init(const ma_loshelf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_loshelf2* pFilter) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_loshelf2_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_loshelf2_init_preallocated(pConfig, pHeap, pFilter); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pFilter->bq._ownsHeap = MA_TRUE; /* <-- This will cause the biquad to take ownership of the heap and free it when it's uninitialized. */ + return MA_SUCCESS; +} + +MA_API void ma_loshelf2_uninit(ma_loshelf2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pFilter == NULL) { + return; + } + + ma_biquad_uninit(&pFilter->bq, pAllocationCallbacks); /* <-- This will free the heap allocation. */ +} + MA_API ma_result ma_loshelf2_reinit(const ma_loshelf2_config* pConfig, ma_loshelf2* pFilter) { ma_result result; @@ -32375,7 +34438,15 @@ static MA_INLINE ma_biquad_config ma_hishelf2__get_biquad_config(const ma_hishel return bqConfig; } -MA_API ma_result ma_hishelf2_init(const ma_hishelf2_config* pConfig, ma_hishelf2* pFilter) +MA_API ma_result ma_hishelf2_get_heap_size(const ma_hishelf2_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_biquad_config bqConfig; + bqConfig = ma_hishelf2__get_biquad_config(pConfig); + + return ma_biquad_get_heap_size(&bqConfig, pHeapSizeInBytes); +} + +MA_API ma_result ma_hishelf2_init_preallocated(const ma_hishelf2_config* pConfig, void* pHeap, ma_hishelf2* pFilter) { ma_result result; ma_biquad_config bqConfig; @@ -32391,7 +34462,7 @@ MA_API ma_result ma_hishelf2_init(const ma_hishelf2_config* pConfig, ma_hishelf2 } bqConfig = ma_hishelf2__get_biquad_config(pConfig); - result = ma_biquad_init(&bqConfig, &pFilter->bq); + result = ma_biquad_init_preallocated(&bqConfig, pHeap, &pFilter->bq); if (result != MA_SUCCESS) { return result; } @@ -32399,6 +34470,45 @@ MA_API ma_result ma_hishelf2_init(const ma_hishelf2_config* pConfig, ma_hishelf2 return MA_SUCCESS; } +MA_API ma_result ma_hishelf2_init(const ma_hishelf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hishelf2* pFilter) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_hishelf2_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_hishelf2_init_preallocated(pConfig, pHeap, pFilter); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pFilter->bq._ownsHeap = MA_TRUE; /* <-- This will cause the biquad to take ownership of the heap and free it when it's uninitialized. */ + return MA_SUCCESS; +} + +MA_API void ma_hishelf2_uninit(ma_hishelf2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pFilter == NULL) { + return; + } + + ma_biquad_uninit(&pFilter->bq, pAllocationCallbacks); /* <-- This will free the heap allocation. */ +} + MA_API ma_result ma_hishelf2_reinit(const ma_hishelf2_config* pConfig, ma_hishelf2* pFilter) { ma_result result; @@ -32447,6 +34557,2208 @@ MA_API ma_uint32 ma_hishelf2_get_latency(const ma_hishelf2* pFilter) +/* +Delay +*/ +MA_API ma_delay_config ma_delay_config_init(ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 delayInFrames, float decay) +{ + ma_delay_config config; + + MA_ZERO_OBJECT(&config); + config.channels = channels; + config.sampleRate = sampleRate; + config.delayInFrames = delayInFrames; + config.delayStart = (decay == 0) ? MA_TRUE : MA_FALSE; /* Delay the start if it looks like we're not configuring an echo. */ + config.wet = 1; + config.dry = 1; + config.decay = decay; + + return config; +} + + +MA_API ma_result ma_delay_init(const ma_delay_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_delay* pDelay) +{ + if (pDelay == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pDelay); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->decay < 0 || pConfig->decay > 1) { + return MA_INVALID_ARGS; + } + + pDelay->config = *pConfig; + pDelay->bufferSizeInFrames = pConfig->delayInFrames; + pDelay->cursor = 0; + + pDelay->pBuffer = (float*)ma_malloc((size_t)(pDelay->bufferSizeInFrames * ma_get_bytes_per_frame(ma_format_f32, pConfig->channels)), pAllocationCallbacks); + if (pDelay->pBuffer == NULL) { + return MA_OUT_OF_MEMORY; + } + + ma_silence_pcm_frames(pDelay->pBuffer, pDelay->bufferSizeInFrames, ma_format_f32, pConfig->channels); + + return MA_SUCCESS; +} + +MA_API void ma_delay_uninit(ma_delay* pDelay, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pDelay == NULL) { + return; + } + + ma_free(pDelay->pBuffer, pAllocationCallbacks); +} + +MA_API ma_result ma_delay_process_pcm_frames(ma_delay* pDelay, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) +{ + ma_uint32 iFrame; + ma_uint32 iChannel; + float* pFramesOutF32 = (float*)pFramesOut; + const float* pFramesInF32 = (const float*)pFramesIn; + + if (pDelay == NULL || pFramesOut == NULL || pFramesIn == NULL) { + return MA_INVALID_ARGS; + } + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannel = 0; iChannel < pDelay->config.channels; iChannel += 1) { + ma_uint32 iBuffer = (pDelay->cursor * pDelay->config.channels) + iChannel; + + if (pDelay->config.delayStart) { + /* Delayed start. */ + + /* Read */ + pFramesOutF32[iChannel] = pDelay->pBuffer[iBuffer] * pDelay->config.wet; + + /* Feedback */ + pDelay->pBuffer[iBuffer] = (pDelay->pBuffer[iBuffer] * pDelay->config.decay) + (pFramesInF32[iChannel] * pDelay->config.dry); + } else { + /* Immediate start */ + + /* Feedback */ + pDelay->pBuffer[iBuffer] = (pDelay->pBuffer[iBuffer] * pDelay->config.decay) + (pFramesInF32[iChannel] * pDelay->config.dry); + + /* Read */ + pFramesOutF32[iChannel] = pDelay->pBuffer[iBuffer] * pDelay->config.wet; + } + } + + pDelay->cursor = (pDelay->cursor + 1) % pDelay->bufferSizeInFrames; + + pFramesOutF32 += pDelay->config.channels; + pFramesInF32 += pDelay->config.channels; + } + + return MA_SUCCESS; +} + +MA_API void ma_delay_set_wet(ma_delay* pDelay, float value) +{ + if (pDelay == NULL) { + return; + } + + pDelay->config.wet = value; +} + +MA_API float ma_delay_get_wet(const ma_delay* pDelay) +{ + if (pDelay == NULL) { + return 0; + } + + return pDelay->config.wet; +} + +MA_API void ma_delay_set_dry(ma_delay* pDelay, float value) +{ + if (pDelay == NULL) { + return; + } + + pDelay->config.dry = value; +} + +MA_API float ma_delay_get_dry(const ma_delay* pDelay) +{ + if (pDelay == NULL) { + return 0; + } + + return pDelay->config.dry; +} + +MA_API void ma_delay_set_decay(ma_delay* pDelay, float value) +{ + if (pDelay == NULL) { + return; + } + + pDelay->config.decay = value; +} + +MA_API float ma_delay_get_decay(const ma_delay* pDelay) +{ + if (pDelay == NULL) { + return 0; + } + + return pDelay->config.decay; +} + + +MA_API ma_gainer_config ma_gainer_config_init(ma_uint32 channels, ma_uint32 smoothTimeInFrames) +{ + ma_gainer_config config; + + MA_ZERO_OBJECT(&config); + config.channels = channels; + config.smoothTimeInFrames = smoothTimeInFrames; + + return config; +} + + +typedef struct +{ + size_t sizeInBytes; + size_t oldGainsOffset; + size_t newGainsOffset; +} ma_gainer_heap_layout; + +static ma_result ma_gainer_get_heap_layout(const ma_gainer_config* pConfig, ma_gainer_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channels == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* Old gains. */ + pHeapLayout->oldGainsOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(float) * pConfig->channels; + + /* New gains. */ + pHeapLayout->newGainsOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(float) * pConfig->channels; + + /* Alignment. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + + +MA_API ma_result ma_gainer_get_heap_size(const ma_gainer_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_gainer_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_gainer_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + + +MA_API ma_result ma_gainer_init_preallocated(const ma_gainer_config* pConfig, void* pHeap, ma_gainer* pGainer) +{ + ma_result result; + ma_gainer_heap_layout heapLayout; + ma_uint32 iChannel; + + if (pGainer == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pGainer); + + if (pConfig == NULL || pHeap == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_gainer_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pGainer->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pGainer->pOldGains = (float*)ma_offset_ptr(pHeap, heapLayout.oldGainsOffset); + pGainer->pNewGains = (float*)ma_offset_ptr(pHeap, heapLayout.newGainsOffset); + + pGainer->config = *pConfig; + pGainer->t = (ma_uint32)-1; /* No interpolation by default. */ + + for (iChannel = 0; iChannel < pConfig->channels; iChannel += 1) { + pGainer->pOldGains[iChannel] = 1; + pGainer->pNewGains[iChannel] = 1; + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_gainer_init(const ma_gainer_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_gainer* pGainer) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_gainer_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the size of the heap allocation. */ + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_gainer_init_preallocated(pConfig, pHeap, pGainer); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pGainer->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_gainer_uninit(ma_gainer* pGainer, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pGainer == NULL) { + return; + } + + if (pGainer->_ownsHeap) { + ma_free(pGainer->_pHeap, pAllocationCallbacks); + } +} + +static float ma_gainer_calculate_current_gain(const ma_gainer* pGainer, ma_uint32 channel) +{ + float a = (float)pGainer->t / pGainer->config.smoothTimeInFrames; + return ma_mix_f32_fast(pGainer->pOldGains[channel], pGainer->pNewGains[channel], a); +} + +MA_API ma_result ma_gainer_process_pcm_frames(ma_gainer* pGainer, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) +{ + ma_uint64 iFrame; + ma_uint32 iChannel; + float* pFramesOutF32 = (float*)pFramesOut; + const float* pFramesInF32 = (const float*)pFramesIn; + + if (pGainer == NULL) { + return MA_INVALID_ARGS; + } + + if (pGainer->t >= pGainer->config.smoothTimeInFrames) { + /* Fast path. No gain calculation required. */ + ma_copy_and_apply_volume_factor_per_channel_f32(pFramesOutF32, pFramesInF32, frameCount, pGainer->config.channels, pGainer->pNewGains); + + /* Now that some frames have been processed we need to make sure future changes to the gain are interpolated. */ + if (pGainer->t == (ma_uint32)-1) { + pGainer->t = pGainer->config.smoothTimeInFrames; + } + } else { + /* Slow path. Need to interpolate the gain for each channel individually. */ + + /* We can allow the input and output buffers to be null in which case we'll just update the internal timer. */ + if (pFramesOut != NULL && pFramesIn != NULL) { + float a = (float)pGainer->t / pGainer->config.smoothTimeInFrames; + float d = 1.0f / pGainer->config.smoothTimeInFrames; + ma_uint32 channelCount = pGainer->config.channels; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannel = 0; iChannel < channelCount; iChannel += 1) { + pFramesOutF32[iChannel] = pFramesInF32[iChannel] * ma_mix_f32_fast(pGainer->pOldGains[iChannel], pGainer->pNewGains[iChannel], a); + } + + pFramesOutF32 += channelCount; + pFramesInF32 += channelCount; + + a += d; + if (a > 1) { + a = 1; + } + } + } + + pGainer->t = (ma_uint32)ma_min(pGainer->t + frameCount, pGainer->config.smoothTimeInFrames); + + #if 0 /* Reference implementation. */ + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + /* We can allow the input and output buffers to be null in which case we'll just update the internal timer. */ + if (pFramesOut != NULL && pFramesIn != NULL) { + for (iChannel = 0; iChannel < pGainer->config.channels; iChannel += 1) { + pFramesOutF32[iFrame*pGainer->config.channels + iChannel] = pFramesInF32[iFrame*pGainer->config.channels + iChannel] * ma_gainer_calculate_current_gain(pGainer, iChannel); + } + } + + /* Move interpolation time forward, but don't go beyond our smoothing time. */ + pGainer->t = ma_min(pGainer->t + 1, pGainer->config.smoothTimeInFrames); + } + #endif + } + + return MA_SUCCESS; +} + +static void ma_gainer_set_gain_by_index(ma_gainer* pGainer, float newGain, ma_uint32 iChannel) +{ + pGainer->pOldGains[iChannel] = ma_gainer_calculate_current_gain(pGainer, iChannel); + pGainer->pNewGains[iChannel] = newGain; +} + +static void ma_gainer_reset_smoothing_time(ma_gainer* pGainer) +{ + if (pGainer->t == (ma_uint32)-1) { + pGainer->t = pGainer->config.smoothTimeInFrames; /* No smoothing required for initial gains setting. */ + } else { + pGainer->t = 0; + } +} + +MA_API ma_result ma_gainer_set_gain(ma_gainer* pGainer, float newGain) +{ + ma_uint32 iChannel; + + if (pGainer == NULL) { + return MA_INVALID_ARGS; + } + + for (iChannel = 0; iChannel < pGainer->config.channels; iChannel += 1) { + ma_gainer_set_gain_by_index(pGainer, newGain, iChannel); + } + + /* The smoothing time needs to be reset to ensure we always interpolate by the configured smoothing time, but only if it's not the first setting. */ + ma_gainer_reset_smoothing_time(pGainer); + + return MA_SUCCESS; +} + +MA_API ma_result ma_gainer_set_gains(ma_gainer* pGainer, float* pNewGains) +{ + ma_uint32 iChannel; + + if (pGainer == NULL || pNewGains == NULL) { + return MA_INVALID_ARGS; + } + + for (iChannel = 0; iChannel < pGainer->config.channels; iChannel += 1) { + ma_gainer_set_gain_by_index(pGainer, pNewGains[iChannel], iChannel); + } + + /* The smoothing time needs to be reset to ensure we always interpolate by the configured smoothing time, but only if it's not the first setting. */ + ma_gainer_reset_smoothing_time(pGainer); + + return MA_SUCCESS; +} + + +MA_API ma_panner_config ma_panner_config_init(ma_format format, ma_uint32 channels) +{ + ma_panner_config config; + + MA_ZERO_OBJECT(&config); + config.format = format; + config.channels = channels; + config.mode = ma_pan_mode_balance; /* Set to balancing mode by default because it's consistent with other audio engines and most likely what the caller is expecting. */ + config.pan = 0; + + return config; +} + + +MA_API ma_result ma_panner_init(const ma_panner_config* pConfig, ma_panner* pPanner) +{ + if (pPanner == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pPanner); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + pPanner->format = pConfig->format; + pPanner->channels = pConfig->channels; + pPanner->mode = pConfig->mode; + pPanner->pan = pConfig->pan; + + return MA_SUCCESS; +} + +static void ma_stereo_balance_pcm_frames_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, float pan) +{ + ma_uint64 iFrame; + + if (pan > 0) { + float factor = 1.0f - pan; + if (pFramesOut == pFramesIn) { + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + pFramesOut[iFrame*2 + 0] = pFramesIn[iFrame*2 + 0] * factor; + } + } else { + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + pFramesOut[iFrame*2 + 0] = pFramesIn[iFrame*2 + 0] * factor; + pFramesOut[iFrame*2 + 1] = pFramesIn[iFrame*2 + 1]; + } + } + } else { + float factor = 1.0f + pan; + if (pFramesOut == pFramesIn) { + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + pFramesOut[iFrame*2 + 1] = pFramesIn[iFrame*2 + 1] * factor; + } + } else { + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + pFramesOut[iFrame*2 + 0] = pFramesIn[iFrame*2 + 0]; + pFramesOut[iFrame*2 + 1] = pFramesIn[iFrame*2 + 1] * factor; + } + } + } +} + +static void ma_stereo_balance_pcm_frames(void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount, ma_format format, float pan) +{ + if (pan == 0) { + /* Fast path. No panning required. */ + if (pFramesOut == pFramesIn) { + /* No-op */ + } else { + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, format, 2); + } + + return; + } + + switch (format) { + case ma_format_f32: ma_stereo_balance_pcm_frames_f32((float*)pFramesOut, (float*)pFramesIn, frameCount, pan); break; + + /* Unknown format. Just copy. */ + default: + { + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, format, 2); + } break; + } +} + + +static void ma_stereo_pan_pcm_frames_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, float pan) +{ + ma_uint64 iFrame; + + if (pan > 0) { + float factorL0 = 1.0f - pan; + float factorL1 = 0.0f + pan; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + float sample0 = (pFramesIn[iFrame*2 + 0] * factorL0); + float sample1 = (pFramesIn[iFrame*2 + 0] * factorL1) + pFramesIn[iFrame*2 + 1]; + + pFramesOut[iFrame*2 + 0] = sample0; + pFramesOut[iFrame*2 + 1] = sample1; + } + } else { + float factorR0 = 0.0f - pan; + float factorR1 = 1.0f + pan; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + float sample0 = pFramesIn[iFrame*2 + 0] + (pFramesIn[iFrame*2 + 1] * factorR0); + float sample1 = (pFramesIn[iFrame*2 + 1] * factorR1); + + pFramesOut[iFrame*2 + 0] = sample0; + pFramesOut[iFrame*2 + 1] = sample1; + } + } +} + +static void ma_stereo_pan_pcm_frames(void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount, ma_format format, float pan) +{ + if (pan == 0) { + /* Fast path. No panning required. */ + if (pFramesOut == pFramesIn) { + /* No-op */ + } else { + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, format, 2); + } + + return; + } + + switch (format) { + case ma_format_f32: ma_stereo_pan_pcm_frames_f32((float*)pFramesOut, (float*)pFramesIn, frameCount, pan); break; + + /* Unknown format. Just copy. */ + default: + { + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, format, 2); + } break; + } +} + +MA_API ma_result ma_panner_process_pcm_frames(ma_panner* pPanner, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) +{ + if (pPanner == NULL || pFramesOut == NULL || pFramesIn == NULL) { + return MA_INVALID_ARGS; + } + + if (pPanner->channels == 2) { + /* Stereo case. For now assume channel 0 is left and channel right is 1, but should probably add support for a channel map. */ + if (pPanner->mode == ma_pan_mode_balance) { + ma_stereo_balance_pcm_frames(pFramesOut, pFramesIn, frameCount, pPanner->format, pPanner->pan); + } else { + ma_stereo_pan_pcm_frames(pFramesOut, pFramesIn, frameCount, pPanner->format, pPanner->pan); + } + } else { + if (pPanner->channels == 1) { + /* Panning has no effect on mono streams. */ + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, pPanner->format, pPanner->channels); + } else { + /* For now we're not going to support non-stereo set ups. Not sure how I want to handle this case just yet. */ + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, pPanner->format, pPanner->channels); + } + } + + return MA_SUCCESS; +} + +MA_API void ma_panner_set_mode(ma_panner* pPanner, ma_pan_mode mode) +{ + if (pPanner == NULL) { + return; + } + + pPanner->mode = mode; +} + +MA_API ma_pan_mode ma_panner_get_mode(const ma_panner* pPanner) +{ + if (pPanner == NULL) { + return ma_pan_mode_balance; + } + + return pPanner->mode; +} + +MA_API void ma_panner_set_pan(ma_panner* pPanner, float pan) +{ + if (pPanner == NULL) { + return; + } + + pPanner->pan = ma_clamp(pan, -1.0f, 1.0f); +} + +MA_API float ma_panner_get_pan(const ma_panner* pPanner) +{ + if (pPanner == NULL) { + return 0; + } + + return pPanner->pan; +} + + + + +MA_API ma_fader_config ma_fader_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate) +{ + ma_fader_config config; + + MA_ZERO_OBJECT(&config); + config.format = format; + config.channels = channels; + config.sampleRate = sampleRate; + + return config; +} + + +MA_API ma_result ma_fader_init(const ma_fader_config* pConfig, ma_fader* pFader) +{ + if (pFader == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pFader); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + /* Only f32 is supported for now. */ + if (pConfig->format != ma_format_f32) { + return MA_INVALID_ARGS; + } + + pFader->config = *pConfig; + pFader->volumeBeg = 1; + pFader->volumeEnd = 1; + pFader->lengthInFrames = 0; + pFader->cursorInFrames = 0; + + return MA_SUCCESS; +} + +MA_API ma_result ma_fader_process_pcm_frames(ma_fader* pFader, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) +{ + if (pFader == NULL) { + return MA_INVALID_ARGS; + } + + /* + For now we need to clamp frameCount so that the cursor never overflows 32-bits. This is required for + the conversion to a float which we use for the linear interpolation. This might be changed later. + */ + if (frameCount + pFader->cursorInFrames > UINT_MAX) { + frameCount = UINT_MAX - pFader->cursorInFrames; + } + + /* Optimized path if volumeBeg and volumeEnd are equal. */ + if (pFader->volumeBeg == pFader->volumeEnd) { + if (pFader->volumeBeg == 1) { + /* Straight copy. */ + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, pFader->config.format, pFader->config.channels); + } else { + /* Copy with volume. */ + ma_copy_and_apply_volume_and_clip_pcm_frames(pFramesOut, pFramesIn, frameCount, pFader->config.format, pFader->config.channels, pFader->volumeEnd); + } + } else { + /* Slower path. Volumes are different, so may need to do an interpolation. */ + if (pFader->cursorInFrames >= pFader->lengthInFrames) { + /* Fast path. We've gone past the end of the fade period so just apply the end volume to all samples. */ + ma_copy_and_apply_volume_and_clip_pcm_frames(pFramesOut, pFramesIn, frameCount, pFader->config.format, pFader->config.channels, pFader->volumeEnd); + } else { + /* Slow path. This is where we do the actual fading. */ + ma_uint64 iFrame; + ma_uint32 iChannel; + + /* For now we only support f32. Support for other formats will be added later. */ + if (pFader->config.format == ma_format_f32) { + const float* pFramesInF32 = (const float*)pFramesIn; + /* */ float* pFramesOutF32 = ( float*)pFramesOut; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + float a = (ma_uint32)ma_min(pFader->cursorInFrames + iFrame, pFader->lengthInFrames) / (float)((ma_uint32)pFader->lengthInFrames); /* Safe cast due to the frameCount clamp at the top of this function. */ + float volume = ma_mix_f32_fast(pFader->volumeBeg, pFader->volumeEnd, a); + + for (iChannel = 0; iChannel < pFader->config.channels; iChannel += 1) { + pFramesOutF32[iFrame*pFader->config.channels + iChannel] = pFramesInF32[iFrame*pFader->config.channels + iChannel] * volume; + } + } + } else { + return MA_NOT_IMPLEMENTED; + } + } + } + + pFader->cursorInFrames += frameCount; + + return MA_SUCCESS; +} + +MA_API void ma_fader_get_data_format(const ma_fader* pFader, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +{ + if (pFader == NULL) { + return; + } + + if (pFormat != NULL) { + *pFormat = pFader->config.format; + } + + if (pChannels != NULL) { + *pChannels = pFader->config.channels; + } + + if (pSampleRate != NULL) { + *pSampleRate = pFader->config.sampleRate; + } +} + +MA_API void ma_fader_set_fade(ma_fader* pFader, float volumeBeg, float volumeEnd, ma_uint64 lengthInFrames) +{ + if (pFader == NULL) { + return; + } + + /* If the volume is negative, use current volume. */ + if (volumeBeg < 0) { + volumeBeg = ma_fader_get_current_volume(pFader); + } + + /* + The length needs to be clamped to 32-bits due to how we convert it to a float for linear + interpolation reasons. I might change this requirement later, but for now it's not important. + */ + if (lengthInFrames > UINT_MAX) { + lengthInFrames = UINT_MAX; + } + + pFader->volumeBeg = volumeBeg; + pFader->volumeEnd = volumeEnd; + pFader->lengthInFrames = lengthInFrames; + pFader->cursorInFrames = 0; /* Reset cursor. */ +} + +MA_API float ma_fader_get_current_volume(ma_fader* pFader) +{ + if (pFader == NULL) { + return 0.0f; + } + + /* The current volume depends on the position of the cursor. */ + if (pFader->cursorInFrames == 0) { + return pFader->volumeBeg; + } else if (pFader->cursorInFrames >= pFader->lengthInFrames) { + return pFader->volumeEnd; + } else { + /* The cursor is somewhere inside the fading period. We can figure this out with a simple linear interpoluation between volumeBeg and volumeEnd based on our cursor position. */ + return ma_mix_f32_fast(pFader->volumeBeg, pFader->volumeEnd, (ma_uint32)pFader->cursorInFrames / (float)((ma_uint32)pFader->lengthInFrames)); /* Safe cast to uint32 because we clamp it in ma_fader_process_pcm_frames(). */ + } +} + + + + + +MA_API ma_vec3f ma_vec3f_init_3f(float x, float y, float z) +{ + ma_vec3f v; + + v.x = x; + v.y = y; + v.z = z; + + return v; +} + +MA_API ma_vec3f ma_vec3f_sub(ma_vec3f a, ma_vec3f b) +{ + return ma_vec3f_init_3f( + a.x - b.x, + a.y - b.y, + a.z - b.z + ); +} + +MA_API ma_vec3f ma_vec3f_neg(ma_vec3f a) +{ + return ma_vec3f_init_3f( + -a.x, + -a.y, + -a.z + ); +} + +MA_API float ma_vec3f_dot(ma_vec3f a, ma_vec3f b) +{ + return a.x*b.x + a.y*b.y + a.z*b.z; +} + +MA_API float ma_vec3f_len2(ma_vec3f v) +{ + return ma_vec3f_dot(v, v); +} + +MA_API float ma_vec3f_len(ma_vec3f v) +{ + return (float)ma_sqrtd(ma_vec3f_len2(v)); +} + +MA_API float ma_vec3f_dist(ma_vec3f a, ma_vec3f b) +{ + return ma_vec3f_len(ma_vec3f_sub(a, b)); +} + +MA_API ma_vec3f ma_vec3f_normalize(ma_vec3f v) +{ + float f; + float l = ma_vec3f_len(v); + if (l == 0) { + return ma_vec3f_init_3f(0, 0, 0); + } + + f = 1 / l; + v.x *= f; + v.y *= f; + v.z *= f; + + return v; +} + +MA_API ma_vec3f ma_vec3f_cross(ma_vec3f a, ma_vec3f b) +{ + return ma_vec3f_init_3f( + a.y*b.z - a.z*b.y, + a.z*b.x - a.x*b.z, + a.x*b.y - a.y*b.x + ); +} + + + +static void ma_channel_map_apply_f32(float* pFramesOut, const ma_channel* pChannelMapOut, ma_uint32 channelsOut, const float* pFramesIn, const ma_channel* pChannelMapIn, ma_uint32 channelsIn, ma_uint64 frameCount, ma_channel_mix_mode mode, ma_mono_expansion_mode monoExpansionMode); +static ma_bool32 ma_is_spatial_channel_position(ma_channel channelPosition); + + +#ifndef MA_DEFAULT_SPEED_OF_SOUND +#define MA_DEFAULT_SPEED_OF_SOUND 343.3f +#endif + +/* +These vectors represent the direction that speakers are facing from the center point. They're used +for panning in the spatializer. Must be normalized. +*/ +static ma_vec3f g_maChannelDirections[MA_CHANNEL_POSITION_COUNT] = { + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_NONE */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_MONO */ + {-0.7071f, 0.0f, -0.7071f }, /* MA_CHANNEL_FRONT_LEFT */ + {+0.7071f, 0.0f, -0.7071f }, /* MA_CHANNEL_FRONT_RIGHT */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_FRONT_CENTER */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_LFE */ + {-0.7071f, 0.0f, +0.7071f }, /* MA_CHANNEL_BACK_LEFT */ + {+0.7071f, 0.0f, +0.7071f }, /* MA_CHANNEL_BACK_RIGHT */ + {-0.3162f, 0.0f, -0.9487f }, /* MA_CHANNEL_FRONT_LEFT_CENTER */ + {+0.3162f, 0.0f, -0.9487f }, /* MA_CHANNEL_FRONT_RIGHT_CENTER */ + { 0.0f, 0.0f, +1.0f }, /* MA_CHANNEL_BACK_CENTER */ + {-1.0f, 0.0f, 0.0f }, /* MA_CHANNEL_SIDE_LEFT */ + {+1.0f, 0.0f, 0.0f }, /* MA_CHANNEL_SIDE_RIGHT */ + { 0.0f, +1.0f, 0.0f }, /* MA_CHANNEL_TOP_CENTER */ + {-0.5774f, +0.5774f, -0.5774f }, /* MA_CHANNEL_TOP_FRONT_LEFT */ + { 0.0f, +0.7071f, -0.7071f }, /* MA_CHANNEL_TOP_FRONT_CENTER */ + {+0.5774f, +0.5774f, -0.5774f }, /* MA_CHANNEL_TOP_FRONT_RIGHT */ + {-0.5774f, +0.5774f, +0.5774f }, /* MA_CHANNEL_TOP_BACK_LEFT */ + { 0.0f, +0.7071f, +0.7071f }, /* MA_CHANNEL_TOP_BACK_CENTER */ + {+0.5774f, +0.5774f, +0.5774f }, /* MA_CHANNEL_TOP_BACK_RIGHT */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_0 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_1 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_2 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_3 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_4 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_5 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_6 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_7 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_8 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_9 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_10 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_11 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_12 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_13 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_14 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_15 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_16 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_17 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_18 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_19 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_20 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_21 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_22 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_23 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_24 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_25 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_26 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_27 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_28 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_29 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_30 */ + { 0.0f, 0.0f, -1.0f } /* MA_CHANNEL_AUX_31 */ +}; + +static ma_vec3f ma_get_channel_direction(ma_channel channel) +{ + if (channel >= MA_CHANNEL_POSITION_COUNT) { + return ma_vec3f_init_3f(0, 0, -1); + } else { + return g_maChannelDirections[channel]; + } +} + + + +static float ma_attenuation_inverse(float distance, float minDistance, float maxDistance, float rolloff) +{ + if (minDistance >= maxDistance) { + return 1; /* To avoid division by zero. Do not attenuate. */ + } + + return minDistance / (minDistance + rolloff * (ma_clamp(distance, minDistance, maxDistance) - minDistance)); +} + +static float ma_attenuation_linear(float distance, float minDistance, float maxDistance, float rolloff) +{ + if (minDistance >= maxDistance) { + return 1; /* To avoid division by zero. Do not attenuate. */ + } + + return 1 - rolloff * (ma_clamp(distance, minDistance, maxDistance) - minDistance) / (maxDistance - minDistance); +} + +static float ma_attenuation_exponential(float distance, float minDistance, float maxDistance, float rolloff) +{ + if (minDistance >= maxDistance) { + return 1; /* To avoid division by zero. Do not attenuate. */ + } + + return (float)ma_powd(ma_clamp(distance, minDistance, maxDistance) / minDistance, -rolloff); +} + + +/* +Dopper Effect calculation taken from the OpenAL spec, with two main differences: + + 1) The source to listener vector will have already been calcualted at an earlier step so we can + just use that directly. We need only the position of the source relative to the origin. + + 2) We don't scale by a frequency because we actually just want the ratio which we'll plug straight + into the resampler directly. +*/ +static float ma_doppler_pitch(ma_vec3f relativePosition, ma_vec3f sourceVelocity, ma_vec3f listenVelocity, float speedOfSound, float dopplerFactor) +{ + float len; + float vls; + float vss; + + len = ma_vec3f_len(relativePosition); + + /* + There's a case where the position of the source will be right on top of the listener in which + case the length will be 0 and we'll end up with a division by zero. We can just return a ratio + of 1.0 in this case. This is not considered in the OpenAL spec, but is necessary. + */ + if (len == 0) { + return 1.0; + } + + vls = ma_vec3f_dot(relativePosition, listenVelocity) / len; + vss = ma_vec3f_dot(relativePosition, sourceVelocity) / len; + + vls = ma_min(vls, speedOfSound / dopplerFactor); + vss = ma_min(vss, speedOfSound / dopplerFactor); + + return (speedOfSound - dopplerFactor*vls) / (speedOfSound - dopplerFactor*vss); +} + + +static void ma_get_default_channel_map_for_spatializer(ma_channel* pChannelMap, size_t channelMapCap, ma_uint32 channelCount) +{ + /* + Special case for stereo. Want to default the left and right speakers to side left and side + right so that they're facing directly down the X axis rather than slightly forward. Not + doing this will result in sounds being quieter when behind the listener. This might + actually be good for some scenerios, but I don't think it's an appropriate default because + it can be a bit unexpected. + */ + if (channelCount == 2) { + pChannelMap[0] = MA_CHANNEL_SIDE_LEFT; + pChannelMap[1] = MA_CHANNEL_SIDE_RIGHT; + } else { + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, channelCount); + } +} + + +MA_API ma_spatializer_listener_config ma_spatializer_listener_config_init(ma_uint32 channelsOut) +{ + ma_spatializer_listener_config config; + + MA_ZERO_OBJECT(&config); + config.channelsOut = channelsOut; + config.pChannelMapOut = NULL; + config.handedness = ma_handedness_right; + config.worldUp = ma_vec3f_init_3f(0, 1, 0); + config.coneInnerAngleInRadians = 6.283185f; /* 360 degrees. */ + config.coneOuterAngleInRadians = 6.283185f; /* 360 degrees. */ + config.coneOuterGain = 0; + config.speedOfSound = 343.3f; /* Same as OpenAL. Used for doppler effect. */ + + return config; +} + + +typedef struct +{ + size_t sizeInBytes; + size_t channelMapOutOffset; +} ma_spatializer_listener_heap_layout; + +static ma_result ma_spatializer_listener_get_heap_layout(const ma_spatializer_listener_config* pConfig, ma_spatializer_listener_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channelsOut == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* Channel map. We always need this, even for passthroughs. */ + pHeapLayout->channelMapOutOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(sizeof(*pConfig->pChannelMapOut) * pConfig->channelsOut); + + return MA_SUCCESS; +} + + +MA_API ma_result ma_spatializer_listener_get_heap_size(const ma_spatializer_listener_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_spatializer_listener_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_spatializer_listener_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_spatializer_listener_init_preallocated(const ma_spatializer_listener_config* pConfig, void* pHeap, ma_spatializer_listener* pListener) +{ + ma_result result; + ma_spatializer_listener_heap_layout heapLayout; + + if (pListener == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pListener); + + result = ma_spatializer_listener_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pListener->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pListener->config = *pConfig; + pListener->position = ma_vec3f_init_3f(0, 0, 0); + pListener->direction = ma_vec3f_init_3f(0, 0, -1); + pListener->velocity = ma_vec3f_init_3f(0, 0, 0); + pListener->isEnabled = MA_TRUE; + + /* Swap the forward direction if we're left handed (it was initialized based on right handed). */ + if (pListener->config.handedness == ma_handedness_left) { + pListener->direction = ma_vec3f_neg(pListener->direction); + } + + + /* We must always have a valid channel map. */ + pListener->config.pChannelMapOut = (ma_channel*)ma_offset_ptr(pHeap, heapLayout.channelMapOutOffset); + + /* Use a slightly different default channel map for stereo. */ + if (pConfig->pChannelMapOut == NULL) { + ma_get_default_channel_map_for_spatializer(pListener->config.pChannelMapOut, pConfig->channelsOut, pConfig->channelsOut); + } else { + ma_channel_map_copy_or_default(pListener->config.pChannelMapOut, pConfig->channelsOut, pConfig->pChannelMapOut, pConfig->channelsOut); + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_spatializer_listener_init(const ma_spatializer_listener_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_spatializer_listener* pListener) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_spatializer_listener_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_spatializer_listener_init_preallocated(pConfig, pHeap, pListener); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pListener->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_spatializer_listener_uninit(ma_spatializer_listener* pListener, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pListener == NULL) { + return; + } + + if (pListener->_ownsHeap) { + ma_free(pListener->_pHeap, pAllocationCallbacks); + } +} + +MA_API ma_channel* ma_spatializer_listener_get_channel_map(ma_spatializer_listener* pListener) +{ + if (pListener == NULL) { + return NULL; + } + + return pListener->config.pChannelMapOut; +} + +MA_API void ma_spatializer_listener_set_cone(ma_spatializer_listener* pListener, float innerAngleInRadians, float outerAngleInRadians, float outerGain) +{ + if (pListener == NULL) { + return; + } + + pListener->config.coneInnerAngleInRadians = innerAngleInRadians; + pListener->config.coneOuterAngleInRadians = outerAngleInRadians; + pListener->config.coneOuterGain = outerGain; +} + +MA_API void ma_spatializer_listener_get_cone(const ma_spatializer_listener* pListener, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain) +{ + if (pListener == NULL) { + return; + } + + if (pInnerAngleInRadians != NULL) { + *pInnerAngleInRadians = pListener->config.coneInnerAngleInRadians; + } + + if (pOuterAngleInRadians != NULL) { + *pOuterAngleInRadians = pListener->config.coneOuterAngleInRadians; + } + + if (pOuterGain != NULL) { + *pOuterGain = pListener->config.coneOuterGain; + } +} + +MA_API void ma_spatializer_listener_set_position(ma_spatializer_listener* pListener, float x, float y, float z) +{ + if (pListener == NULL) { + return; + } + + pListener->position = ma_vec3f_init_3f(x, y, z); +} + +MA_API ma_vec3f ma_spatializer_listener_get_position(const ma_spatializer_listener* pListener) +{ + if (pListener == NULL) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return pListener->position; +} + +MA_API void ma_spatializer_listener_set_direction(ma_spatializer_listener* pListener, float x, float y, float z) +{ + if (pListener == NULL) { + return; + } + + pListener->direction = ma_vec3f_init_3f(x, y, z); +} + +MA_API ma_vec3f ma_spatializer_listener_get_direction(const ma_spatializer_listener* pListener) +{ + if (pListener == NULL) { + return ma_vec3f_init_3f(0, 0, -1); + } + + return pListener->direction; +} + +MA_API void ma_spatializer_listener_set_velocity(ma_spatializer_listener* pListener, float x, float y, float z) +{ + if (pListener == NULL) { + return; + } + + pListener->velocity = ma_vec3f_init_3f(x, y, z); +} + +MA_API ma_vec3f ma_spatializer_listener_get_velocity(const ma_spatializer_listener* pListener) +{ + if (pListener == NULL) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return pListener->velocity; +} + +MA_API void ma_spatializer_listener_set_speed_of_sound(ma_spatializer_listener* pListener, float speedOfSound) +{ + if (pListener == NULL) { + return; + } + + pListener->config.speedOfSound = speedOfSound; +} + +MA_API float ma_spatializer_listener_get_speed_of_sound(const ma_spatializer_listener* pListener) +{ + if (pListener == NULL) { + return 0; + } + + return pListener->config.speedOfSound; +} + +MA_API void ma_spatializer_listener_set_world_up(ma_spatializer_listener* pListener, float x, float y, float z) +{ + if (pListener == NULL) { + return; + } + + pListener->config.worldUp = ma_vec3f_init_3f(x, y, z); +} + +MA_API ma_vec3f ma_spatializer_listener_get_world_up(const ma_spatializer_listener* pListener) +{ + if (pListener == NULL) { + return ma_vec3f_init_3f(0, 1, 0); + } + + return pListener->config.worldUp; +} + +MA_API void ma_spatializer_listener_set_enabled(ma_spatializer_listener* pListener, ma_bool32 isEnabled) +{ + if (pListener == NULL) { + return; + } + + pListener->isEnabled = isEnabled; +} + +MA_API ma_bool32 ma_spatializer_listener_is_enabled(const ma_spatializer_listener* pListener) +{ + if (pListener == NULL) { + return MA_FALSE; + } + + return pListener->isEnabled; +} + + + + +MA_API ma_spatializer_config ma_spatializer_config_init(ma_uint32 channelsIn, ma_uint32 channelsOut) +{ + ma_spatializer_config config; + + MA_ZERO_OBJECT(&config); + config.channelsIn = channelsIn; + config.channelsOut = channelsOut; + config.pChannelMapIn = NULL; + config.attenuationModel = ma_attenuation_model_inverse; + config.positioning = ma_positioning_absolute; + config.handedness = ma_handedness_right; + config.minGain = 0; + config.maxGain = 1; + config.minDistance = 1; + config.maxDistance = MA_FLT_MAX; + config.rolloff = 1; + config.coneInnerAngleInRadians = 6.283185f; /* 360 degrees. */ + config.coneOuterAngleInRadians = 6.283185f; /* 360 degress. */ + config.coneOuterGain = 0.0f; + config.dopplerFactor = 1; + config.gainSmoothTimeInFrames = 360; /* 7.5ms @ 48K. */ + + return config; +} + + +static ma_gainer_config ma_spatializer_gainer_config_init(const ma_spatializer_config* pConfig) +{ + MA_ASSERT(pConfig != NULL); + return ma_gainer_config_init(pConfig->channelsOut, pConfig->gainSmoothTimeInFrames); +} + +static ma_result ma_spatializer_validate_config(const ma_spatializer_config* pConfig) +{ + MA_ASSERT(pConfig != NULL); + + if (pConfig->channelsIn == 0 || pConfig->channelsOut == 0) { + return MA_INVALID_ARGS; + } + + return MA_SUCCESS; +} + +typedef struct +{ + size_t sizeInBytes; + size_t channelMapInOffset; + size_t newChannelGainsOffset; + size_t gainerOffset; +} ma_spatializer_heap_layout; + +static ma_result ma_spatializer_get_heap_layout(const ma_spatializer_config* pConfig, ma_spatializer_heap_layout* pHeapLayout) +{ + ma_result result; + + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_spatializer_validate_config(pConfig); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes = 0; + + /* Channel map. */ + pHeapLayout->channelMapInOffset = MA_SIZE_MAX; /* <-- MA_SIZE_MAX indicates no allocation necessary. */ + if (pConfig->pChannelMapIn != NULL) { + pHeapLayout->channelMapInOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(sizeof(*pConfig->pChannelMapIn) * pConfig->channelsIn); + } + + /* New channel gains for output. */ + pHeapLayout->newChannelGainsOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(sizeof(float) * pConfig->channelsOut); + + /* Gainer. */ + { + size_t gainerHeapSizeInBytes; + ma_gainer_config gainerConfig; + + gainerConfig = ma_spatializer_gainer_config_init(pConfig); + + result = ma_gainer_get_heap_size(&gainerConfig, &gainerHeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->gainerOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(gainerHeapSizeInBytes); + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_spatializer_get_heap_size(const ma_spatializer_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_spatializer_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; /* Safety. */ + + result = ma_spatializer_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + + +MA_API ma_result ma_spatializer_init_preallocated(const ma_spatializer_config* pConfig, void* pHeap, ma_spatializer* pSpatializer) +{ + ma_result result; + ma_spatializer_heap_layout heapLayout; + ma_gainer_config gainerConfig; + + if (pSpatializer == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pSpatializer); + + if (pConfig == NULL || pHeap == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_spatializer_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pSpatializer->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pSpatializer->config = *pConfig; + pSpatializer->position = ma_vec3f_init_3f(0, 0, 0); + pSpatializer->direction = ma_vec3f_init_3f(0, 0, -1); + pSpatializer->velocity = ma_vec3f_init_3f(0, 0, 0); + pSpatializer->dopplerPitch = 1; + + /* Swap the forward direction if we're left handed (it was initialized based on right handed). */ + if (pSpatializer->config.handedness == ma_handedness_left) { + pSpatializer->direction = ma_vec3f_neg(pSpatializer->direction); + } + + /* Channel map. This will be on the heap. */ + if (pConfig->pChannelMapIn != NULL) { + pSpatializer->config.pChannelMapIn = (ma_channel*)ma_offset_ptr(pHeap, heapLayout.channelMapInOffset); + ma_channel_map_copy_or_default(pSpatializer->config.pChannelMapIn, pSpatializer->config.channelsIn, pConfig->pChannelMapIn, pSpatializer->config.channelsIn); + } + + /* New channel gains for output channels. */ + pSpatializer->pNewChannelGainsOut = (float*)ma_offset_ptr(pHeap, heapLayout.newChannelGainsOffset); + + /* Gainer. */ + gainerConfig = ma_spatializer_gainer_config_init(pConfig); + + result = ma_gainer_init_preallocated(&gainerConfig, ma_offset_ptr(pHeap, heapLayout.gainerOffset), &pSpatializer->gainer); + if (result != MA_SUCCESS) { + return result; /* Failed to initialize the gainer. */ + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_spatializer_init(const ma_spatializer_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_spatializer* pSpatializer) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + /* We'll need a heap allocation to retrieve the size. */ + result = ma_spatializer_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_spatializer_init_preallocated(pConfig, pHeap, pSpatializer); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pSpatializer->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_spatializer_uninit(ma_spatializer* pSpatializer, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pSpatializer == NULL) { + return; + } + + ma_gainer_uninit(&pSpatializer->gainer, pAllocationCallbacks); + + if (pSpatializer->_ownsHeap) { + ma_free(pSpatializer->_pHeap, pAllocationCallbacks); + } +} + +static float ma_calculate_angular_gain(ma_vec3f dirA, ma_vec3f dirB, float coneInnerAngleInRadians, float coneOuterAngleInRadians, float coneOuterGain) +{ + /* + Angular attenuation. + + Unlike distance gain, the math for this is not specified by the OpenAL spec so we'll just go ahead and figure + this out for ourselves at the expense of possibly being inconsistent with other implementations. + + To do cone attenuation, I'm just using the same math that we'd use to implement a basic spotlight in OpenGL. We + just need to get the direction from the source to the listener and then do a dot product against that and the + direction of the spotlight. Then we just compare that dot product against the cosine of the inner and outer + angles. If the dot product is greater than the the outer angle, we just use coneOuterGain. If it's less than + the inner angle, we just use a gain of 1. Otherwise we linearly interpolate between 1 and coneOuterGain. + */ + if (coneInnerAngleInRadians < 6.283185f) { + float angularGain = 1; + float cutoffInner = (float)ma_cosd(coneInnerAngleInRadians*0.5f); + float cutoffOuter = (float)ma_cosd(coneOuterAngleInRadians*0.5f); + float d; + + d = ma_vec3f_dot(dirA, dirB); + + if (d > cutoffInner) { + /* It's inside the inner angle. */ + angularGain = 1; + } else { + /* It's outside the inner angle. */ + if (d > cutoffOuter) { + /* It's between the inner and outer angle. We need to linearly interpolate between 1 and coneOuterGain. */ + angularGain = ma_mix_f32(coneOuterGain, 1, (d - cutoffOuter) / (cutoffInner - cutoffOuter)); + } else { + /* It's outside the outer angle. */ + angularGain = coneOuterGain; + } + } + + /*printf("d = %f; cutoffInner = %f; cutoffOuter = %f; angularGain = %f\n", d, cutoffInner, cutoffOuter, angularGain);*/ + return angularGain; + } else { + /* Inner angle is 360 degrees so no need to do any attenuation. */ + return 1; + } +} + +MA_API ma_result ma_spatializer_process_pcm_frames(ma_spatializer* pSpatializer, ma_spatializer_listener* pListener, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) +{ + ma_channel* pChannelMapIn = pSpatializer->config.pChannelMapIn; + ma_channel* pChannelMapOut = pListener->config.pChannelMapOut; + + if (pSpatializer == NULL) { + return MA_INVALID_ARGS; + } + + /* If we're not spatializing we need to run an optimized path. */ + if (pSpatializer->config.attenuationModel == ma_attenuation_model_none) { + if (ma_spatializer_listener_is_enabled(pListener)) { + /* No attenuation is required, but we'll need to do some channel conversion. */ + if (pSpatializer->config.channelsIn == pSpatializer->config.channelsOut) { + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, ma_format_f32, pSpatializer->config.channelsIn); + } else { + ma_channel_map_apply_f32((float*)pFramesOut, pChannelMapOut, pSpatializer->config.channelsOut, (const float*)pFramesIn, pChannelMapIn, pSpatializer->config.channelsIn, frameCount, ma_channel_mix_mode_rectangular, ma_mono_expansion_mode_default); /* Safe casts to float* because f32 is the only supported format. */ + } + } else { + /* The listener is disabled. Output silence. */ + ma_silence_pcm_frames(pFramesOut, frameCount, ma_format_f32, pSpatializer->config.channelsOut); + } + + /* + We're not doing attenuation so don't bother with doppler for now. I'm not sure if this is + the correct thinking so might need to review this later. + */ + pSpatializer->dopplerPitch = 1; + } else { + /* + Let's first determine which listener the sound is closest to. Need to keep in mind that we + might not have a world or any listeners, in which case we just spatializer based on the + listener being positioned at the origin (0, 0, 0). + */ + ma_vec3f relativePosNormalized; + ma_vec3f relativePos; /* The position relative to the listener. */ + ma_vec3f relativeDir; /* The direction of the sound, relative to the listener. */ + ma_vec3f listenerVel; /* The volocity of the listener. For doppler pitch calculation. */ + float speedOfSound; + float distance = 0; + float gain = 1; + ma_uint32 iChannel; + const ma_uint32 channelsOut = pSpatializer->config.channelsOut; + const ma_uint32 channelsIn = pSpatializer->config.channelsIn; + + /* + We'll need the listener velocity for doppler pitch calculations. The speed of sound is + defined by the listener, so we'll grab that here too. + */ + if (pListener != NULL) { + listenerVel = pListener->velocity; + speedOfSound = pListener->config.speedOfSound; + } else { + listenerVel = ma_vec3f_init_3f(0, 0, 0); + speedOfSound = MA_DEFAULT_SPEED_OF_SOUND; + } + + if (pListener == NULL || pSpatializer->config.positioning == ma_positioning_relative) { + /* There's no listener or we're using relative positioning. */ + relativePos = pSpatializer->position; + relativeDir = pSpatializer->direction; + } else { + /* + We've found a listener and we're using absolute positioning. We need to transform the + sound's position and direction so that it's relative to listener. Later on we'll use + this for determining the factors to apply to each channel to apply the panning effect. + */ + ma_vec3f v; + ma_vec3f axisX; + ma_vec3f axisY; + ma_vec3f axisZ; + float m[4][4]; + + /* + We need to calcualte the right vector from our forward and up vectors. This is done with + a cross product. + */ + axisZ = ma_vec3f_normalize(pListener->direction); /* Normalization required here because we can't trust the caller. */ + axisX = ma_vec3f_normalize(ma_vec3f_cross(axisZ, pListener->config.worldUp)); /* Normalization required here because the world up vector may not be perpendicular with the forward vector. */ + + /* + The calculation of axisX above can result in a zero-length vector if the listener is + looking straight up on the Y axis. We'll need to fall back to a +X in this case so that + the calculations below don't fall apart. This is where a quaternion based listener and + sound orientation would come in handy. + */ + if (ma_vec3f_len2(axisX) == 0) { + axisX = ma_vec3f_init_3f(1, 0, 0); + } + + axisY = ma_vec3f_cross(axisX, axisZ); /* No normalization is required here because axisX and axisZ are unit length and perpendicular. */ + + /* + We need to swap the X axis if we're left handed because otherwise the cross product above + will have resulted in it pointing in the wrong direction (right handed was assumed in the + cross products above). + */ + if (pListener->config.handedness == ma_handedness_left) { + axisX = ma_vec3f_neg(axisX); + } + + /* Lookat. */ + m[0][0] = axisX.x; m[1][0] = axisX.y; m[2][0] = axisX.z; m[3][0] = -ma_vec3f_dot(axisX, pListener->position); + m[0][1] = axisY.x; m[1][1] = axisY.y; m[2][1] = axisY.z; m[3][1] = -ma_vec3f_dot(axisY, pListener->position); + m[0][2] = -axisZ.x; m[1][2] = -axisZ.y; m[2][2] = -axisZ.z; m[3][2] = -ma_vec3f_dot(ma_vec3f_neg(axisZ), pListener->position); + m[0][3] = 0; m[1][3] = 0; m[2][3] = 0; m[3][3] = 1; + + /* + Multiply the lookat matrix by the spatializer position to transform it to listener + space. This allows calculations to work based on the sound being relative to the + origin which makes things simpler. + */ + v = pSpatializer->position; + relativePos.x = m[0][0] * v.x + m[1][0] * v.y + m[2][0] * v.z + m[3][0] * 1; + relativePos.y = m[0][1] * v.x + m[1][1] * v.y + m[2][1] * v.z + m[3][1] * 1; + relativePos.z = m[0][2] * v.x + m[1][2] * v.y + m[2][2] * v.z + m[3][2] * 1; + + /* + The direction of the sound needs to also be transformed so that it's relative to the + rotation of the listener. + */ + v = pSpatializer->direction; + relativeDir.x = m[0][0] * v.x + m[1][0] * v.y + m[2][0] * v.z; + relativeDir.y = m[0][1] * v.x + m[1][1] * v.y + m[2][1] * v.z; + relativeDir.z = m[0][2] * v.x + m[1][2] * v.y + m[2][2] * v.z; + } + + distance = ma_vec3f_len(relativePos); + + /* We've gathered the data, so now we can apply some spatialization. */ + switch (pSpatializer->config.attenuationModel) { + case ma_attenuation_model_inverse: + { + gain = ma_attenuation_inverse(distance, pSpatializer->config.minDistance, pSpatializer->config.maxDistance, pSpatializer->config.rolloff); + } break; + case ma_attenuation_model_linear: + { + gain = ma_attenuation_linear(distance, pSpatializer->config.minDistance, pSpatializer->config.maxDistance, pSpatializer->config.rolloff); + } break; + case ma_attenuation_model_exponential: + { + gain = ma_attenuation_exponential(distance, pSpatializer->config.minDistance, pSpatializer->config.maxDistance, pSpatializer->config.rolloff); + } break; + case ma_attenuation_model_none: + default: + { + gain = 1; + } break; + } + + /* Normalize the position. */ + if (distance > 0.001f) { + float distanceInv = 1/distance; + relativePosNormalized = relativePos; + relativePosNormalized.x *= distanceInv; + relativePosNormalized.y *= distanceInv; + relativePosNormalized.z *= distanceInv; + } else { + distance = 0; + relativePosNormalized = ma_vec3f_init_3f(0, 0, 0); + } + + /* + Angular attenuation. + + Unlike distance gain, the math for this is not specified by the OpenAL spec so we'll just go ahead and figure + this out for ourselves at the expense of possibly being inconsistent with other implementations. + + To do cone attenuation, I'm just using the same math that we'd use to implement a basic spotlight in OpenGL. We + just need to get the direction from the source to the listener and then do a dot product against that and the + direction of the spotlight. Then we just compare that dot product against the cosine of the inner and outer + angles. If the dot product is greater than the the outer angle, we just use coneOuterGain. If it's less than + the inner angle, we just use a gain of 1. Otherwise we linearly interpolate between 1 and coneOuterGain. + */ + if (distance > 0) { + /* Source anglular gain. */ + gain *= ma_calculate_angular_gain(relativeDir, ma_vec3f_neg(relativePosNormalized), pSpatializer->config.coneInnerAngleInRadians, pSpatializer->config.coneOuterAngleInRadians, pSpatializer->config.coneOuterGain); + + /* + We're supporting angular gain on the listener as well for those who want to reduce the volume of sounds that + are positioned behind the listener. On default settings, this will have no effect. + */ + if (pListener != NULL && pListener->config.coneInnerAngleInRadians < 6.283185f) { + ma_vec3f listenerDirection; + float listenerInnerAngle; + float listenerOuterAngle; + float listenerOuterGain; + + if (pListener->config.handedness == ma_handedness_right) { + listenerDirection = ma_vec3f_init_3f(0, 0, -1); + } else { + listenerDirection = ma_vec3f_init_3f(0, 0, +1); + } + + listenerInnerAngle = pListener->config.coneInnerAngleInRadians; + listenerOuterAngle = pListener->config.coneOuterAngleInRadians; + listenerOuterGain = pListener->config.coneOuterGain; + + gain *= ma_calculate_angular_gain(listenerDirection, relativePosNormalized, listenerInnerAngle, listenerOuterAngle, listenerOuterGain); + } + } else { + /* The sound is right on top of the listener. Don't do any angular attenuation. */ + } + + + /* Clamp the gain. */ + gain = ma_clamp(gain, pSpatializer->config.minGain, pSpatializer->config.maxGain); + + /* + Panning. This is where we'll apply the gain and convert to the output channel count. We have an optimized path for + when we're converting to a mono stream. In that case we don't really need to do any panning - we just apply the + gain to the final output. + */ + /*printf("distance=%f; gain=%f\n", distance, gain);*/ + + /* We must have a valid channel map here to ensure we spatialize properly. */ + MA_ASSERT(pChannelMapOut != NULL); + + /* + We're not converting to mono so we'll want to apply some panning. This is where the feeling of something being + to the left, right, infront or behind the listener is calculated. I'm just using a basic model here. Note that + the code below is not based on any specific algorithm. I'm just implementing this off the top of my head and + seeing how it goes. There might be better ways to do this. + + To determine the direction of the sound relative to a speaker I'm using dot products. Each speaker is given a + direction. For example, the left channel in a stereo system will be -1 on the X axis and the right channel will + be +1 on the X axis. A dot product is performed against the direction vector of the channel and the normalized + position of the sound. + */ + for (iChannel = 0; iChannel < channelsOut; iChannel += 1) { + pSpatializer->pNewChannelGainsOut[iChannel] = gain; + } + + /* + Convert to our output channel count. If the listener is disabled we just output silence here. We cannot ignore + the whole section of code here because we need to update some internal spatialization state. + */ + if (ma_spatializer_listener_is_enabled(pListener)) { + ma_channel_map_apply_f32((float*)pFramesOut, pChannelMapOut, channelsOut, (const float*)pFramesIn, pChannelMapIn, channelsIn, frameCount, ma_channel_mix_mode_rectangular, ma_mono_expansion_mode_default); + } else { + ma_silence_pcm_frames(pFramesOut, frameCount, ma_format_f32, pSpatializer->config.channelsOut); + } + + /* + Calculate our per-channel gains. We do this based on the normalized relative position of the sound and it's + relation to the direction of the channel. + */ + if (distance > 0) { + ma_vec3f unitPos = relativePos; + float distanceInv = 1/distance; + unitPos.x *= distanceInv; + unitPos.y *= distanceInv; + unitPos.z *= distanceInv; + + for (iChannel = 0; iChannel < channelsOut; iChannel += 1) { + ma_channel channelOut; + float d; + float dMin; + + channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannel); + if (ma_is_spatial_channel_position(channelOut)) { + d = ma_vec3f_dot(unitPos, ma_get_channel_direction(channelOut)); + } else { + d = 1; /* It's not a spatial channel so there's no real notion of direction. */ + } + + /* + In my testing, if the panning effect is too aggressive it makes spatialization feel uncomfortable. + The "dMin" variable below is used to control the aggressiveness of the panning effect. When set to + 0, panning will be most extreme and any sounds that are positioned on the opposite side of the + speaker will be completely silent from that speaker. Not only does this feel uncomfortable, it + doesn't even remotely represent the real world at all because sounds that come from your right side + are still clearly audible from your left side. Setting "dMin" to 1 will result in no panning at + all, which is also not ideal. By setting it to something greater than 0, the spatialization effect + becomes much less dramatic and a lot more bearable. + + Summary: 0 = more extreme panning; 1 = no panning. + */ + dMin = 0.2f; /* TODO: Consider making this configurable. */ + + /* + At this point, "d" will be positive if the sound is on the same side as the channel and negative if + it's on the opposite side. It will be in the range of -1..1. There's two ways I can think of to + calculate a panning value. The first is to simply convert it to 0..1, however this has a problem + which I'm not entirely happy with. Considering a stereo system, when a sound is positioned right + in front of the listener it'll result in each speaker getting a gain of 0.5. I don't know if I like + the idea of having a scaling factor of 0.5 being applied to a sound when it's sitting right in front + of the listener. I would intuitively expect that to be played at full volume, or close to it. + + The second idea I think of is to only apply a reduction in gain when the sound is on the opposite + side of the speaker. That is, reduce the gain only when the dot product is negative. The problem + with this is that there will not be any attenuation as the sound sweeps around the 180 degrees + where the dot product is positive. The idea with this option is that you leave the gain at 1 when + the sound is being played on the same side as the speaker and then you just reduce the volume when + the sound is on the other side. + + The summarize, I think the first option should give a better sense of spatialization, but the second + option is better for preserving the sound's power. + + UPDATE: In my testing, I find the first option to sound better. You can feel the sense of space a + bit better, but you can also hear the reduction in volume when it's right in front. + */ + #if 1 + { + /* + Scale the dot product from -1..1 to 0..1. Will result in a sound directly in front losing power + by being played at 0.5 gain. + */ + d = (d + 1) * 0.5f; /* -1..1 to 0..1 */ + d = ma_max(d, dMin); + pSpatializer->pNewChannelGainsOut[iChannel] *= d; + } + #else + { + /* + Only reduce the volume of the sound if it's on the opposite side. This path keeps the volume more + consistent, but comes at the expense of a worse sense of space and positioning. + */ + if (d < 0) { + d += 1; /* Move into the positive range. */ + d = ma_max(d, dMin); + channelGainsOut[iChannel] *= d; + } + } + #endif + } + } else { + /* Assume the sound is right on top of us. Don't do any panning. */ + } + + /* Now we need to apply the volume to each channel. This needs to run through the gainer to ensure we get a smooth volume transition. */ + ma_gainer_set_gains(&pSpatializer->gainer, pSpatializer->pNewChannelGainsOut); + ma_gainer_process_pcm_frames(&pSpatializer->gainer, pFramesOut, pFramesOut, frameCount); + + /* + Before leaving we'll want to update our doppler pitch so that the caller can apply some + pitch shifting if they desire. Note that we need to negate the relative position here + because the doppler calculation needs to be source-to-listener, but ours is listener-to- + source. + */ + if (pSpatializer->config.dopplerFactor > 0) { + pSpatializer->dopplerPitch = ma_doppler_pitch(ma_vec3f_sub(pListener->position, pSpatializer->position), pSpatializer->velocity, listenerVel, speedOfSound, pSpatializer->config.dopplerFactor); + } else { + pSpatializer->dopplerPitch = 1; + } + } + + return MA_SUCCESS; +} + +MA_API ma_uint32 ma_spatializer_get_input_channels(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 0; + } + + return pSpatializer->config.channelsIn; +} + +MA_API ma_uint32 ma_spatializer_get_output_channels(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 0; + } + + return pSpatializer->config.channelsOut; +} + +MA_API void ma_spatializer_set_attenuation_model(ma_spatializer* pSpatializer, ma_attenuation_model attenuationModel) +{ + if (pSpatializer == NULL) { + return; + } + + pSpatializer->config.attenuationModel = attenuationModel; +} + +MA_API ma_attenuation_model ma_spatializer_get_attenuation_model(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return ma_attenuation_model_none; + } + + return pSpatializer->config.attenuationModel; +} + +MA_API void ma_spatializer_set_positioning(ma_spatializer* pSpatializer, ma_positioning positioning) +{ + if (pSpatializer == NULL) { + return; + } + + pSpatializer->config.positioning = positioning; +} + +MA_API ma_positioning ma_spatializer_get_positioning(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return ma_positioning_absolute; + } + + return pSpatializer->config.positioning; +} + +MA_API void ma_spatializer_set_rolloff(ma_spatializer* pSpatializer, float rolloff) +{ + if (pSpatializer == NULL) { + return; + } + + pSpatializer->config.rolloff = rolloff; +} + +MA_API float ma_spatializer_get_rolloff(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 0; + } + + return pSpatializer->config.rolloff; +} + +MA_API void ma_spatializer_set_min_gain(ma_spatializer* pSpatializer, float minGain) +{ + if (pSpatializer == NULL) { + return; + } + + pSpatializer->config.minGain = minGain; +} + +MA_API float ma_spatializer_get_min_gain(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 0; + } + + return pSpatializer->config.minGain; +} + +MA_API void ma_spatializer_set_max_gain(ma_spatializer* pSpatializer, float maxGain) +{ + if (pSpatializer == NULL) { + return; + } + + pSpatializer->config.maxGain = maxGain; +} + +MA_API float ma_spatializer_get_max_gain(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 0; + } + + return pSpatializer->config.maxGain; +} + +MA_API void ma_spatializer_set_min_distance(ma_spatializer* pSpatializer, float minDistance) +{ + if (pSpatializer == NULL) { + return; + } + + pSpatializer->config.minDistance = minDistance; +} + +MA_API float ma_spatializer_get_min_distance(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 0; + } + + return pSpatializer->config.minDistance; +} + +MA_API void ma_spatializer_set_max_distance(ma_spatializer* pSpatializer, float maxDistance) +{ + if (pSpatializer == NULL) { + return; + } + + pSpatializer->config.maxDistance = maxDistance; +} + +MA_API float ma_spatializer_get_max_distance(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 0; + } + + return pSpatializer->config.maxDistance; +} + +MA_API void ma_spatializer_set_cone(ma_spatializer* pSpatializer, float innerAngleInRadians, float outerAngleInRadians, float outerGain) +{ + if (pSpatializer == NULL) { + return; + } + + pSpatializer->config.coneInnerAngleInRadians = innerAngleInRadians; + pSpatializer->config.coneOuterAngleInRadians = outerAngleInRadians; + pSpatializer->config.coneOuterGain = outerGain; +} + +MA_API void ma_spatializer_get_cone(const ma_spatializer* pSpatializer, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain) +{ + if (pSpatializer == NULL) { + return; + } + + if (pInnerAngleInRadians != NULL) { + *pInnerAngleInRadians = pSpatializer->config.coneInnerAngleInRadians; + } + + if (pOuterAngleInRadians != NULL) { + *pOuterAngleInRadians = pSpatializer->config.coneOuterAngleInRadians; + } + + if (pOuterGain != NULL) { + *pOuterGain = pSpatializer->config.coneOuterGain; + } +} + +MA_API void ma_spatializer_set_doppler_factor(ma_spatializer* pSpatializer, float dopplerFactor) +{ + if (pSpatializer == NULL) { + return; + } + + pSpatializer->config.dopplerFactor = dopplerFactor; +} + +MA_API float ma_spatializer_get_doppler_factor(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 1; + } + + return pSpatializer->config.dopplerFactor; +} + +MA_API void ma_spatializer_set_position(ma_spatializer* pSpatializer, float x, float y, float z) +{ + if (pSpatializer == NULL) { + return; + } + + pSpatializer->position = ma_vec3f_init_3f(x, y, z); +} + +MA_API ma_vec3f ma_spatializer_get_position(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return pSpatializer->position; +} + +MA_API void ma_spatializer_set_direction(ma_spatializer* pSpatializer, float x, float y, float z) +{ + if (pSpatializer == NULL) { + return; + } + + pSpatializer->direction = ma_vec3f_init_3f(x, y, z); +} + +MA_API ma_vec3f ma_spatializer_get_direction(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return ma_vec3f_init_3f(0, 0, -1); + } + + return pSpatializer->direction; +} + +MA_API void ma_spatializer_set_velocity(ma_spatializer* pSpatializer, float x, float y, float z) +{ + if (pSpatializer == NULL) { + return; + } + + pSpatializer->velocity = ma_vec3f_init_3f(x, y, z); +} + +MA_API ma_vec3f ma_spatializer_get_velocity(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return pSpatializer->velocity; +} + + + + /************************************************************************************************************************************************************** Resampling @@ -32466,6 +36778,16 @@ MA_API ma_linear_resampler_config ma_linear_resampler_config_init(ma_format form return config; } + +typedef struct +{ + size_t sizeInBytes; + size_t x0Offset; + size_t x1Offset; + size_t lpfOffset; +} ma_linear_resampler_heap_layout; + + static void ma_linear_resampler_adjust_timer_for_new_rate(ma_linear_resampler* pResampler, ma_uint32 oldSampleRateOut, ma_uint32 newSampleRateOut) { /* @@ -32484,7 +36806,7 @@ static void ma_linear_resampler_adjust_timer_for_new_rate(ma_linear_resampler* p pResampler->inTimeFrac = pResampler->inTimeFrac % pResampler->config.sampleRateOut; } -static ma_result ma_linear_resampler_set_rate_internal(ma_linear_resampler* pResampler, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, ma_bool32 isResamplerAlreadyInitialized) +static ma_result ma_linear_resampler_set_rate_internal(ma_linear_resampler* pResampler, void* pHeap, ma_linear_resampler_heap_layout* pHeapLayout, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, ma_bool32 isResamplerAlreadyInitialized) { ma_result result; ma_uint32 gcf; @@ -32528,7 +36850,7 @@ static ma_result ma_linear_resampler_set_rate_internal(ma_linear_resampler* pRes if (isResamplerAlreadyInitialized) { result = ma_lpf_reinit(&lpfConfig, &pResampler->lpf); } else { - result = ma_lpf_init(&lpfConfig, &pResampler->lpf); + result = ma_lpf_init_preallocated(&lpfConfig, ma_offset_ptr(pHeap, pHeapLayout->lpfOffset), &pResampler->lpf); } if (result != MA_SUCCESS) { @@ -32545,9 +36867,88 @@ static ma_result ma_linear_resampler_set_rate_internal(ma_linear_resampler* pRes return MA_SUCCESS; } -MA_API ma_result ma_linear_resampler_init(const ma_linear_resampler_config* pConfig, ma_linear_resampler* pResampler) +static ma_result ma_linear_resampler_get_heap_layout(const ma_linear_resampler_config* pConfig, ma_linear_resampler_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->format != ma_format_f32 && pConfig->format != ma_format_s16) { + return MA_INVALID_ARGS; + } + + if (pConfig->channels == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* x0 */ + pHeapLayout->x0Offset = pHeapLayout->sizeInBytes; + if (pConfig->format == ma_format_f32) { + pHeapLayout->sizeInBytes += sizeof(float) * pConfig->channels; + } else { + pHeapLayout->sizeInBytes += sizeof(ma_int16) * pConfig->channels; + } + + /* x1 */ + pHeapLayout->x1Offset = pHeapLayout->sizeInBytes; + if (pConfig->format == ma_format_f32) { + pHeapLayout->sizeInBytes += sizeof(float) * pConfig->channels; + } else { + pHeapLayout->sizeInBytes += sizeof(ma_int16) * pConfig->channels; + } + + /* LPF */ + pHeapLayout->lpfOffset = pHeapLayout->sizeInBytes; + { + ma_result result; + size_t lpfHeapSizeInBytes; + ma_lpf_config lpfConfig = ma_lpf_config_init(pConfig->format, pConfig->channels, 1, 1, pConfig->lpfOrder); /* Sample rate and cutoff frequency do not matter. */ + + result = ma_lpf_get_heap_size(&lpfConfig, &lpfHeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += lpfHeapSizeInBytes; + } + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_linear_resampler_get_heap_size(const ma_linear_resampler_config* pConfig, size_t* pHeapSizeInBytes) { ma_result result; + ma_linear_resampler_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_linear_resampler_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_linear_resampler_init_preallocated(const ma_linear_resampler_config* pConfig, void* pHeap, ma_linear_resampler* pResampler) +{ + ma_result result; + ma_linear_resampler_heap_layout heapLayout; if (pResampler == NULL) { return MA_INVALID_ARGS; @@ -32555,18 +36956,26 @@ MA_API ma_result ma_linear_resampler_init(const ma_linear_resampler_config* pCon MA_ZERO_OBJECT(pResampler); - if (pConfig == NULL) { - return MA_INVALID_ARGS; - } - - if (pConfig->channels < MA_MIN_CHANNELS || pConfig->channels > MA_MAX_CHANNELS) { - return MA_INVALID_ARGS; + result = ma_linear_resampler_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; } pResampler->config = *pConfig; + pResampler->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + if (pConfig->format == ma_format_f32) { + pResampler->x0.f32 = (float*)ma_offset_ptr(pHeap, heapLayout.x0Offset); + pResampler->x1.f32 = (float*)ma_offset_ptr(pHeap, heapLayout.x1Offset); + } else { + pResampler->x0.s16 = (ma_int16*)ma_offset_ptr(pHeap, heapLayout.x0Offset); + pResampler->x1.s16 = (ma_int16*)ma_offset_ptr(pHeap, heapLayout.x1Offset); + } + /* Setting the rate will set up the filter and time advances for us. */ - result = ma_linear_resampler_set_rate_internal(pResampler, pConfig->sampleRateIn, pConfig->sampleRateOut, /* isResamplerAlreadyInitialized = */ MA_FALSE); + result = ma_linear_resampler_set_rate_internal(pResampler, pHeap, &heapLayout, pConfig->sampleRateIn, pConfig->sampleRateOut, /* isResamplerAlreadyInitialized = */ MA_FALSE); if (result != MA_SUCCESS) { return result; } @@ -32577,11 +36986,47 @@ MA_API ma_result ma_linear_resampler_init(const ma_linear_resampler_config* pCon return MA_SUCCESS; } -MA_API void ma_linear_resampler_uninit(ma_linear_resampler* pResampler) +MA_API ma_result ma_linear_resampler_init(const ma_linear_resampler_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_linear_resampler* pResampler) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_linear_resampler_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_linear_resampler_init_preallocated(pConfig, pHeap, pResampler); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pResampler->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_linear_resampler_uninit(ma_linear_resampler* pResampler, const ma_allocation_callbacks* pAllocationCallbacks) { if (pResampler == NULL) { return; } + + ma_lpf_uninit(&pResampler->lpf, pAllocationCallbacks); + + if (pResampler->_ownsHeap) { + ma_free(pResampler->_pHeap, pAllocationCallbacks); + } } static MA_INLINE ma_int16 ma_linear_resampler_mix_s16(ma_int16 x, ma_int16 y, ma_int32 a, const ma_int32 shift) @@ -32611,7 +37056,7 @@ static void ma_linear_resampler_interpolate_frame_s16(ma_linear_resampler* pResa a = (pResampler->inTimeFrac << shift) / pResampler->config.sampleRateOut; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { ma_int16 s = ma_linear_resampler_mix_s16(pResampler->x0.s16[c], pResampler->x1.s16[c], a, shift); pFrameOut[c] = s; @@ -32630,7 +37075,7 @@ static void ma_linear_resampler_interpolate_frame_f32(ma_linear_resampler* pResa a = (float)pResampler->inTimeFrac / pResampler->config.sampleRateOut; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { float s = ma_mix_f32_fast(pResampler->x0.f32[c], pResampler->x1.f32[c], a); pFrameOut[c] = s; @@ -32977,7 +37422,7 @@ MA_API ma_result ma_linear_resampler_process_pcm_frames(ma_linear_resampler* pRe MA_API ma_result ma_linear_resampler_set_rate(ma_linear_resampler* pResampler, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut) { - return ma_linear_resampler_set_rate_internal(pResampler, sampleRateIn, sampleRateOut, /* isResamplerAlreadyInitialized = */ MA_TRUE); + return ma_linear_resampler_set_rate_internal(pResampler, NULL, NULL, sampleRateIn, sampleRateOut, /* isResamplerAlreadyInitialized = */ MA_TRUE); } MA_API ma_result ma_linear_resampler_set_rate_ratio(ma_linear_resampler* pResampler, float ratioInOut) @@ -32997,19 +37442,42 @@ MA_API ma_result ma_linear_resampler_set_rate_ratio(ma_linear_resampler* pResamp return ma_linear_resampler_set_rate(pResampler, n, d); } - -MA_API ma_uint64 ma_linear_resampler_get_required_input_frame_count(const ma_linear_resampler* pResampler, ma_uint64 outputFrameCount) +MA_API ma_uint64 ma_linear_resampler_get_input_latency(const ma_linear_resampler* pResampler) { - ma_uint64 inputFrameCount; - if (pResampler == NULL) { return 0; } - if (outputFrameCount == 0) { + return 1 + ma_lpf_get_latency(&pResampler->lpf); +} + +MA_API ma_uint64 ma_linear_resampler_get_output_latency(const ma_linear_resampler* pResampler) +{ + if (pResampler == NULL) { return 0; } + return ma_linear_resampler_get_input_latency(pResampler) * pResampler->config.sampleRateOut / pResampler->config.sampleRateIn; +} + +MA_API ma_result ma_linear_resampler_get_required_input_frame_count(const ma_linear_resampler* pResampler, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount) +{ + ma_uint64 inputFrameCount; + + if (pInputFrameCount == NULL) { + return MA_INVALID_ARGS; + } + + *pInputFrameCount = 0; + + if (pResampler == NULL) { + return MA_INVALID_ARGS; + } + + if (outputFrameCount == 0) { + return MA_SUCCESS; + } + /* Any whole input frames are consumed before the first output frame is generated. */ inputFrameCount = pResampler->inTimeInt; outputFrameCount -= 1; @@ -33018,17 +37486,25 @@ MA_API ma_uint64 ma_linear_resampler_get_required_input_frame_count(const ma_lin inputFrameCount += outputFrameCount * pResampler->inAdvanceInt; inputFrameCount += (pResampler->inTimeFrac + (outputFrameCount * pResampler->inAdvanceFrac)) / pResampler->config.sampleRateOut; - return inputFrameCount; + *pInputFrameCount = inputFrameCount; + + return MA_SUCCESS; } -MA_API ma_uint64 ma_linear_resampler_get_expected_output_frame_count(const ma_linear_resampler* pResampler, ma_uint64 inputFrameCount) +MA_API ma_result ma_linear_resampler_get_expected_output_frame_count(const ma_linear_resampler* pResampler, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount) { ma_uint64 outputFrameCount; ma_uint64 preliminaryInputFrameCountFromFrac; ma_uint64 preliminaryInputFrameCount; + if (pOutputFrameCount == NULL) { + return MA_INVALID_ARGS; + } + + *pOutputFrameCount = 0; + if (pResampler == NULL) { - return 0; + return MA_INVALID_ARGS; } /* @@ -33055,45 +37531,117 @@ MA_API ma_uint64 ma_linear_resampler_get_expected_output_frame_count(const ma_li outputFrameCount += 1; } - return outputFrameCount; + *pOutputFrameCount = outputFrameCount; + + return MA_SUCCESS; } -MA_API ma_uint64 ma_linear_resampler_get_input_latency(const ma_linear_resampler* pResampler) + +/* Linear resampler backend vtable. */ +static ma_linear_resampler_config ma_resampling_backend_get_config__linear(const ma_resampler_config* pConfig) { - if (pResampler == NULL) { - return 0; + ma_linear_resampler_config linearConfig; + + linearConfig = ma_linear_resampler_config_init(pConfig->format, pConfig->channels, pConfig->sampleRateIn, pConfig->sampleRateOut); + linearConfig.lpfOrder = pConfig->linear.lpfOrder; + + return linearConfig; +} + +static ma_result ma_resampling_backend_get_heap_size__linear(void* pUserData, const ma_resampler_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_linear_resampler_config linearConfig; + + (void)pUserData; + + linearConfig = ma_resampling_backend_get_config__linear(pConfig); + + return ma_linear_resampler_get_heap_size(&linearConfig, pHeapSizeInBytes); +} + +static ma_result ma_resampling_backend_init__linear(void* pUserData, const ma_resampler_config* pConfig, void* pHeap, ma_resampling_backend** ppBackend) +{ + ma_resampler* pResampler = (ma_resampler*)pUserData; + ma_result result; + ma_linear_resampler_config linearConfig; + + (void)pUserData; + + linearConfig = ma_resampling_backend_get_config__linear(pConfig); + + result = ma_linear_resampler_init_preallocated(&linearConfig, pHeap, &pResampler->state.linear); + if (result != MA_SUCCESS) { + return result; } - return 1 + ma_lpf_get_latency(&pResampler->lpf); + *ppBackend = &pResampler->state.linear; + + return MA_SUCCESS; } -MA_API ma_uint64 ma_linear_resampler_get_output_latency(const ma_linear_resampler* pResampler) +static void ma_resampling_backend_uninit__linear(void* pUserData, ma_resampling_backend* pBackend, const ma_allocation_callbacks* pAllocationCallbacks) { - if (pResampler == NULL) { - return 0; - } + (void)pUserData; - return ma_linear_resampler_get_input_latency(pResampler) * pResampler->config.sampleRateOut / pResampler->config.sampleRateIn; + ma_linear_resampler_uninit((ma_linear_resampler*)pBackend, pAllocationCallbacks); } - -#if defined(ma_speex_resampler_h) -#define MA_HAS_SPEEX_RESAMPLER - -static ma_result ma_result_from_speex_err(int err) +static ma_result ma_resampling_backend_process__linear(void* pUserData, ma_resampling_backend* pBackend, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) { - switch (err) - { - case RESAMPLER_ERR_SUCCESS: return MA_SUCCESS; - case RESAMPLER_ERR_ALLOC_FAILED: return MA_OUT_OF_MEMORY; - case RESAMPLER_ERR_BAD_STATE: return MA_ERROR; - case RESAMPLER_ERR_INVALID_ARG: return MA_INVALID_ARGS; - case RESAMPLER_ERR_PTR_OVERLAP: return MA_INVALID_ARGS; - case RESAMPLER_ERR_OVERFLOW: return MA_ERROR; - default: return MA_ERROR; - } + (void)pUserData; + + return ma_linear_resampler_process_pcm_frames((ma_linear_resampler*)pBackend, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); } -#endif /* ma_speex_resampler_h */ + +static ma_result ma_resampling_backend_set_rate__linear(void* pUserData, ma_resampling_backend* pBackend, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut) +{ + (void)pUserData; + + return ma_linear_resampler_set_rate((ma_linear_resampler*)pBackend, sampleRateIn, sampleRateOut); +} + +static ma_uint64 ma_resampling_backend_get_input_latency__linear(void* pUserData, const ma_resampling_backend* pBackend) +{ + (void)pUserData; + + return ma_linear_resampler_get_input_latency((const ma_linear_resampler*)pBackend); +} + +static ma_uint64 ma_resampling_backend_get_output_latency__linear(void* pUserData, const ma_resampling_backend* pBackend) +{ + (void)pUserData; + + return ma_linear_resampler_get_output_latency((const ma_linear_resampler*)pBackend); +} + +static ma_result ma_resampling_backend_get_required_input_frame_count__linear(void* pUserData, const ma_resampling_backend* pBackend, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount) +{ + (void)pUserData; + + return ma_linear_resampler_get_required_input_frame_count((const ma_linear_resampler*)pBackend, outputFrameCount, pInputFrameCount); +} + +static ma_result ma_resampling_backend_get_expected_output_frame_count__linear(void* pUserData, const ma_resampling_backend* pBackend, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount) +{ + (void)pUserData; + + return ma_linear_resampler_get_expected_output_frame_count((const ma_linear_resampler*)pBackend, inputFrameCount, pOutputFrameCount); +} + +static ma_resampling_backend_vtable g_ma_linear_resampler_vtable = +{ + ma_resampling_backend_get_heap_size__linear, + ma_resampling_backend_init__linear, + ma_resampling_backend_uninit__linear, + ma_resampling_backend_process__linear, + ma_resampling_backend_set_rate__linear, + ma_resampling_backend_get_input_latency__linear, + ma_resampling_backend_get_output_latency__linear, + ma_resampling_backend_get_required_input_frame_count__linear, + ma_resampling_backend_get_expected_output_frame_count__linear +}; + + MA_API ma_resampler_config ma_resampler_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, ma_resample_algorithm algorithm) { @@ -33108,15 +37656,74 @@ MA_API ma_resampler_config ma_resampler_config_init(ma_format format, ma_uint32 /* Linear. */ config.linear.lpfOrder = ma_min(MA_DEFAULT_RESAMPLER_LPF_ORDER, MA_MAX_FILTER_ORDER); - config.linear.lpfNyquistFactor = 1; - - /* Speex. */ - config.speex.quality = 3; /* Cannot leave this as 0 as that is actually a valid value for Speex resampling quality. */ return config; } -MA_API ma_result ma_resampler_init(const ma_resampler_config* pConfig, ma_resampler* pResampler) +static ma_result ma_resampler_get_vtable(const ma_resampler_config* pConfig, ma_resampler* pResampler, ma_resampling_backend_vtable** ppVTable, void** ppUserData) +{ + MA_ASSERT(pConfig != NULL); + MA_ASSERT(ppVTable != NULL); + MA_ASSERT(ppUserData != NULL); + + /* Safety. */ + *ppVTable = NULL; + *ppUserData = NULL; + + switch (pConfig->algorithm) + { + case ma_resample_algorithm_linear: + { + *ppVTable = &g_ma_linear_resampler_vtable; + *ppUserData = pResampler; + } break; + + case ma_resample_algorithm_custom: + { + *ppVTable = pConfig->pBackendVTable; + *ppUserData = pConfig->pBackendUserData; + } break; + + default: return MA_INVALID_ARGS; + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_resampler_get_heap_size(const ma_resampler_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_resampling_backend_vtable* pVTable; + void* pVTableUserData; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_resampler_get_vtable(pConfig, NULL, &pVTable, &pVTableUserData); + if (result != MA_SUCCESS) { + return result; + } + + if (pVTable == NULL || pVTable->onGetHeapSize == NULL) { + return MA_NOT_IMPLEMENTED; + } + + result = pVTable->onGetHeapSize(pVTableUserData, pConfig, pHeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_resampler_init_preallocated(const ma_resampler_config* pConfig, void* pHeap, ma_resampler* pResampler) { ma_result result; @@ -33130,291 +37737,75 @@ MA_API ma_result ma_resampler_init(const ma_resampler_config* pConfig, ma_resamp return MA_INVALID_ARGS; } - if (pConfig->format != ma_format_f32 && pConfig->format != ma_format_s16) { - return MA_INVALID_ARGS; + pResampler->_pHeap = pHeap; + pResampler->format = pConfig->format; + pResampler->channels = pConfig->channels; + pResampler->sampleRateIn = pConfig->sampleRateIn; + pResampler->sampleRateOut = pConfig->sampleRateOut; + + result = ma_resampler_get_vtable(pConfig, pResampler, &pResampler->pBackendVTable, &pResampler->pBackendUserData); + if (result != MA_SUCCESS) { + return result; } - pResampler->config = *pConfig; + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onInit == NULL) { + return MA_NOT_IMPLEMENTED; /* onInit not implemented. */ + } - switch (pConfig->algorithm) - { - case ma_resample_algorithm_linear: - { - ma_linear_resampler_config linearConfig; - linearConfig = ma_linear_resampler_config_init(pConfig->format, pConfig->channels, pConfig->sampleRateIn, pConfig->sampleRateOut); - linearConfig.lpfOrder = pConfig->linear.lpfOrder; - linearConfig.lpfNyquistFactor = pConfig->linear.lpfNyquistFactor; - - result = ma_linear_resampler_init(&linearConfig, &pResampler->state.linear); - if (result != MA_SUCCESS) { - return result; - } - } break; - - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - int speexErr; - pResampler->state.speex.pSpeexResamplerState = speex_resampler_init(pConfig->channels, pConfig->sampleRateIn, pConfig->sampleRateOut, pConfig->speex.quality, &speexErr); - if (pResampler->state.speex.pSpeexResamplerState == NULL) { - return ma_result_from_speex_err(speexErr); - } - #else - /* Speex resampler not available. */ - return MA_NO_BACKEND; - #endif - } break; - - default: return MA_INVALID_ARGS; + result = pResampler->pBackendVTable->onInit(pResampler->pBackendUserData, pConfig, pHeap, &pResampler->pBackend); + if (result != MA_SUCCESS) { + return result; } return MA_SUCCESS; } -MA_API void ma_resampler_uninit(ma_resampler* pResampler) +MA_API ma_result ma_resampler_init(const ma_resampler_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_resampler* pResampler) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_resampler_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_resampler_init_preallocated(pConfig, pHeap, pResampler); + if (result != MA_SUCCESS) { + return result; + } + + pResampler->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_resampler_uninit(ma_resampler* pResampler, const ma_allocation_callbacks* pAllocationCallbacks) { if (pResampler == NULL) { return; } - if (pResampler->config.algorithm == ma_resample_algorithm_linear) { - ma_linear_resampler_uninit(&pResampler->state.linear); + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onUninit == NULL) { + return; } -#if defined(MA_HAS_SPEEX_RESAMPLER) - if (pResampler->config.algorithm == ma_resample_algorithm_speex) { - speex_resampler_destroy((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState); + pResampler->pBackendVTable->onUninit(pResampler->pBackendUserData, pResampler->pBackend, pAllocationCallbacks); + + if (pResampler->_ownsHeap) { + ma_free(pResampler->_pHeap, pAllocationCallbacks); } -#endif } -static ma_result ma_resampler_process_pcm_frames__read__linear(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) -{ - return ma_linear_resampler_process_pcm_frames(&pResampler->state.linear, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); -} - -#if defined(MA_HAS_SPEEX_RESAMPLER) -static ma_result ma_resampler_process_pcm_frames__read__speex(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) -{ - int speexErr; - ma_uint64 frameCountOut; - ma_uint64 frameCountIn; - ma_uint64 framesProcessedOut; - ma_uint64 framesProcessedIn; - unsigned int framesPerIteration = UINT_MAX; - - MA_ASSERT(pResampler != NULL); - MA_ASSERT(pFramesOut != NULL); - MA_ASSERT(pFrameCountOut != NULL); - MA_ASSERT(pFrameCountIn != NULL); - - /* - Reading from the Speex resampler requires a bit of dancing around for a few reasons. The first thing is that it's frame counts - are in unsigned int's whereas ours is in ma_uint64. We therefore need to run the conversion in a loop. The other, more complicated - problem, is that we need to keep track of the input time, similar to what we do with the linear resampler. The reason we need to - do this is for ma_resampler_get_required_input_frame_count() and ma_resampler_get_expected_output_frame_count(). - */ - frameCountOut = *pFrameCountOut; - frameCountIn = *pFrameCountIn; - framesProcessedOut = 0; - framesProcessedIn = 0; - - while (framesProcessedOut < frameCountOut && framesProcessedIn < frameCountIn) { - unsigned int frameCountInThisIteration; - unsigned int frameCountOutThisIteration; - const void* pFramesInThisIteration; - void* pFramesOutThisIteration; - - frameCountInThisIteration = framesPerIteration; - if ((ma_uint64)frameCountInThisIteration > (frameCountIn - framesProcessedIn)) { - frameCountInThisIteration = (unsigned int)(frameCountIn - framesProcessedIn); - } - - frameCountOutThisIteration = framesPerIteration; - if ((ma_uint64)frameCountOutThisIteration > (frameCountOut - framesProcessedOut)) { - frameCountOutThisIteration = (unsigned int)(frameCountOut - framesProcessedOut); - } - - pFramesInThisIteration = ma_offset_ptr(pFramesIn, framesProcessedIn * ma_get_bytes_per_frame(pResampler->config.format, pResampler->config.channels)); - pFramesOutThisIteration = ma_offset_ptr(pFramesOut, framesProcessedOut * ma_get_bytes_per_frame(pResampler->config.format, pResampler->config.channels)); - - if (pResampler->config.format == ma_format_f32) { - speexErr = speex_resampler_process_interleaved_float((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState, (const float*)pFramesInThisIteration, &frameCountInThisIteration, (float*)pFramesOutThisIteration, &frameCountOutThisIteration); - } else if (pResampler->config.format == ma_format_s16) { - speexErr = speex_resampler_process_interleaved_int((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState, (const spx_int16_t*)pFramesInThisIteration, &frameCountInThisIteration, (spx_int16_t*)pFramesOutThisIteration, &frameCountOutThisIteration); - } else { - /* Format not supported. Should never get here. */ - MA_ASSERT(MA_FALSE); - return MA_INVALID_OPERATION; - } - - if (speexErr != RESAMPLER_ERR_SUCCESS) { - return ma_result_from_speex_err(speexErr); - } - - framesProcessedIn += frameCountInThisIteration; - framesProcessedOut += frameCountOutThisIteration; - } - - *pFrameCountOut = framesProcessedOut; - *pFrameCountIn = framesProcessedIn; - - return MA_SUCCESS; -} -#endif - -static ma_result ma_resampler_process_pcm_frames__read(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) -{ - MA_ASSERT(pResampler != NULL); - MA_ASSERT(pFramesOut != NULL); - - /* pFramesOut is not NULL, which means we must have a capacity. */ - if (pFrameCountOut == NULL) { - return MA_INVALID_ARGS; - } - - /* It doesn't make sense to not have any input frames to process. */ - if (pFrameCountIn == NULL || pFramesIn == NULL) { - return MA_INVALID_ARGS; - } - - switch (pResampler->config.algorithm) - { - case ma_resample_algorithm_linear: - { - return ma_resampler_process_pcm_frames__read__linear(pResampler, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } - - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - return ma_resampler_process_pcm_frames__read__speex(pResampler, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - #else - break; - #endif - } - - default: break; - } - - /* Should never get here. */ - MA_ASSERT(MA_FALSE); - return MA_INVALID_ARGS; -} - - -static ma_result ma_resampler_process_pcm_frames__seek__linear(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, ma_uint64* pFrameCountOut) -{ - MA_ASSERT(pResampler != NULL); - - /* Seeking is supported natively by the linear resampler. */ - return ma_linear_resampler_process_pcm_frames(&pResampler->state.linear, pFramesIn, pFrameCountIn, NULL, pFrameCountOut); -} - -#if defined(MA_HAS_SPEEX_RESAMPLER) -static ma_result ma_resampler_process_pcm_frames__seek__speex(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, ma_uint64* pFrameCountOut) -{ - /* The generic seek method is implemented in on top of ma_resampler_process_pcm_frames__read() by just processing into a dummy buffer. */ - float devnull[4096]; - ma_uint64 totalOutputFramesToProcess; - ma_uint64 totalOutputFramesProcessed; - ma_uint64 totalInputFramesProcessed; - ma_uint32 bpf; - ma_result result; - - MA_ASSERT(pResampler != NULL); - - totalOutputFramesProcessed = 0; - totalInputFramesProcessed = 0; - bpf = ma_get_bytes_per_frame(pResampler->config.format, pResampler->config.channels); - - if (pFrameCountOut != NULL) { - /* Seek by output frames. */ - totalOutputFramesToProcess = *pFrameCountOut; - } else { - /* Seek by input frames. */ - MA_ASSERT(pFrameCountIn != NULL); - totalOutputFramesToProcess = ma_resampler_get_expected_output_frame_count(pResampler, *pFrameCountIn); - } - - if (pFramesIn != NULL) { - /* Process input data. */ - MA_ASSERT(pFrameCountIn != NULL); - while (totalOutputFramesProcessed < totalOutputFramesToProcess && totalInputFramesProcessed < *pFrameCountIn) { - ma_uint64 inputFramesToProcessThisIteration = (*pFrameCountIn - totalInputFramesProcessed); - ma_uint64 outputFramesToProcessThisIteration = (totalOutputFramesToProcess - totalOutputFramesProcessed); - if (outputFramesToProcessThisIteration > sizeof(devnull) / bpf) { - outputFramesToProcessThisIteration = sizeof(devnull) / bpf; - } - - result = ma_resampler_process_pcm_frames__read(pResampler, ma_offset_ptr(pFramesIn, totalInputFramesProcessed*bpf), &inputFramesToProcessThisIteration, ma_offset_ptr(devnull, totalOutputFramesProcessed*bpf), &outputFramesToProcessThisIteration); - if (result != MA_SUCCESS) { - return result; - } - - totalOutputFramesProcessed += outputFramesToProcessThisIteration; - totalInputFramesProcessed += inputFramesToProcessThisIteration; - } - } else { - /* Don't process input data - just update timing and filter state as if zeroes were passed in. */ - while (totalOutputFramesProcessed < totalOutputFramesToProcess) { - ma_uint64 inputFramesToProcessThisIteration = 16384; - ma_uint64 outputFramesToProcessThisIteration = (totalOutputFramesToProcess - totalOutputFramesProcessed); - if (outputFramesToProcessThisIteration > sizeof(devnull) / bpf) { - outputFramesToProcessThisIteration = sizeof(devnull) / bpf; - } - - result = ma_resampler_process_pcm_frames__read(pResampler, NULL, &inputFramesToProcessThisIteration, ma_offset_ptr(devnull, totalOutputFramesProcessed*bpf), &outputFramesToProcessThisIteration); - if (result != MA_SUCCESS) { - return result; - } - - totalOutputFramesProcessed += outputFramesToProcessThisIteration; - totalInputFramesProcessed += inputFramesToProcessThisIteration; - } - } - - - if (pFrameCountIn != NULL) { - *pFrameCountIn = totalInputFramesProcessed; - } - if (pFrameCountOut != NULL) { - *pFrameCountOut = totalOutputFramesProcessed; - } - - return MA_SUCCESS; -} -#endif - -static ma_result ma_resampler_process_pcm_frames__seek(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, ma_uint64* pFrameCountOut) -{ - MA_ASSERT(pResampler != NULL); - - switch (pResampler->config.algorithm) - { - case ma_resample_algorithm_linear: - { - return ma_resampler_process_pcm_frames__seek__linear(pResampler, pFramesIn, pFrameCountIn, pFrameCountOut); - } break; - - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - return ma_resampler_process_pcm_frames__seek__speex(pResampler, pFramesIn, pFrameCountIn, pFrameCountOut); - #else - break; - #endif - }; - - default: break; - } - - /* Should never hit this. */ - MA_ASSERT(MA_FALSE); - return MA_INVALID_ARGS; -} - - MA_API ma_result ma_resampler_process_pcm_frames(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) { if (pResampler == NULL) { @@ -33425,17 +37816,17 @@ MA_API ma_result ma_resampler_process_pcm_frames(ma_resampler* pResampler, const return MA_INVALID_ARGS; } - if (pFramesOut != NULL) { - /* Reading. */ - return ma_resampler_process_pcm_frames__read(pResampler, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } else { - /* Seeking. */ - return ma_resampler_process_pcm_frames__seek(pResampler, pFramesIn, pFrameCountIn, pFrameCountOut); + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onProcess == NULL) { + return MA_NOT_IMPLEMENTED; } + + return pResampler->pBackendVTable->onProcess(pResampler->pBackendUserData, pResampler->pBackend, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); } MA_API ma_result ma_resampler_set_rate(ma_resampler* pResampler, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut) { + ma_result result; + if (pResampler == NULL) { return MA_INVALID_ARGS; } @@ -33444,137 +37835,40 @@ MA_API ma_result ma_resampler_set_rate(ma_resampler* pResampler, ma_uint32 sampl return MA_INVALID_ARGS; } - pResampler->config.sampleRateIn = sampleRateIn; - pResampler->config.sampleRateOut = sampleRateOut; - - switch (pResampler->config.algorithm) - { - case ma_resample_algorithm_linear: - { - return ma_linear_resampler_set_rate(&pResampler->state.linear, sampleRateIn, sampleRateOut); - } break; - - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - return ma_result_from_speex_err(speex_resampler_set_rate((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState, sampleRateIn, sampleRateOut)); - #else - break; - #endif - }; - - default: break; + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onSetRate == NULL) { + return MA_NOT_IMPLEMENTED; } - /* Should never get here. */ - MA_ASSERT(MA_FALSE); - return MA_INVALID_OPERATION; + result = pResampler->pBackendVTable->onSetRate(pResampler->pBackendUserData, pResampler->pBackend, sampleRateIn, sampleRateOut); + if (result != MA_SUCCESS) { + return result; + } + + pResampler->sampleRateIn = sampleRateIn; + pResampler->sampleRateOut = sampleRateOut; + + return MA_SUCCESS; } MA_API ma_result ma_resampler_set_rate_ratio(ma_resampler* pResampler, float ratio) { + ma_uint32 n; + ma_uint32 d; + if (pResampler == NULL) { return MA_INVALID_ARGS; } - if (pResampler->config.algorithm == ma_resample_algorithm_linear) { - return ma_linear_resampler_set_rate_ratio(&pResampler->state.linear, ratio); - } else { - /* Getting here means the backend does not have native support for setting the rate as a ratio so we just do it generically. */ - ma_uint32 n; - ma_uint32 d; + d = 1000; + n = (ma_uint32)(ratio * d); - d = 1000; - n = (ma_uint32)(ratio * d); - - if (n == 0) { - return MA_INVALID_ARGS; /* Ratio too small. */ - } - - MA_ASSERT(n != 0); - - return ma_resampler_set_rate(pResampler, n, d); - } -} - -MA_API ma_uint64 ma_resampler_get_required_input_frame_count(const ma_resampler* pResampler, ma_uint64 outputFrameCount) -{ - if (pResampler == NULL) { - return 0; + if (n == 0) { + return MA_INVALID_ARGS; /* Ratio too small. */ } - if (outputFrameCount == 0) { - return 0; - } + MA_ASSERT(n != 0); - switch (pResampler->config.algorithm) - { - case ma_resample_algorithm_linear: - { - return ma_linear_resampler_get_required_input_frame_count(&pResampler->state.linear, outputFrameCount); - } - - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - spx_uint64_t count; - int speexErr = ma_speex_resampler_get_required_input_frame_count((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState, outputFrameCount, &count); - if (speexErr != RESAMPLER_ERR_SUCCESS) { - return 0; - } - - return (ma_uint64)count; - #else - break; - #endif - } - - default: break; - } - - /* Should never get here. */ - MA_ASSERT(MA_FALSE); - return 0; -} - -MA_API ma_uint64 ma_resampler_get_expected_output_frame_count(const ma_resampler* pResampler, ma_uint64 inputFrameCount) -{ - if (pResampler == NULL) { - return 0; /* Invalid args. */ - } - - if (inputFrameCount == 0) { - return 0; - } - - switch (pResampler->config.algorithm) - { - case ma_resample_algorithm_linear: - { - return ma_linear_resampler_get_expected_output_frame_count(&pResampler->state.linear, inputFrameCount); - } - - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - spx_uint64_t count; - int speexErr = ma_speex_resampler_get_expected_output_frame_count((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState, inputFrameCount, &count); - if (speexErr != RESAMPLER_ERR_SUCCESS) { - return 0; - } - - return (ma_uint64)count; - #else - break; - #endif - } - - default: break; - } - - /* Should never get here. */ - MA_ASSERT(MA_FALSE); - return 0; + return ma_resampler_set_rate(pResampler, n, d); } MA_API ma_uint64 ma_resampler_get_input_latency(const ma_resampler* pResampler) @@ -33583,28 +37877,11 @@ MA_API ma_uint64 ma_resampler_get_input_latency(const ma_resampler* pResampler) return 0; } - switch (pResampler->config.algorithm) - { - case ma_resample_algorithm_linear: - { - return ma_linear_resampler_get_input_latency(&pResampler->state.linear); - } - - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - return (ma_uint64)ma_speex_resampler_get_input_latency((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState); - #else - break; - #endif - } - - default: break; + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onGetInputLatency == NULL) { + return 0; } - /* Should never get here. */ - MA_ASSERT(MA_FALSE); - return 0; + return pResampler->pBackendVTable->onGetInputLatency(pResampler->pBackendUserData, pResampler->pBackend); } MA_API ma_uint64 ma_resampler_get_output_latency(const ma_resampler* pResampler) @@ -33613,28 +37890,49 @@ MA_API ma_uint64 ma_resampler_get_output_latency(const ma_resampler* pResampler) return 0; } - switch (pResampler->config.algorithm) - { - case ma_resample_algorithm_linear: - { - return ma_linear_resampler_get_output_latency(&pResampler->state.linear); - } - - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - return (ma_uint64)ma_speex_resampler_get_output_latency((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState); - #else - break; - #endif - } - - default: break; + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onGetOutputLatency == NULL) { + return 0; } - /* Should never get here. */ - MA_ASSERT(MA_FALSE); - return 0; + return pResampler->pBackendVTable->onGetOutputLatency(pResampler->pBackendUserData, pResampler->pBackend); +} + +MA_API ma_result ma_resampler_get_required_input_frame_count(const ma_resampler* pResampler, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount) +{ + if (pInputFrameCount == NULL) { + return MA_INVALID_ARGS; + } + + *pInputFrameCount = 0; + + if (pResampler == NULL) { + return MA_INVALID_ARGS; + } + + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onGetRequiredInputFrameCount == NULL) { + return MA_NOT_IMPLEMENTED; + } + + return pResampler->pBackendVTable->onGetRequiredInputFrameCount(pResampler->pBackendUserData, pResampler->pBackend, outputFrameCount, pInputFrameCount); +} + +MA_API ma_result ma_resampler_get_expected_output_frame_count(const ma_resampler* pResampler, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount) +{ + if (pOutputFrameCount == NULL) { + return MA_INVALID_ARGS; + } + + *pOutputFrameCount = 0; + + if (pResampler == NULL) { + return MA_INVALID_ARGS; + } + + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onGetExpectedOutputFrameCount == NULL) { + return MA_NOT_IMPLEMENTED; + } + + return pResampler->pBackendVTable->onGetExpectedOutputFrameCount(pResampler->pBackendUserData, pResampler->pBackend, inputFrameCount, pOutputFrameCount); } /************************************************************************************************************************************************************** @@ -33755,17 +38053,13 @@ MA_API ma_channel_converter_config ma_channel_converter_config_init(ma_format fo { ma_channel_converter_config config; - /* Channel counts need to be clamped. */ - channelsIn = ma_min(channelsIn, ma_countof(config.channelMapIn)); - channelsOut = ma_min(channelsOut, ma_countof(config.channelMapOut)); - MA_ZERO_OBJECT(&config); - config.format = format; - config.channelsIn = channelsIn; - config.channelsOut = channelsOut; - ma_channel_map_copy_or_default(config.channelMapIn, pChannelMapIn, channelsIn); - ma_channel_map_copy_or_default(config.channelMapOut, pChannelMapOut, channelsOut); - config.mixingMode = mixingMode; + config.format = format; + config.channelsIn = channelsIn; + config.channelsOut = channelsOut; + config.pChannelMapIn = pChannelMapIn; + config.pChannelMapOut = pChannelMapOut; + config.mixingMode = mixingMode; return config; } @@ -33796,320 +38090,862 @@ static ma_bool32 ma_is_spatial_channel_position(ma_channel channelPosition) return MA_FALSE; } -MA_API ma_result ma_channel_converter_init(const ma_channel_converter_config* pConfig, ma_channel_converter* pConverter) + +static ma_bool32 ma_channel_map_is_passthrough(const ma_channel* pChannelMapIn, ma_uint32 channelsIn, const ma_channel* pChannelMapOut, ma_uint32 channelsOut) +{ + if (channelsOut == channelsIn) { + return ma_channel_map_is_equal(pChannelMapOut, pChannelMapIn, channelsOut); + } else { + return MA_FALSE; /* Channel counts differ, so cannot be a passthrough. */ + } +} + +static ma_channel_conversion_path ma_channel_map_get_conversion_path(const ma_channel* pChannelMapIn, ma_uint32 channelsIn, const ma_channel* pChannelMapOut, ma_uint32 channelsOut, ma_channel_mix_mode mode) +{ + if (ma_channel_map_is_passthrough(pChannelMapIn, channelsIn, pChannelMapOut, channelsOut)) { + return ma_channel_conversion_path_passthrough; + } + + if (channelsOut == 1 && (pChannelMapOut == NULL || pChannelMapOut[0] == MA_CHANNEL_MONO)) { + return ma_channel_conversion_path_mono_out; + } + + if (channelsIn == 1 && (pChannelMapIn == NULL || pChannelMapIn[0] == MA_CHANNEL_MONO)) { + return ma_channel_conversion_path_mono_in; + } + + if (mode == ma_channel_mix_mode_custom_weights) { + return ma_channel_conversion_path_weights; + } + + /* + We can use a simple shuffle if both channel maps have the same channel count and all channel + positions are present in both. + */ + if (channelsIn == channelsOut) { + ma_uint32 iChannelIn; + ma_bool32 areAllChannelPositionsPresent = MA_TRUE; + for (iChannelIn = 0; iChannelIn < channelsIn; ++iChannelIn) { + ma_bool32 isInputChannelPositionInOutput = MA_FALSE; + if (ma_channel_map_contains_channel_position(channelsOut, pChannelMapOut, ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn))) { + isInputChannelPositionInOutput = MA_TRUE; + break; + } + + if (!isInputChannelPositionInOutput) { + areAllChannelPositionsPresent = MA_FALSE; + break; + } + } + + if (areAllChannelPositionsPresent) { + return ma_channel_conversion_path_shuffle; + } + } + + /* Getting here means we'll need to use weights. */ + return ma_channel_conversion_path_weights; +} + + +static ma_result ma_channel_map_build_shuffle_table(const ma_channel* pChannelMapIn, ma_uint32 channelCountIn, const ma_channel* pChannelMapOut, ma_uint32 channelCountOut, ma_uint8* pShuffleTable) { ma_uint32 iChannelIn; ma_uint32 iChannelOut; + if (pShuffleTable == NULL || channelCountIn == 0 || channelCountOut == 0) { + return MA_INVALID_ARGS; + } + + /* + When building the shuffle table we just do a 1:1 mapping based on the first occurance of a channel. If the + input channel has more than one occurance of a channel position, the second one will be ignored. + */ + for (iChannelOut = 0; iChannelOut < channelCountOut; iChannelOut += 1) { + ma_channel channelOut; + + /* Default to MA_CHANNEL_INDEX_NULL so that if a mapping is not found it'll be set appropriately. */ + pShuffleTable[iChannelOut] = MA_CHANNEL_INDEX_NULL; + + channelOut = ma_channel_map_get_channel(pChannelMapOut, channelCountOut, iChannelOut); + for (iChannelIn = 0; iChannelIn < channelCountIn; iChannelIn += 1) { + ma_channel channelIn; + + channelIn = ma_channel_map_get_channel(pChannelMapIn, channelCountIn, iChannelIn); + if (channelOut == channelIn) { + pShuffleTable[iChannelOut] = (ma_uint8)iChannelIn; + break; + } + + /* + Getting here means the channels don't exactly match, but we are going to support some + relaxed matching for practicality. If, for example, there are two stereo channel maps, + but one uses front left/right and the other uses side left/right, it makes logical + sense to just map these. The way we'll do it is we'll check if there is a logical + corresponding mapping, and if so, apply it, but we will *not* break from the loop, + thereby giving the loop a chance to find an exact match later which will take priority. + */ + switch (channelOut) + { + /* Left channels. */ + case MA_CHANNEL_FRONT_LEFT: + case MA_CHANNEL_SIDE_LEFT: + { + switch (channelIn) { + case MA_CHANNEL_FRONT_LEFT: + case MA_CHANNEL_SIDE_LEFT: + { + pShuffleTable[iChannelOut] = (ma_uint8)iChannelIn; + } break; + } + } break; + + /* Right channels. */ + case MA_CHANNEL_FRONT_RIGHT: + case MA_CHANNEL_SIDE_RIGHT: + { + switch (channelIn) { + case MA_CHANNEL_FRONT_RIGHT: + case MA_CHANNEL_SIDE_RIGHT: + { + pShuffleTable[iChannelOut] = (ma_uint8)iChannelIn; + } break; + } + } break; + + default: break; + } + } + } + + return MA_SUCCESS; +} + + +static void ma_channel_map_apply_shuffle_table_u8(ma_uint8* pFramesOut, ma_uint32 channelsOut, const ma_uint8* pFramesIn, ma_uint32 channelsIn, ma_uint64 frameCount, const ma_uint8* pShuffleTable) +{ + ma_uint64 iFrame; + ma_uint32 iChannelOut; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_uint8 iChannelIn = pShuffleTable[iChannelOut]; + if (iChannelIn < channelsIn) { /* For safety, and to deal with MA_CHANNEL_INDEX_NULL. */ + pFramesOut[iChannelOut] = pFramesIn[iChannelIn]; + } else { + pFramesOut[iChannelOut] = 0; + } + } + + pFramesOut += channelsOut; + pFramesIn += channelsIn; + } +} + +static void ma_channel_map_apply_shuffle_table_s16(ma_int16* pFramesOut, ma_uint32 channelsOut, const ma_int16* pFramesIn, ma_uint32 channelsIn, ma_uint64 frameCount, const ma_uint8* pShuffleTable) +{ + ma_uint64 iFrame; + ma_uint32 iChannelOut; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_uint8 iChannelIn = pShuffleTable[iChannelOut]; + if (iChannelIn < channelsIn) { /* For safety, and to deal with MA_CHANNEL_INDEX_NULL. */ + pFramesOut[iChannelOut] = pFramesIn[iChannelIn]; + } else { + pFramesOut[iChannelOut] = 0; + } + } + + pFramesOut += channelsOut; + pFramesIn += channelsIn; + } +} + +static void ma_channel_map_apply_shuffle_table_s24(ma_uint8* pFramesOut, ma_uint32 channelsOut, const ma_uint8* pFramesIn, ma_uint32 channelsIn, ma_uint64 frameCount, const ma_uint8* pShuffleTable) +{ + ma_uint64 iFrame; + ma_uint32 iChannelOut; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_uint8 iChannelIn = pShuffleTable[iChannelOut]; + if (iChannelIn < channelsIn) { /* For safety, and to deal with MA_CHANNEL_INDEX_NULL. */ + pFramesOut[iChannelOut*3 + 0] = pFramesIn[iChannelIn*3 + 0]; + pFramesOut[iChannelOut*3 + 1] = pFramesIn[iChannelIn*3 + 1]; + pFramesOut[iChannelOut*3 + 2] = pFramesIn[iChannelIn*3 + 2]; + } else { + pFramesOut[iChannelOut*3 + 0] = 0; + } pFramesOut[iChannelOut*3 + 1] = 0; + } pFramesOut[iChannelOut*3 + 2] = 0; + + pFramesOut += channelsOut*3; + pFramesIn += channelsIn*3; + } +} + +static void ma_channel_map_apply_shuffle_table_s32(ma_int32* pFramesOut, ma_uint32 channelsOut, const ma_int32* pFramesIn, ma_uint32 channelsIn, ma_uint64 frameCount, const ma_uint8* pShuffleTable) +{ + ma_uint64 iFrame; + ma_uint32 iChannelOut; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_uint8 iChannelIn = pShuffleTable[iChannelOut]; + if (iChannelIn < channelsIn) { /* For safety, and to deal with MA_CHANNEL_INDEX_NULL. */ + pFramesOut[iChannelOut] = pFramesIn[iChannelIn]; + } else { + pFramesOut[iChannelOut] = 0; + } + } + + pFramesOut += channelsOut; + pFramesIn += channelsIn; + } +} + +static void ma_channel_map_apply_shuffle_table_f32(float* pFramesOut, ma_uint32 channelsOut, const float* pFramesIn, ma_uint32 channelsIn, ma_uint64 frameCount, const ma_uint8* pShuffleTable) +{ + ma_uint64 iFrame; + ma_uint32 iChannelOut; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_uint8 iChannelIn = pShuffleTable[iChannelOut]; + if (iChannelIn < channelsIn) { /* For safety, and to deal with MA_CHANNEL_INDEX_NULL. */ + pFramesOut[iChannelOut] = pFramesIn[iChannelIn]; + } else { + pFramesOut[iChannelOut] = 0; + } + } + + pFramesOut += channelsOut; + pFramesIn += channelsIn; + } +} + +static ma_result ma_channel_map_apply_shuffle_table(void* pFramesOut, ma_uint32 channelsOut, const void* pFramesIn, ma_uint32 channelsIn, ma_uint64 frameCount, const ma_uint8* pShuffleTable, ma_format format) +{ + if (pFramesOut == NULL || pFramesIn == NULL || channelsOut == 0 || pShuffleTable == NULL) { + return MA_INVALID_ARGS; + } + + switch (format) + { + case ma_format_u8: + { + ma_channel_map_apply_shuffle_table_u8((ma_uint8*)pFramesOut, channelsOut, (const ma_uint8*)pFramesIn, channelsIn, frameCount, pShuffleTable); + } break; + + case ma_format_s16: + { + ma_channel_map_apply_shuffle_table_s16((ma_int16*)pFramesOut, channelsOut, (const ma_int16*)pFramesIn, channelsIn, frameCount, pShuffleTable); + } break; + + case ma_format_s24: + { + ma_channel_map_apply_shuffle_table_s24((ma_uint8*)pFramesOut, channelsOut, (const ma_uint8*)pFramesIn, channelsIn, frameCount, pShuffleTable); + } break; + + case ma_format_s32: + { + ma_channel_map_apply_shuffle_table_s32((ma_int32*)pFramesOut, channelsOut, (const ma_int32*)pFramesIn, channelsIn, frameCount, pShuffleTable); + } break; + + case ma_format_f32: + { + ma_channel_map_apply_shuffle_table_f32((float*)pFramesOut, channelsOut, (const float*)pFramesIn, channelsIn, frameCount, pShuffleTable); + } break; + + default: return MA_INVALID_ARGS; /* Unknown format. */ + } + + return MA_SUCCESS; +} + +static ma_result ma_channel_map_apply_mono_out_f32(float* pFramesOut, const float* pFramesIn, const ma_channel* pChannelMapIn, ma_uint32 channelsIn, ma_uint64 frameCount) +{ + ma_uint64 iFrame; + ma_uint32 iChannelIn; + ma_uint32 accumulationCount; + + if (pFramesOut == NULL || pFramesIn == NULL || channelsIn == 0) { + return MA_INVALID_ARGS; + } + + /* In this case the output stream needs to be the average of all channels, ignoring NONE. */ + + /* A quick pre-processing step to get the accumulation counter since we're ignoring NONE channels. */ + accumulationCount = 0; + for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) { + if (ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn) != MA_CHANNEL_NONE) { + accumulationCount += 1; + } + } + + if (accumulationCount > 0) { /* <-- Prevent a division by zero. */ + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + float accumulation = 0; + + for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) { + ma_channel channelIn = ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn); + if (channelIn != MA_CHANNEL_NONE) { + accumulation += pFramesIn[iChannelIn]; + } + } + + pFramesOut[0] = accumulation / accumulationCount; + pFramesOut += 1; + pFramesIn += channelsIn; + } + } else { + ma_silence_pcm_frames(pFramesOut, frameCount, ma_format_f32, 1); + } + + return MA_SUCCESS; +} + +static ma_result ma_channel_map_apply_mono_in_f32(float* pFramesOut, const ma_channel* pChannelMapOut, ma_uint32 channelsOut, const float* pFramesIn, ma_uint64 frameCount, ma_mono_expansion_mode monoExpansionMode) +{ + ma_uint64 iFrame; + ma_uint32 iChannelOut; + + if (pFramesOut == NULL || channelsOut == 0 || pFramesIn == NULL) { + return MA_INVALID_ARGS; + } + + /* Note that the MA_CHANNEL_NONE channel must be ignored in all cases. */ + switch (monoExpansionMode) + { + case ma_mono_expansion_mode_average: + { + float weight; + ma_uint32 validChannelCount = 0; + + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + if (channelOut != MA_CHANNEL_NONE) { + validChannelCount += 1; + } + } + + weight = 1.0f / validChannelCount; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + if (channelOut != MA_CHANNEL_NONE) { + pFramesOut[iChannelOut] = pFramesIn[0] * weight; + } + } + + pFramesOut += channelsOut; + pFramesIn += 1; + } + } break; + + case ma_mono_expansion_mode_stereo_only: + { + if (channelsOut >= 2) { + ma_uint32 iChannelLeft = (ma_uint32)-1; + ma_uint32 iChannelRight = (ma_uint32)-1; + + /* + We first need to find our stereo channels. We prefer front-left and front-right, but + if they're not available, we'll also try side-left and side-right. If neither are + available we'll fall through to the default case below. + */ + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + if (channelOut == MA_CHANNEL_SIDE_LEFT) { + iChannelLeft = iChannelOut; + } + if (channelOut == MA_CHANNEL_SIDE_RIGHT) { + iChannelRight = iChannelOut; + } + } + + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + if (channelOut == MA_CHANNEL_FRONT_LEFT) { + iChannelLeft = iChannelOut; + } + if (channelOut == MA_CHANNEL_FRONT_RIGHT) { + iChannelRight = iChannelOut; + } + } + + + if (iChannelLeft != (ma_uint32)-1 && iChannelRight != (ma_uint32)-1) { + /* We found our stereo channels so we can duplicate the signal across those channels. */ + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + if (channelOut != MA_CHANNEL_NONE) { + if (iChannelOut == iChannelLeft || iChannelOut == iChannelRight) { + pFramesOut[iChannelOut] = pFramesIn[0]; + } else { + pFramesOut[iChannelOut] = 0.0f; + } + } + } + + pFramesOut += channelsOut; + pFramesIn += 1; + } + + break; /* Get out of the switch. */ + } else { + /* Fallthrough. Does not have left and right channels. */ + goto default_handler; + } + } else { + /* Fallthrough. Does not have stereo channels. */ + goto default_handler; + } + }; /* Fallthrough. See comments above. */ + + case ma_mono_expansion_mode_duplicate: + default: + { + default_handler: + { + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + if (channelOut != MA_CHANNEL_NONE) { + pFramesOut[iChannelOut] = pFramesIn[0]; + } + } + + pFramesOut += channelsOut; + pFramesIn += 1; + } + } + } break; + } + + return MA_SUCCESS; +} + +static void ma_channel_map_apply_f32(float* pFramesOut, const ma_channel* pChannelMapOut, ma_uint32 channelsOut, const float* pFramesIn, const ma_channel* pChannelMapIn, ma_uint32 channelsIn, ma_uint64 frameCount, ma_channel_mix_mode mode, ma_mono_expansion_mode monoExpansionMode) +{ + ma_channel_conversion_path conversionPath = ma_channel_map_get_conversion_path(pChannelMapIn, channelsIn, pChannelMapOut, channelsOut, mode); + + /* Optimized Path: Passthrough */ + if (conversionPath == ma_channel_conversion_path_passthrough) { + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, ma_format_f32, channelsOut); + return; + } + + /* Special Path: Mono Output. */ + if (conversionPath == ma_channel_conversion_path_mono_out) { + ma_channel_map_apply_mono_out_f32(pFramesOut, pFramesIn, pChannelMapIn, channelsIn, frameCount); + return; + } + + /* Special Path: Mono Input. */ + if (conversionPath == ma_channel_conversion_path_mono_in) { + ma_channel_map_apply_mono_in_f32(pFramesOut, pChannelMapOut, channelsOut, pFramesIn, frameCount, monoExpansionMode); + return; + } + + /* Getting here means we aren't running on an optimized conversion path. */ + if (channelsOut <= MA_MAX_CHANNELS) { + ma_result result; + + if (mode == ma_channel_mix_mode_simple) { + ma_channel shuffleTable[MA_MAX_CHANNELS]; + + result = ma_channel_map_build_shuffle_table(pChannelMapIn, channelsIn, pChannelMapOut, channelsOut, shuffleTable); + if (result != MA_SUCCESS) { + return; + } + + result = ma_channel_map_apply_shuffle_table(pFramesOut, channelsOut, pFramesIn, channelsIn, frameCount, shuffleTable, ma_format_f32); + if (result != MA_SUCCESS) { + return; + } + } else { + ma_uint32 iFrame; + ma_uint32 iChannelOut; + ma_uint32 iChannelIn; + float weights[32][32]; /* Do not use MA_MAX_CHANNELS here! */ + + /* + If we have a small enough number of channels, pre-compute the weights. Otherwise we'll just need to + fall back to a slower path because otherwise we'll run out of stack space. + */ + if (channelsIn <= ma_countof(weights) && channelsOut <= ma_countof(weights)) { + /* Pre-compute weights. */ + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) { + ma_channel channelIn = ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn); + weights[iChannelOut][iChannelIn] = ma_calculate_channel_position_rectangular_weight(channelOut, channelIn); + } + } + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + float accumulation = 0; + + for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) { + accumulation += pFramesIn[iChannelIn] * weights[iChannelOut][iChannelIn]; + } + + pFramesOut[iChannelOut] = accumulation; + } + + pFramesOut += channelsOut; + pFramesIn += channelsIn; + } + } else { + /* Cannot pre-compute weights because not enough room in stack-allocated buffer. */ + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + float accumulation = 0; + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + + for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) { + ma_channel channelIn = ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn); + accumulation += pFramesIn[iChannelIn] * ma_calculate_channel_position_rectangular_weight(channelOut, channelIn); + } + + pFramesOut[iChannelOut] = accumulation; + } + + pFramesOut += channelsOut; + pFramesIn += channelsIn; + } + } + } + } else { + /* Fall back to silence. If you hit this, what are you doing with so many channels?! */ + ma_silence_pcm_frames(pFramesOut, frameCount, ma_format_f32, channelsOut); + } +} + + +typedef struct +{ + size_t sizeInBytes; + size_t channelMapInOffset; + size_t channelMapOutOffset; + size_t shuffleTableOffset; + size_t weightsOffset; +} ma_channel_converter_heap_layout; + +static ma_channel_conversion_path ma_channel_converter_config_get_conversion_path(const ma_channel_converter_config* pConfig) +{ + return ma_channel_map_get_conversion_path(pConfig->pChannelMapIn, pConfig->channelsIn, pConfig->pChannelMapOut, pConfig->channelsOut, pConfig->mixingMode); +} + +static ma_result ma_channel_converter_get_heap_layout(const ma_channel_converter_config* pConfig, ma_channel_converter_heap_layout* pHeapLayout) +{ + ma_channel_conversion_path conversionPath; + + MA_ASSERT(pHeapLayout != NULL); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channelsIn == 0 || pConfig->channelsOut == 0) { + return MA_INVALID_ARGS; + } + + if (!ma_channel_map_is_valid(pConfig->pChannelMapIn, pConfig->channelsIn)) { + return MA_INVALID_ARGS; + } + + if (!ma_channel_map_is_valid(pConfig->pChannelMapOut, pConfig->channelsOut)) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* Input channel map. Only need to allocate this if we have an input channel map (otherwise default channel map is assumed). */ + pHeapLayout->channelMapInOffset = pHeapLayout->sizeInBytes; + if (pConfig->pChannelMapIn != NULL) { + pHeapLayout->sizeInBytes += sizeof(ma_channel) * pConfig->channelsIn; + } + + /* Output channel map. Only need to allocate this if we have an output channel map (otherwise default channel map is assumed). */ + pHeapLayout->channelMapOutOffset = pHeapLayout->sizeInBytes; + if (pConfig->pChannelMapOut != NULL) { + pHeapLayout->sizeInBytes += sizeof(ma_channel) * pConfig->channelsOut; + } + + /* Alignment for the next section. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + /* Whether or not we use weights of a shuffle table depends on the channel map themselves and the algorithm we've chosen. */ + conversionPath = ma_channel_converter_config_get_conversion_path(pConfig); + + /* Shuffle table */ + pHeapLayout->shuffleTableOffset = pHeapLayout->sizeInBytes; + if (conversionPath == ma_channel_conversion_path_shuffle) { + pHeapLayout->sizeInBytes += sizeof(ma_uint8) * pConfig->channelsOut; + } + + /* Weights */ + pHeapLayout->weightsOffset = pHeapLayout->sizeInBytes; + if (conversionPath == ma_channel_conversion_path_weights) { + pHeapLayout->sizeInBytes += sizeof(float*) * pConfig->channelsIn; + pHeapLayout->sizeInBytes += sizeof(float ) * pConfig->channelsIn * pConfig->channelsOut; + } + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_channel_converter_get_heap_size(const ma_channel_converter_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_channel_converter_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_channel_converter_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_channel_converter_init_preallocated(const ma_channel_converter_config* pConfig, void* pHeap, ma_channel_converter* pConverter) +{ + ma_result result; + ma_channel_converter_heap_layout heapLayout; + if (pConverter == NULL) { return MA_INVALID_ARGS; } MA_ZERO_OBJECT(pConverter); - if (pConfig == NULL) { - return MA_INVALID_ARGS; + result = ma_channel_converter_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; } - /* Basic validation for channel counts. */ - if (pConfig->channelsIn < MA_MIN_CHANNELS || pConfig->channelsIn > MA_MAX_CHANNELS || - pConfig->channelsOut < MA_MIN_CHANNELS || pConfig->channelsOut > MA_MAX_CHANNELS) { - return MA_INVALID_ARGS; - } - - if (!ma_channel_map_valid(pConfig->channelsIn, pConfig->channelMapIn)) { - return MA_INVALID_ARGS; /* Invalid input channel map. */ - } - if (!ma_channel_map_valid(pConfig->channelsOut, pConfig->channelMapOut)) { - return MA_INVALID_ARGS; /* Invalid output channel map. */ - } + pConverter->_pHeap = pHeap; + MA_ZERO_MEMORY(pConverter->_pHeap, heapLayout.sizeInBytes); pConverter->format = pConfig->format; pConverter->channelsIn = pConfig->channelsIn; pConverter->channelsOut = pConfig->channelsOut; - ma_channel_map_copy_or_default(pConverter->channelMapIn, pConfig->channelMapIn, pConfig->channelsIn); - ma_channel_map_copy_or_default(pConverter->channelMapOut, pConfig->channelMapOut, pConfig->channelsOut); pConverter->mixingMode = pConfig->mixingMode; - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; iChannelIn += 1) { - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - if (pConverter->format == ma_format_f32) { - pConverter->weights.f32[iChannelIn][iChannelOut] = pConfig->weights[iChannelIn][iChannelOut]; - } else { - pConverter->weights.s16[iChannelIn][iChannelOut] = ma_channel_converter_float_to_fixed(pConfig->weights[iChannelIn][iChannelOut]); + if (pConfig->pChannelMapIn != NULL) { + pConverter->pChannelMapIn = (ma_channel*)ma_offset_ptr(pHeap, heapLayout.channelMapInOffset); + ma_channel_map_copy_or_default(pConverter->pChannelMapIn, pConfig->channelsIn, pConfig->pChannelMapIn, pConfig->channelsIn); + } else { + pConverter->pChannelMapIn = NULL; /* Use default channel map. */ + } + + if (pConfig->pChannelMapOut != NULL) { + pConverter->pChannelMapOut = (ma_channel*)ma_offset_ptr(pHeap, heapLayout.channelMapOutOffset); + ma_channel_map_copy_or_default(pConverter->pChannelMapOut, pConfig->channelsOut, pConfig->pChannelMapOut, pConfig->channelsOut); + } else { + pConverter->pChannelMapOut = NULL; /* Use default channel map. */ + } + + pConverter->conversionPath = ma_channel_converter_config_get_conversion_path(pConfig); + + if (pConverter->conversionPath == ma_channel_conversion_path_shuffle) { + pConverter->pShuffleTable = (ma_uint8*)ma_offset_ptr(pHeap, heapLayout.shuffleTableOffset); + ma_channel_map_build_shuffle_table(pConverter->pChannelMapIn, pConverter->channelsIn, pConverter->pChannelMapOut, pConverter->channelsOut, pConverter->pShuffleTable); + } + + if (pConverter->conversionPath == ma_channel_conversion_path_weights) { + ma_uint32 iChannelIn; + ma_uint32 iChannelOut; + + if (pConverter->format == ma_format_f32) { + pConverter->weights.f32 = (float** )ma_offset_ptr(pHeap, heapLayout.weightsOffset); + for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; iChannelIn += 1) { + pConverter->weights.f32[iChannelIn] = (float*)ma_offset_ptr(pHeap, heapLayout.weightsOffset + ((sizeof(float*) * pConverter->channelsIn) + (sizeof(float) * pConverter->channelsOut * iChannelIn))); + } + } else { + pConverter->weights.s16 = (ma_int32**)ma_offset_ptr(pHeap, heapLayout.weightsOffset); + for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; iChannelIn += 1) { + pConverter->weights.s16[iChannelIn] = (ma_int32*)ma_offset_ptr(pHeap, heapLayout.weightsOffset + ((sizeof(ma_int32*) * pConverter->channelsIn) + (sizeof(ma_int32) * pConverter->channelsOut * iChannelIn))); } } - } - - - /* If the input and output channels and channel maps are the same we should use a passthrough. */ - if (pConverter->channelsIn == pConverter->channelsOut) { - if (ma_channel_map_equal(pConverter->channelsIn, pConverter->channelMapIn, pConverter->channelMapOut)) { - pConverter->isPassthrough = MA_TRUE; - } - if (ma_channel_map_blank(pConverter->channelsIn, pConverter->channelMapIn) || ma_channel_map_blank(pConverter->channelsOut, pConverter->channelMapOut)) { - pConverter->isPassthrough = MA_TRUE; - } - } - - - /* - We can use a simple case for expanding the mono channel. This will used when expanding a mono input into any output so long - as no LFE is present in the output. - */ - if (!pConverter->isPassthrough) { - if (pConverter->channelsIn == 1 && pConverter->channelMapIn[0] == MA_CHANNEL_MONO) { - /* Optimal case if no LFE is in the output channel map. */ - pConverter->isSimpleMonoExpansion = MA_TRUE; - if (ma_channel_map_contains_channel_position(pConverter->channelsOut, pConverter->channelMapOut, MA_CHANNEL_LFE)) { - pConverter->isSimpleMonoExpansion = MA_FALSE; - } - } - } - - /* Another optimized case is stereo to mono. */ - if (!pConverter->isPassthrough) { - if (pConverter->channelsOut == 1 && pConverter->channelMapOut[0] == MA_CHANNEL_MONO && pConverter->channelsIn == 2) { - /* Optimal case if no LFE is in the input channel map. */ - pConverter->isStereoToMono = MA_TRUE; - if (ma_channel_map_contains_channel_position(pConverter->channelsIn, pConverter->channelMapIn, MA_CHANNEL_LFE)) { - pConverter->isStereoToMono = MA_FALSE; - } - } - } - - - /* - Here is where we do a bit of pre-processing to know how each channel should be combined to make up the output. Rules: - - 1) If it's a passthrough, do nothing - it's just a simple memcpy(). - 2) If the channel counts are the same and every channel position in the input map is present in the output map, use a - simple shuffle. An example might be different 5.1 channel layouts. - 3) Otherwise channels are blended based on spatial locality. - */ - if (!pConverter->isPassthrough) { - if (pConverter->channelsIn == pConverter->channelsOut) { - ma_bool32 areAllChannelPositionsPresent = MA_TRUE; - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_bool32 isInputChannelPositionInOutput = MA_FALSE; - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - if (pConverter->channelMapIn[iChannelIn] == pConverter->channelMapOut[iChannelOut]) { - isInputChannelPositionInOutput = MA_TRUE; - break; - } - } - - if (!isInputChannelPositionInOutput) { - areAllChannelPositionsPresent = MA_FALSE; - break; + /* Silence our weights by default. */ + for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; iChannelIn += 1) { + for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; iChannelOut += 1) { + if (pConverter->format == ma_format_f32) { + pConverter->weights.f32[iChannelIn][iChannelOut] = 0.0f; + } else { + pConverter->weights.s16[iChannelIn][iChannelOut] = 0; } } + } - if (areAllChannelPositionsPresent) { - pConverter->isSimpleShuffle = MA_TRUE; + /* + We now need to fill out our weights table. This is determined by the mixing mode. + */ + switch (pConverter->mixingMode) + { + case ma_channel_mix_mode_custom_weights: + { + if (pConfig->ppWeights == NULL) { + return MA_INVALID_ARGS; /* Config specified a custom weights mixing mode, but no custom weights have been specified. */ + } - /* - All the router will be doing is rearranging channels which means all we need to do is use a shuffling table which is just - a mapping between the index of the input channel to the index of the output channel. - */ - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - if (pConverter->channelMapIn[iChannelIn] == pConverter->channelMapOut[iChannelOut]) { - pConverter->shuffleTable[iChannelIn] = (ma_uint8)iChannelOut; - break; + for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; iChannelIn += 1) { + for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; iChannelOut += 1) { + float weight = pConfig->ppWeights[iChannelIn][iChannelOut]; + + if (pConverter->format == ma_format_f32) { + pConverter->weights.f32[iChannelIn][iChannelOut] = weight; + } else { + pConverter->weights.s16[iChannelIn][iChannelOut] = ma_channel_converter_float_to_fixed(weight); } } } - } - } - } + } break; - - /* - Here is where weights are calculated. Note that we calculate the weights at all times, even when using a passthrough and simple - shuffling. We use different algorithms for calculating weights depending on our mixing mode. - - In simple mode we don't do any blending (except for converting between mono, which is done in a later step). Instead we just - map 1:1 matching channels. In this mode, if no channels in the input channel map correspond to anything in the output channel - map, nothing will be heard! - */ - - /* In all cases we need to make sure all channels that are present in both channel maps have a 1:1 mapping. */ - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_channel channelPosIn = pConverter->channelMapIn[iChannelIn]; - - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - ma_channel channelPosOut = pConverter->channelMapOut[iChannelOut]; - - if (channelPosIn == channelPosOut) { - if (pConverter->format == ma_format_f32) { - pConverter->weights.f32[iChannelIn][iChannelOut] = 1; - } else { - pConverter->weights.s16[iChannelIn][iChannelOut] = (1 << MA_CHANNEL_CONVERTER_FIXED_POINT_SHIFT); - } - } - } - } - - /* - The mono channel is accumulated on all other channels, except LFE. Make sure in this loop we exclude output mono channels since - they were handled in the pass above. - */ - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_channel channelPosIn = pConverter->channelMapIn[iChannelIn]; - - if (channelPosIn == MA_CHANNEL_MONO) { - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - ma_channel channelPosOut = pConverter->channelMapOut[iChannelOut]; - - if (channelPosOut != MA_CHANNEL_NONE && channelPosOut != MA_CHANNEL_MONO && channelPosOut != MA_CHANNEL_LFE) { + case ma_channel_mix_mode_simple: + { + /* In simple mode, excess channels need to be silenced or dropped. */ + ma_uint32 iChannel; + for (iChannel = 0; iChannel < ma_min(pConverter->channelsIn, pConverter->channelsOut); iChannel += 1) { if (pConverter->format == ma_format_f32) { - pConverter->weights.f32[iChannelIn][iChannelOut] = 1; + if (pConverter->weights.f32[iChannel][iChannel] == 0) { + pConverter->weights.f32[iChannel][iChannel] = 1; + } } else { - pConverter->weights.s16[iChannelIn][iChannelOut] = (1 << MA_CHANNEL_CONVERTER_FIXED_POINT_SHIFT); - } - } - } - } - } - - /* The output mono channel is the average of all non-none, non-mono and non-lfe input channels. */ - { - ma_uint32 len = 0; - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_channel channelPosIn = pConverter->channelMapIn[iChannelIn]; - - if (channelPosIn != MA_CHANNEL_NONE && channelPosIn != MA_CHANNEL_MONO && channelPosIn != MA_CHANNEL_LFE) { - len += 1; - } - } - - if (len > 0) { - float monoWeight = 1.0f / len; - - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - ma_channel channelPosOut = pConverter->channelMapOut[iChannelOut]; - - if (channelPosOut == MA_CHANNEL_MONO) { - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_channel channelPosIn = pConverter->channelMapIn[iChannelIn]; - - if (channelPosIn != MA_CHANNEL_NONE && channelPosIn != MA_CHANNEL_MONO && channelPosIn != MA_CHANNEL_LFE) { - if (pConverter->format == ma_format_f32) { - pConverter->weights.f32[iChannelIn][iChannelOut] = monoWeight; - } else { - pConverter->weights.s16[iChannelIn][iChannelOut] = ma_channel_converter_float_to_fixed(monoWeight); - } + if (pConverter->weights.s16[iChannel][iChannel] == 0) { + pConverter->weights.s16[iChannel][iChannel] = ma_channel_converter_float_to_fixed(1); } } } - } - } - } + } break; + case ma_channel_mix_mode_rectangular: + default: + { + /* Unmapped input channels. */ + for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { + ma_channel channelPosIn = pConverter->pChannelMapIn[iChannelIn]; - /* Input and output channels that are not present on the other side need to be blended in based on spatial locality. */ - switch (pConverter->mixingMode) - { - case ma_channel_mix_mode_rectangular: - { - /* Unmapped input channels. */ - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_channel channelPosIn = pConverter->channelMapIn[iChannelIn]; + if (ma_is_spatial_channel_position(channelPosIn)) { + if (!ma_channel_map_contains_channel_position(pConverter->channelsOut, pConverter->pChannelMapOut, channelPosIn)) { + for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { + ma_channel channelPosOut = pConverter->pChannelMapOut[iChannelOut]; - if (ma_is_spatial_channel_position(channelPosIn)) { - if (!ma_channel_map_contains_channel_position(pConverter->channelsOut, pConverter->channelMapOut, channelPosIn)) { - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - ma_channel channelPosOut = pConverter->channelMapOut[iChannelOut]; - - if (ma_is_spatial_channel_position(channelPosOut)) { - float weight = 0; - if (pConverter->mixingMode == ma_channel_mix_mode_rectangular) { - weight = ma_calculate_channel_position_rectangular_weight(channelPosIn, channelPosOut); - } - - /* Only apply the weight if we haven't already got some contribution from the respective channels. */ - if (pConverter->format == ma_format_f32) { - if (pConverter->weights.f32[iChannelIn][iChannelOut] == 0) { - pConverter->weights.f32[iChannelIn][iChannelOut] = weight; + if (ma_is_spatial_channel_position(channelPosOut)) { + float weight = 0; + if (pConverter->mixingMode == ma_channel_mix_mode_rectangular) { + weight = ma_calculate_channel_position_rectangular_weight(channelPosIn, channelPosOut); } - } else { - if (pConverter->weights.s16[iChannelIn][iChannelOut] == 0) { - pConverter->weights.s16[iChannelIn][iChannelOut] = ma_channel_converter_float_to_fixed(weight); + + /* Only apply the weight if we haven't already got some contribution from the respective channels. */ + if (pConverter->format == ma_format_f32) { + if (pConverter->weights.f32[iChannelIn][iChannelOut] == 0) { + pConverter->weights.f32[iChannelIn][iChannelOut] = weight; + } + } else { + if (pConverter->weights.s16[iChannelIn][iChannelOut] == 0) { + pConverter->weights.s16[iChannelIn][iChannelOut] = ma_channel_converter_float_to_fixed(weight); + } } } } } } } - } - /* Unmapped output channels. */ - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - ma_channel channelPosOut = pConverter->channelMapOut[iChannelOut]; + /* Unmapped output channels. */ + for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { + ma_channel channelPosOut = pConverter->pChannelMapOut[iChannelOut]; - if (ma_is_spatial_channel_position(channelPosOut)) { - if (!ma_channel_map_contains_channel_position(pConverter->channelsIn, pConverter->channelMapIn, channelPosOut)) { - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_channel channelPosIn = pConverter->channelMapIn[iChannelIn]; + if (ma_is_spatial_channel_position(channelPosOut)) { + if (!ma_channel_map_contains_channel_position(pConverter->channelsIn, pConverter->pChannelMapIn, channelPosOut)) { + for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { + ma_channel channelPosIn = pConverter->pChannelMapIn[iChannelIn]; - if (ma_is_spatial_channel_position(channelPosIn)) { - float weight = 0; - if (pConverter->mixingMode == ma_channel_mix_mode_rectangular) { - weight = ma_calculate_channel_position_rectangular_weight(channelPosIn, channelPosOut); - } - - /* Only apply the weight if we haven't already got some contribution from the respective channels. */ - if (pConverter->format == ma_format_f32) { - if (pConverter->weights.f32[iChannelIn][iChannelOut] == 0) { - pConverter->weights.f32[iChannelIn][iChannelOut] = weight; + if (ma_is_spatial_channel_position(channelPosIn)) { + float weight = 0; + if (pConverter->mixingMode == ma_channel_mix_mode_rectangular) { + weight = ma_calculate_channel_position_rectangular_weight(channelPosIn, channelPosOut); } - } else { - if (pConverter->weights.s16[iChannelIn][iChannelOut] == 0) { - pConverter->weights.s16[iChannelIn][iChannelOut] = ma_channel_converter_float_to_fixed(weight); + + /* Only apply the weight if we haven't already got some contribution from the respective channels. */ + if (pConverter->format == ma_format_f32) { + if (pConverter->weights.f32[iChannelIn][iChannelOut] == 0) { + pConverter->weights.f32[iChannelIn][iChannelOut] = weight; + } + } else { + if (pConverter->weights.s16[iChannelIn][iChannelOut] == 0) { + pConverter->weights.s16[iChannelIn][iChannelOut] = ma_channel_converter_float_to_fixed(weight); + } } } } } } } - } - } break; - - case ma_channel_mix_mode_simple: - { - /* In simple mode, excess channels need to be silenced or dropped. */ - ma_uint32 iChannel; - for (iChannel = 0; iChannel < ma_min(pConverter->channelsIn, pConverter->channelsOut); iChannel += 1) { - if (pConverter->format == ma_format_f32) { - if (pConverter->weights.f32[iChannel][iChannel] == 0) { - pConverter->weights.f32[iChannel][iChannel] = 1; - } - } else { - if (pConverter->weights.s16[iChannel][iChannel] == 0) { - pConverter->weights.s16[iChannel][iChannel] = ma_channel_converter_float_to_fixed(1); - } - } - } - } break; - - case ma_channel_mix_mode_custom_weights: - default: - { - /* Fallthrough. */ - } break; + } break; + } } - return MA_SUCCESS; } -MA_API void ma_channel_converter_uninit(ma_channel_converter* pConverter) +MA_API ma_result ma_channel_converter_init(const ma_channel_converter_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_channel_converter* pConverter) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_channel_converter_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_channel_converter_init_preallocated(pConfig, pHeap, pConverter); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pConverter->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_channel_converter_uninit(ma_channel_converter* pConverter, const ma_allocation_callbacks* pAllocationCallbacks) { if (pConverter == NULL) { return; } + + if (pConverter->_ownsHeap) { + ma_free(pConverter->_pHeap, pAllocationCallbacks); + } } static ma_result ma_channel_converter_process_pcm_frames__passthrough(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) @@ -34122,103 +38958,17 @@ static ma_result ma_channel_converter_process_pcm_frames__passthrough(ma_channel return MA_SUCCESS; } -static ma_result ma_channel_converter_process_pcm_frames__simple_shuffle(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) +static ma_result ma_channel_converter_process_pcm_frames__shuffle(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) { - ma_uint32 iFrame; - ma_uint32 iChannelIn; - MA_ASSERT(pConverter != NULL); MA_ASSERT(pFramesOut != NULL); MA_ASSERT(pFramesIn != NULL); MA_ASSERT(pConverter->channelsIn == pConverter->channelsOut); - switch (pConverter->format) - { - case ma_format_u8: - { - /* */ ma_uint8* pFramesOutU8 = ( ma_uint8*)pFramesOut; - const ma_uint8* pFramesInU8 = (const ma_uint8*)pFramesIn; - - for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - pFramesOutU8[pConverter->shuffleTable[iChannelIn]] = pFramesInU8[iChannelIn]; - } - - pFramesOutU8 += pConverter->channelsOut; - pFramesInU8 += pConverter->channelsIn; - } - } break; - - case ma_format_s16: - { - /* */ ma_int16* pFramesOutS16 = ( ma_int16*)pFramesOut; - const ma_int16* pFramesInS16 = (const ma_int16*)pFramesIn; - - for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - pFramesOutS16[pConverter->shuffleTable[iChannelIn]] = pFramesInS16[iChannelIn]; - } - - pFramesOutS16 += pConverter->channelsOut; - pFramesInS16 += pConverter->channelsIn; - } - } break; - - case ma_format_s24: - { - /* */ ma_uint8* pFramesOutS24 = ( ma_uint8*)pFramesOut; - const ma_uint8* pFramesInS24 = (const ma_uint8*)pFramesIn; - - for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_uint32 iChannelOut = pConverter->shuffleTable[iChannelIn]; - pFramesOutS24[iChannelOut*3 + 0] = pFramesInS24[iChannelIn*3 + 0]; - pFramesOutS24[iChannelOut*3 + 1] = pFramesInS24[iChannelIn*3 + 1]; - pFramesOutS24[iChannelOut*3 + 2] = pFramesInS24[iChannelIn*3 + 2]; - } - - pFramesOutS24 += pConverter->channelsOut*3; - pFramesInS24 += pConverter->channelsIn*3; - } - } break; - - case ma_format_s32: - { - /* */ ma_int32* pFramesOutS32 = ( ma_int32*)pFramesOut; - const ma_int32* pFramesInS32 = (const ma_int32*)pFramesIn; - - for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - pFramesOutS32[pConverter->shuffleTable[iChannelIn]] = pFramesInS32[iChannelIn]; - } - - pFramesOutS32 += pConverter->channelsOut; - pFramesInS32 += pConverter->channelsIn; - } - } break; - - case ma_format_f32: - { - /* */ float* pFramesOutF32 = ( float*)pFramesOut; - const float* pFramesInF32 = (const float*)pFramesIn; - - for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - pFramesOutF32[pConverter->shuffleTable[iChannelIn]] = pFramesInF32[iChannelIn]; - } - - pFramesOutF32 += pConverter->channelsOut; - pFramesInF32 += pConverter->channelsIn; - } - } break; - - default: return MA_INVALID_OPERATION; /* Unknown format. */ - } - - return MA_SUCCESS; + return ma_channel_map_apply_shuffle_table(pFramesOut, pConverter->channelsOut, pFramesIn, pConverter->channelsIn, frameCount, pConverter->pShuffleTable, pConverter->format); } -static ma_result ma_channel_converter_process_pcm_frames__simple_mono_expansion(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) +static ma_result ma_channel_converter_process_pcm_frames__mono_in(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) { ma_uint64 iFrame; @@ -34318,14 +39068,14 @@ static ma_result ma_channel_converter_process_pcm_frames__simple_mono_expansion( return MA_SUCCESS; } -static ma_result ma_channel_converter_process_pcm_frames__stereo_to_mono(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) +static ma_result ma_channel_converter_process_pcm_frames__mono_out(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) { ma_uint64 iFrame; + ma_uint32 iChannel; MA_ASSERT(pConverter != NULL); MA_ASSERT(pFramesOut != NULL); MA_ASSERT(pFramesIn != NULL); - MA_ASSERT(pConverter->channelsIn == 2); MA_ASSERT(pConverter->channelsOut == 1); switch (pConverter->format) @@ -34336,7 +39086,12 @@ static ma_result ma_channel_converter_process_pcm_frames__stereo_to_mono(ma_chan const ma_uint8* pFramesInU8 = (const ma_uint8*)pFramesIn; for (iFrame = 0; iFrame < frameCount; ++iFrame) { - pFramesOutU8[iFrame] = ma_clip_u8((ma_int16)((ma_pcm_sample_u8_to_s16_no_scale(pFramesInU8[iFrame*2+0]) + ma_pcm_sample_u8_to_s16_no_scale(pFramesInU8[iFrame*2+1])) / 2)); + ma_int32 t = 0; + for (iChannel = 0; iChannel < pConverter->channelsIn; iChannel += 1) { + t += ma_pcm_sample_u8_to_s16_no_scale(pFramesInU8[iFrame*pConverter->channelsIn + iChannel]); + } + + pFramesOutU8[iFrame] = ma_clip_u8(t / pConverter->channelsOut); } } break; @@ -34346,7 +39101,12 @@ static ma_result ma_channel_converter_process_pcm_frames__stereo_to_mono(ma_chan const ma_int16* pFramesInS16 = (const ma_int16*)pFramesIn; for (iFrame = 0; iFrame < frameCount; ++iFrame) { - pFramesOutS16[iFrame] = (ma_int16)(((ma_int32)pFramesInS16[iFrame*2+0] + (ma_int32)pFramesInS16[iFrame*2+1]) / 2); + ma_int32 t = 0; + for (iChannel = 0; iChannel < pConverter->channelsIn; iChannel += 1) { + t += pFramesInS16[iFrame*pConverter->channelsIn + iChannel]; + } + + pFramesOutS16[iFrame] = (ma_int16)(t / pConverter->channelsIn); } } break; @@ -34356,9 +39116,12 @@ static ma_result ma_channel_converter_process_pcm_frames__stereo_to_mono(ma_chan const ma_uint8* pFramesInS24 = (const ma_uint8*)pFramesIn; for (iFrame = 0; iFrame < frameCount; ++iFrame) { - ma_int64 s24_0 = ma_pcm_sample_s24_to_s32_no_scale(&pFramesInS24[(iFrame*2+0)*3]); - ma_int64 s24_1 = ma_pcm_sample_s24_to_s32_no_scale(&pFramesInS24[(iFrame*2+1)*3]); - ma_pcm_sample_s32_to_s24_no_scale((s24_0 + s24_1) / 2, &pFramesOutS24[iFrame*3]); + ma_int64 t = 0; + for (iChannel = 0; iChannel < pConverter->channelsIn; iChannel += 1) { + t += ma_pcm_sample_s24_to_s32_no_scale(&pFramesInS24[(iFrame*pConverter->channelsIn + iChannel)*3]); + } + + ma_pcm_sample_s32_to_s24_no_scale(t / pConverter->channelsIn, &pFramesOutS24[iFrame*3]); } } break; @@ -34368,7 +39131,12 @@ static ma_result ma_channel_converter_process_pcm_frames__stereo_to_mono(ma_chan const ma_int32* pFramesInS32 = (const ma_int32*)pFramesIn; for (iFrame = 0; iFrame < frameCount; ++iFrame) { - pFramesOutS32[iFrame] = (ma_int16)(((ma_int32)pFramesInS32[iFrame*2+0] + (ma_int32)pFramesInS32[iFrame*2+1]) / 2); + ma_int64 t = 0; + for (iChannel = 0; iChannel < pConverter->channelsIn; iChannel += 1) { + t += pFramesInS32[iFrame*pConverter->channelsIn + iChannel]; + } + + pFramesOutS32[iFrame] = (ma_int32)(t / pConverter->channelsIn); } } break; @@ -34378,7 +39146,12 @@ static ma_result ma_channel_converter_process_pcm_frames__stereo_to_mono(ma_chan const float* pFramesInF32 = (const float*)pFramesIn; for (iFrame = 0; iFrame < frameCount; ++iFrame) { - pFramesOutF32[iFrame] = (pFramesInF32[iFrame*2+0] + pFramesInF32[iFrame*2+1]) * 0.5f; + float t = 0; + for (iChannel = 0; iChannel < pConverter->channelsIn; iChannel += 1) { + t += pFramesInF32[iFrame*pConverter->channelsIn + iChannel]; + } + + pFramesOutF32[iFrame] = t / pConverter->channelsIn; } } break; @@ -34509,19 +39282,42 @@ MA_API ma_result ma_channel_converter_process_pcm_frames(ma_channel_converter* p return MA_SUCCESS; } - if (pConverter->isPassthrough) { - return ma_channel_converter_process_pcm_frames__passthrough(pConverter, pFramesOut, pFramesIn, frameCount); - } else if (pConverter->isSimpleShuffle) { - return ma_channel_converter_process_pcm_frames__simple_shuffle(pConverter, pFramesOut, pFramesIn, frameCount); - } else if (pConverter->isSimpleMonoExpansion) { - return ma_channel_converter_process_pcm_frames__simple_mono_expansion(pConverter, pFramesOut, pFramesIn, frameCount); - } else if (pConverter->isStereoToMono) { - return ma_channel_converter_process_pcm_frames__stereo_to_mono(pConverter, pFramesOut, pFramesIn, frameCount); - } else { - return ma_channel_converter_process_pcm_frames__weights(pConverter, pFramesOut, pFramesIn, frameCount); + switch (pConverter->conversionPath) + { + case ma_channel_conversion_path_passthrough: return ma_channel_converter_process_pcm_frames__passthrough(pConverter, pFramesOut, pFramesIn, frameCount); + case ma_channel_conversion_path_mono_out: return ma_channel_converter_process_pcm_frames__mono_out(pConverter, pFramesOut, pFramesIn, frameCount); + case ma_channel_conversion_path_mono_in: return ma_channel_converter_process_pcm_frames__mono_in(pConverter, pFramesOut, pFramesIn, frameCount); + case ma_channel_conversion_path_shuffle: return ma_channel_converter_process_pcm_frames__shuffle(pConverter, pFramesOut, pFramesIn, frameCount); + case ma_channel_conversion_path_weights: + default: + { + return ma_channel_converter_process_pcm_frames__weights(pConverter, pFramesOut, pFramesIn, frameCount); + } } } +MA_API ma_result ma_channel_converter_get_input_channel_map(const ma_channel_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap) +{ + if (pConverter == NULL || pChannelMap == NULL) { + return MA_INVALID_ARGS; + } + + ma_channel_map_copy_or_default(pChannelMap, channelMapCap, pConverter->pChannelMapIn, pConverter->channelsIn); + + return MA_SUCCESS; +} + +MA_API ma_result ma_channel_converter_get_output_channel_map(const ma_channel_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap) +{ + if (pConverter == NULL || pChannelMap == NULL) { + return MA_INVALID_ARGS; + } + + ma_channel_map_copy_or_default(pChannelMap, channelMapCap, pConverter->pChannelMapOut, pConverter->channelsOut); + + return MA_SUCCESS; +} + /************************************************************************************************************************************************************** @@ -34535,14 +39331,10 @@ MA_API ma_data_converter_config ma_data_converter_config_init_default() config.ditherMode = ma_dither_mode_none; config.resampling.algorithm = ma_resample_algorithm_linear; - config.resampling.allowDynamicSampleRate = MA_FALSE; /* Disable dynamic sample rates by default because dynamic rate adjustments should be quite rare and it allows an optimization for cases when the in and out sample rates are the same. */ + config.allowDynamicSampleRate = MA_FALSE; /* Disable dynamic sample rates by default because dynamic rate adjustments should be quite rare and it allows an optimization for cases when the in and out sample rates are the same. */ /* Linear resampling defaults. */ config.resampling.linear.lpfOrder = 1; - config.resampling.linear.lpfNyquistFactor = 1; - - /* Speex resampling defaults. */ - config.resampling.speex.quality = 3; return config; } @@ -34552,18 +39344,168 @@ MA_API ma_data_converter_config ma_data_converter_config_init(ma_format formatIn ma_data_converter_config config = ma_data_converter_config_init_default(); config.formatIn = formatIn; config.formatOut = formatOut; - config.channelsIn = ma_min(channelsIn, MA_MAX_CHANNELS); - config.channelsOut = ma_min(channelsOut, MA_MAX_CHANNELS); + config.channelsIn = channelsIn; + config.channelsOut = channelsOut; config.sampleRateIn = sampleRateIn; config.sampleRateOut = sampleRateOut; return config; } -MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, ma_data_converter* pConverter) + +typedef struct +{ + size_t sizeInBytes; + size_t channelConverterOffset; + size_t resamplerOffset; +} ma_data_converter_heap_layout; + +static ma_bool32 ma_data_converter_config_is_resampler_required(const ma_data_converter_config* pConfig) +{ + MA_ASSERT(pConfig != NULL); + + return pConfig->allowDynamicSampleRate || pConfig->sampleRateIn != pConfig->sampleRateOut; +} + +static ma_format ma_data_converter_config_get_mid_format(const ma_data_converter_config* pConfig) +{ + MA_ASSERT(pConfig != NULL); + + /* + We want to avoid as much data conversion as possible. The channel converter and linear + resampler both support s16 and f32 natively. We need to decide on the format to use for this + stage. We call this the mid format because it's used in the middle stage of the conversion + pipeline. If the output format is either s16 or f32 we use that one. If that is not the case it + will do the same thing for the input format. If it's neither we just use f32. If we are using a + custom resampling backend, we can only guarantee that f32 will be supported so we'll be forced + to use that if resampling is required. + */ + if (ma_data_converter_config_is_resampler_required(pConfig) && pConfig->resampling.algorithm != ma_resample_algorithm_linear) { + return ma_format_f32; /* <-- Force f32 since that is the only one we can guarantee will be supported by the resampler. */ + } else { + /* */ if (pConfig->formatOut == ma_format_s16 || pConfig->formatOut == ma_format_f32) { + return pConfig->formatOut; + } else if (pConfig->formatIn == ma_format_s16 || pConfig->formatIn == ma_format_f32) { + return pConfig->formatIn; + } else { + return ma_format_f32; + } + } +} + +static ma_channel_converter_config ma_channel_converter_config_init_from_data_converter_config(const ma_data_converter_config* pConfig) +{ + ma_channel_converter_config channelConverterConfig; + + MA_ASSERT(pConfig != NULL); + + channelConverterConfig = ma_channel_converter_config_init(ma_data_converter_config_get_mid_format(pConfig), pConfig->channelsIn, pConfig->pChannelMapIn, pConfig->channelsOut, pConfig->pChannelMapOut, pConfig->channelMixMode); + channelConverterConfig.ppWeights = pConfig->ppChannelWeights; + + return channelConverterConfig; +} + +static ma_resampler_config ma_resampler_config_init_from_data_converter_config(const ma_data_converter_config* pConfig) +{ + ma_resampler_config resamplerConfig; + ma_uint32 resamplerChannels; + + MA_ASSERT(pConfig != NULL); + + /* The resampler is the most expensive part of the conversion process, so we need to do it at the stage where the channel count is at it's lowest. */ + if (pConfig->channelsIn < pConfig->channelsOut) { + resamplerChannels = pConfig->channelsIn; + } else { + resamplerChannels = pConfig->channelsOut; + } + + resamplerConfig = ma_resampler_config_init(ma_data_converter_config_get_mid_format(pConfig), resamplerChannels, pConfig->sampleRateIn, pConfig->sampleRateOut, pConfig->resampling.algorithm); + resamplerConfig.linear = pConfig->resampling.linear; + resamplerConfig.pBackendVTable = pConfig->resampling.pBackendVTable; + resamplerConfig.pBackendUserData = pConfig->resampling.pBackendUserData; + + return resamplerConfig; +} + +static ma_result ma_data_converter_get_heap_layout(const ma_data_converter_config* pConfig, ma_data_converter_heap_layout* pHeapLayout) { ma_result result; + + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channelsIn == 0 || pConfig->channelsOut == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* Channel converter. */ + pHeapLayout->channelConverterOffset = pHeapLayout->sizeInBytes; + { + size_t heapSizeInBytes; + ma_channel_converter_config channelConverterConfig = ma_channel_converter_config_init_from_data_converter_config(pConfig); + + result = ma_channel_converter_get_heap_size(&channelConverterConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += heapSizeInBytes; + } + + /* Resampler. */ + pHeapLayout->resamplerOffset = pHeapLayout->sizeInBytes; + if (ma_data_converter_config_is_resampler_required(pConfig)) { + size_t heapSizeInBytes; + ma_resampler_config resamplerConfig = ma_resampler_config_init_from_data_converter_config(pConfig); + + result = ma_resampler_get_heap_size(&resamplerConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += heapSizeInBytes; + } + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_data_converter_get_heap_size(const ma_data_converter_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_data_converter_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_data_converter_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_data_converter_init_preallocated(const ma_data_converter_config* pConfig, void* pHeap, ma_data_converter* pConverter) +{ + ma_result result; + ma_data_converter_heap_layout heapLayout; ma_format midFormat; + ma_bool32 isResamplingRequired; if (pConverter == NULL) { return MA_INVALID_ARGS; @@ -34571,82 +39513,52 @@ MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, MA_ZERO_OBJECT(pConverter); - if (pConfig == NULL) { - return MA_INVALID_ARGS; + result = ma_data_converter_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; } - pConverter->config = *pConfig; + pConverter->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); - /* Basic validation. */ - if (pConfig->channelsIn < MA_MIN_CHANNELS || pConfig->channelsOut < MA_MIN_CHANNELS || - pConfig->channelsIn > MA_MAX_CHANNELS || pConfig->channelsOut > MA_MAX_CHANNELS) { - return MA_INVALID_ARGS; - } + pConverter->formatIn = pConfig->formatIn; + pConverter->formatOut = pConfig->formatOut; + pConverter->channelsIn = pConfig->channelsIn; + pConverter->channelsOut = pConfig->channelsOut; + pConverter->sampleRateIn = pConfig->sampleRateIn; + pConverter->sampleRateOut = pConfig->sampleRateOut; + pConverter->ditherMode = pConfig->ditherMode; /* - We want to avoid as much data conversion as possible. The channel converter and resampler both support s16 and f32 natively. We need to decide - on the format to use for this stage. We call this the mid format because it's used in the middle stage of the conversion pipeline. If the output - format is either s16 or f32 we use that one. If that is not the case it will do the same thing for the input format. If it's neither we just - use f32. + Determine if resampling is required. We need to do this so we can determine an appropriate + mid format to use. If resampling is required, the mid format must be ma_format_f32 since + that is the only one that is guaranteed to supported by custom resampling backends. */ - /* */ if (pConverter->config.formatOut == ma_format_s16 || pConverter->config.formatOut == ma_format_f32) { - midFormat = pConverter->config.formatOut; - } else if (pConverter->config.formatIn == ma_format_s16 || pConverter->config.formatIn == ma_format_f32) { - midFormat = pConverter->config.formatIn; - } else { - midFormat = ma_format_f32; - } + isResamplingRequired = ma_data_converter_config_is_resampler_required(pConfig); + midFormat = ma_data_converter_config_get_mid_format(pConfig); + /* Channel converter. We always initialize this, but we check if it configures itself as a passthrough to determine whether or not it's needed. */ { - ma_uint32 iChannelIn; - ma_uint32 iChannelOut; - ma_channel_converter_config channelConverterConfig; + ma_channel_converter_config channelConverterConfig = ma_channel_converter_config_init_from_data_converter_config(pConfig); - channelConverterConfig = ma_channel_converter_config_init(midFormat, pConverter->config.channelsIn, pConverter->config.channelMapIn, pConverter->config.channelsOut, pConverter->config.channelMapOut, pConverter->config.channelMixMode); - - /* Channel weights. */ - for (iChannelIn = 0; iChannelIn < pConverter->config.channelsIn; iChannelIn += 1) { - for (iChannelOut = 0; iChannelOut < pConverter->config.channelsOut; iChannelOut += 1) { - channelConverterConfig.weights[iChannelIn][iChannelOut] = pConverter->config.channelWeights[iChannelIn][iChannelOut]; - } - } - - result = ma_channel_converter_init(&channelConverterConfig, &pConverter->channelConverter); + result = ma_channel_converter_init_preallocated(&channelConverterConfig, ma_offset_ptr(pHeap, heapLayout.channelConverterOffset), &pConverter->channelConverter); if (result != MA_SUCCESS) { return result; } /* If the channel converter is not a passthrough we need to enable it. Otherwise we can skip it. */ - if (pConverter->channelConverter.isPassthrough == MA_FALSE) { + if (pConverter->channelConverter.conversionPath != ma_channel_conversion_path_passthrough) { pConverter->hasChannelConverter = MA_TRUE; } } - /* Always enable dynamic sample rates if the input sample rate is different because we're always going to need a resampler in this case anyway. */ - if (pConverter->config.resampling.allowDynamicSampleRate == MA_FALSE) { - pConverter->config.resampling.allowDynamicSampleRate = pConverter->config.sampleRateIn != pConverter->config.sampleRateOut; - } - /* Resampler. */ - if (pConverter->config.resampling.allowDynamicSampleRate) { - ma_resampler_config resamplerConfig; - ma_uint32 resamplerChannels; + if (isResamplingRequired) { + ma_resampler_config resamplerConfig = ma_resampler_config_init_from_data_converter_config(pConfig); - /* The resampler is the most expensive part of the conversion process, so we need to do it at the stage where the channel count is at it's lowest. */ - if (pConverter->config.channelsIn < pConverter->config.channelsOut) { - resamplerChannels = pConverter->config.channelsIn; - } else { - resamplerChannels = pConverter->config.channelsOut; - } - - resamplerConfig = ma_resampler_config_init(midFormat, resamplerChannels, pConverter->config.sampleRateIn, pConverter->config.sampleRateOut, pConverter->config.resampling.algorithm); - resamplerConfig.linear.lpfOrder = pConverter->config.resampling.linear.lpfOrder; - resamplerConfig.linear.lpfNyquistFactor = pConverter->config.resampling.linear.lpfNyquistFactor; - resamplerConfig.speex.quality = pConverter->config.resampling.speex.quality; - - result = ma_resampler_init(&resamplerConfig, &pConverter->resampler); + result = ma_resampler_init_preallocated(&resamplerConfig, ma_offset_ptr(pHeap, heapLayout.resamplerOffset), &pConverter->resampler); if (result != MA_SUCCESS) { return result; } @@ -34658,7 +39570,7 @@ MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, /* We can simplify pre- and post-format conversion if we have neither channel conversion nor resampling. */ if (pConverter->hasChannelConverter == MA_FALSE && pConverter->hasResampler == MA_FALSE) { /* We have neither channel conversion nor resampling so we'll only need one of pre- or post-format conversion, or none if the input and output formats are the same. */ - if (pConverter->config.formatIn == pConverter->config.formatOut) { + if (pConverter->formatIn == pConverter->formatOut) { /* The formats are the same so we can just pass through. */ pConverter->hasPreFormatConversion = MA_FALSE; pConverter->hasPostFormatConversion = MA_FALSE; @@ -34669,10 +39581,10 @@ MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, } } else { /* We have a channel converter and/or resampler so we'll need channel conversion based on the mid format. */ - if (pConverter->config.formatIn != midFormat) { - pConverter->hasPreFormatConversion = MA_TRUE; + if (pConverter->formatIn != midFormat) { + pConverter->hasPreFormatConversion = MA_TRUE; } - if (pConverter->config.formatOut != midFormat) { + if (pConverter->formatOut != midFormat) { pConverter->hasPostFormatConversion = MA_TRUE; } } @@ -34685,17 +39597,86 @@ MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, pConverter->isPassthrough = MA_TRUE; } + + /* We now need to determine our execution path. */ + if (pConverter->isPassthrough) { + pConverter->executionPath = ma_data_converter_execution_path_passthrough; + } else { + if (pConverter->channelsIn < pConverter->channelsOut) { + /* Do resampling first, if necessary. */ + MA_ASSERT(pConverter->hasChannelConverter == MA_TRUE); + + if (pConverter->hasResampler) { + pConverter->executionPath = ma_data_converter_execution_path_resample_first; + } else { + pConverter->executionPath = ma_data_converter_execution_path_channels_only; + } + } else { + /* Do channel conversion first, if necessary. */ + if (pConverter->hasChannelConverter) { + if (pConverter->hasResampler) { + pConverter->executionPath = ma_data_converter_execution_path_channels_first; + } else { + pConverter->executionPath = ma_data_converter_execution_path_channels_only; + } + } else { + /* Channel routing not required. */ + if (pConverter->hasResampler) { + pConverter->executionPath = ma_data_converter_execution_path_resample_only; + } else { + pConverter->executionPath = ma_data_converter_execution_path_format_only; + } + } + } + } + return MA_SUCCESS; } -MA_API void ma_data_converter_uninit(ma_data_converter* pConverter) +MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_converter* pConverter) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_data_converter_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_data_converter_init_preallocated(pConfig, pHeap, pConverter); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pConverter->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_data_converter_uninit(ma_data_converter* pConverter, const ma_allocation_callbacks* pAllocationCallbacks) { if (pConverter == NULL) { return; } if (pConverter->hasResampler) { - ma_resampler_uninit(&pConverter->resampler); + ma_resampler_uninit(&pConverter->resampler, pAllocationCallbacks); + } + + ma_channel_converter_uninit(&pConverter->channelConverter, pAllocationCallbacks); + + if (pConverter->_ownsHeap) { + ma_free(pConverter->_pHeap, pAllocationCallbacks); } } @@ -34721,9 +39702,9 @@ static ma_result ma_data_converter_process_pcm_frames__passthrough(ma_data_conve if (pFramesOut != NULL) { if (pFramesIn != NULL) { - ma_copy_memory_64(pFramesOut, pFramesIn, frameCount * ma_get_bytes_per_frame(pConverter->config.formatOut, pConverter->config.channelsOut)); + ma_copy_memory_64(pFramesOut, pFramesIn, frameCount * ma_get_bytes_per_frame(pConverter->formatOut, pConverter->channelsOut)); } else { - ma_zero_memory_64(pFramesOut, frameCount * ma_get_bytes_per_frame(pConverter->config.formatOut, pConverter->config.channelsOut)); + ma_zero_memory_64(pFramesOut, frameCount * ma_get_bytes_per_frame(pConverter->formatOut, pConverter->channelsOut)); } } @@ -34759,9 +39740,9 @@ static ma_result ma_data_converter_process_pcm_frames__format_only(ma_data_conve if (pFramesOut != NULL) { if (pFramesIn != NULL) { - ma_convert_pcm_frames_format(pFramesOut, pConverter->config.formatOut, pFramesIn, pConverter->config.formatIn, frameCount, pConverter->config.channelsIn, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pFramesOut, pConverter->formatOut, pFramesIn, pConverter->formatIn, frameCount, pConverter->channelsIn, pConverter->ditherMode); } else { - ma_zero_memory_64(pFramesOut, frameCount * ma_get_bytes_per_frame(pConverter->config.formatOut, pConverter->config.channelsOut)); + ma_zero_memory_64(pFramesOut, frameCount * ma_get_bytes_per_frame(pConverter->formatOut, pConverter->channelsOut)); } } @@ -34801,20 +39782,20 @@ static ma_result ma_data_converter_process_pcm_frames__resample_with_format_conv while (framesProcessedOut < frameCountOut) { ma_uint8 pTempBufferOut[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - const ma_uint32 tempBufferOutCap = sizeof(pTempBufferOut) / ma_get_bytes_per_frame(pConverter->resampler.config.format, pConverter->resampler.config.channels); + const ma_uint32 tempBufferOutCap = sizeof(pTempBufferOut) / ma_get_bytes_per_frame(pConverter->resampler.format, pConverter->resampler.channels); const void* pFramesInThisIteration; /* */ void* pFramesOutThisIteration; ma_uint64 frameCountInThisIteration; ma_uint64 frameCountOutThisIteration; if (pFramesIn != NULL) { - pFramesInThisIteration = ma_offset_ptr(pFramesIn, framesProcessedIn * ma_get_bytes_per_frame(pConverter->config.formatIn, pConverter->config.channelsIn)); + pFramesInThisIteration = ma_offset_ptr(pFramesIn, framesProcessedIn * ma_get_bytes_per_frame(pConverter->formatIn, pConverter->channelsIn)); } else { pFramesInThisIteration = NULL; } if (pFramesOut != NULL) { - pFramesOutThisIteration = ma_offset_ptr(pFramesOut, framesProcessedOut * ma_get_bytes_per_frame(pConverter->config.formatOut, pConverter->config.channelsOut)); + pFramesOutThisIteration = ma_offset_ptr(pFramesOut, framesProcessedOut * ma_get_bytes_per_frame(pConverter->formatOut, pConverter->channelsOut)); } else { pFramesOutThisIteration = NULL; } @@ -34822,7 +39803,7 @@ static ma_result ma_data_converter_process_pcm_frames__resample_with_format_conv /* Do a pre format conversion if necessary. */ if (pConverter->hasPreFormatConversion) { ma_uint8 pTempBufferIn[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - const ma_uint32 tempBufferInCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->resampler.config.format, pConverter->resampler.config.channels); + const ma_uint32 tempBufferInCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->resampler.format, pConverter->resampler.channels); frameCountInThisIteration = (frameCountIn - framesProcessedIn); if (frameCountInThisIteration > tempBufferInCap) { @@ -34836,7 +39817,7 @@ static ma_result ma_data_converter_process_pcm_frames__resample_with_format_conv } if (pFramesInThisIteration != NULL) { - ma_convert_pcm_frames_format(pTempBufferIn, pConverter->resampler.config.format, pFramesInThisIteration, pConverter->config.formatIn, frameCountInThisIteration, pConverter->config.channelsIn, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pTempBufferIn, pConverter->resampler.format, pFramesInThisIteration, pConverter->formatIn, frameCountInThisIteration, pConverter->channelsIn, pConverter->ditherMode); } else { MA_ZERO_MEMORY(pTempBufferIn, sizeof(pTempBufferIn)); } @@ -34877,7 +39858,7 @@ static ma_result ma_data_converter_process_pcm_frames__resample_with_format_conv /* If we are doing a post format conversion we need to do that now. */ if (pConverter->hasPostFormatConversion) { if (pFramesOutThisIteration != NULL) { - ma_convert_pcm_frames_format(pFramesOutThisIteration, pConverter->config.formatOut, pTempBufferOut, pConverter->resampler.config.format, frameCountOutThisIteration, pConverter->resampler.config.channels, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pFramesOutThisIteration, pConverter->formatOut, pTempBufferOut, pConverter->resampler.format, frameCountOutThisIteration, pConverter->resampler.channels, pConverter->ditherMode); } } @@ -34954,13 +39935,13 @@ static ma_result ma_data_converter_process_pcm_frames__channels_only(ma_data_con ma_uint64 frameCountThisIteration; if (pFramesIn != NULL) { - pFramesInThisIteration = ma_offset_ptr(pFramesIn, framesProcessed * ma_get_bytes_per_frame(pConverter->config.formatIn, pConverter->config.channelsIn)); + pFramesInThisIteration = ma_offset_ptr(pFramesIn, framesProcessed * ma_get_bytes_per_frame(pConverter->formatIn, pConverter->channelsIn)); } else { pFramesInThisIteration = NULL; } if (pFramesOut != NULL) { - pFramesOutThisIteration = ma_offset_ptr(pFramesOut, framesProcessed * ma_get_bytes_per_frame(pConverter->config.formatOut, pConverter->config.channelsOut)); + pFramesOutThisIteration = ma_offset_ptr(pFramesOut, framesProcessed * ma_get_bytes_per_frame(pConverter->formatOut, pConverter->channelsOut)); } else { pFramesOutThisIteration = NULL; } @@ -34982,7 +39963,7 @@ static ma_result ma_data_converter_process_pcm_frames__channels_only(ma_data_con } if (pFramesInThisIteration != NULL) { - ma_convert_pcm_frames_format(pTempBufferIn, pConverter->channelConverter.format, pFramesInThisIteration, pConverter->config.formatIn, frameCountThisIteration, pConverter->config.channelsIn, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pTempBufferIn, pConverter->channelConverter.format, pFramesInThisIteration, pConverter->formatIn, frameCountThisIteration, pConverter->channelsIn, pConverter->ditherMode); } else { MA_ZERO_MEMORY(pTempBufferIn, sizeof(pTempBufferIn)); } @@ -35016,7 +39997,7 @@ static ma_result ma_data_converter_process_pcm_frames__channels_only(ma_data_con /* If we are doing a post format conversion we need to do that now. */ if (pConverter->hasPostFormatConversion) { if (pFramesOutThisIteration != NULL) { - ma_convert_pcm_frames_format(pFramesOutThisIteration, pConverter->config.formatOut, pTempBufferOut, pConverter->channelConverter.format, frameCountThisIteration, pConverter->channelConverter.channelsOut, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pFramesOutThisIteration, pConverter->formatOut, pTempBufferOut, pConverter->channelConverter.format, frameCountThisIteration, pConverter->channelConverter.channelsOut, pConverter->ditherMode); } } @@ -35034,7 +40015,7 @@ static ma_result ma_data_converter_process_pcm_frames__channels_only(ma_data_con return MA_SUCCESS; } -static ma_result ma_data_converter_process_pcm_frames__resampling_first(ma_data_converter* pConverter, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) +static ma_result ma_data_converter_process_pcm_frames__resample_first(ma_data_converter* pConverter, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) { ma_result result; ma_uint64 frameCountIn; @@ -35049,9 +40030,9 @@ static ma_result ma_data_converter_process_pcm_frames__resampling_first(ma_data_ ma_uint64 tempBufferOutCap; MA_ASSERT(pConverter != NULL); - MA_ASSERT(pConverter->resampler.config.format == pConverter->channelConverter.format); - MA_ASSERT(pConverter->resampler.config.channels == pConverter->channelConverter.channelsIn); - MA_ASSERT(pConverter->resampler.config.channels < pConverter->channelConverter.channelsOut); + MA_ASSERT(pConverter->resampler.format == pConverter->channelConverter.format); + MA_ASSERT(pConverter->resampler.channels == pConverter->channelConverter.channelsIn); + MA_ASSERT(pConverter->resampler.channels < pConverter->channelConverter.channelsOut); frameCountIn = 0; if (pFrameCountIn != NULL) { @@ -35066,8 +40047,8 @@ static ma_result ma_data_converter_process_pcm_frames__resampling_first(ma_data_ framesProcessedIn = 0; framesProcessedOut = 0; - tempBufferInCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->resampler.config.format, pConverter->resampler.config.channels); - tempBufferMidCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->resampler.config.format, pConverter->resampler.config.channels); + tempBufferInCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->resampler.format, pConverter->resampler.channels); + tempBufferMidCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->resampler.format, pConverter->resampler.channels); tempBufferOutCap = sizeof(pTempBufferOut) / ma_get_bytes_per_frame(pConverter->channelConverter.format, pConverter->channelConverter.channelsOut); while (framesProcessedOut < frameCountOut) { @@ -35079,10 +40060,10 @@ static ma_result ma_data_converter_process_pcm_frames__resampling_first(ma_data_ void* pChannelsBufferOut; if (pFramesIn != NULL) { - pRunningFramesIn = ma_offset_ptr(pFramesIn, framesProcessedIn * ma_get_bytes_per_frame(pConverter->config.formatIn, pConverter->config.channelsIn)); + pRunningFramesIn = ma_offset_ptr(pFramesIn, framesProcessedIn * ma_get_bytes_per_frame(pConverter->formatIn, pConverter->channelsIn)); } if (pFramesOut != NULL) { - pRunningFramesOut = ma_offset_ptr(pFramesOut, framesProcessedOut * ma_get_bytes_per_frame(pConverter->config.formatOut, pConverter->config.channelsOut)); + pRunningFramesOut = ma_offset_ptr(pFramesOut, framesProcessedOut * ma_get_bytes_per_frame(pConverter->formatOut, pConverter->channelsOut)); } /* Run input data through the resampler and output it to the temporary buffer. */ @@ -35107,16 +40088,31 @@ static ma_result ma_data_converter_process_pcm_frames__resampling_first(ma_data_ } /* We need to ensure we don't try to process too many input frames that we run out of room in the output buffer. If this happens we'll end up glitching. */ + + /* + We need to try to predict how many input frames will be required for the resampler. If the + resampler can tell us, we'll use that. Otherwise we'll need to make a best guess. The further + off we are from this, the more wasted format conversions we'll end up doing. + */ + #if 1 { - ma_uint64 requiredInputFrameCount = ma_resampler_get_required_input_frame_count(&pConverter->resampler, frameCountOutThisIteration); + ma_uint64 requiredInputFrameCount; + + result = ma_resampler_get_required_input_frame_count(&pConverter->resampler, frameCountOutThisIteration, &requiredInputFrameCount); + if (result != MA_SUCCESS) { + /* Fall back to a best guess. */ + requiredInputFrameCount = (frameCountOutThisIteration * pConverter->resampler.sampleRateIn) / pConverter->resampler.sampleRateOut; + } + if (frameCountInThisIteration > requiredInputFrameCount) { frameCountInThisIteration = requiredInputFrameCount; } } + #endif if (pConverter->hasPreFormatConversion) { if (pFramesIn != NULL) { - ma_convert_pcm_frames_format(pTempBufferIn, pConverter->resampler.config.format, pRunningFramesIn, pConverter->config.formatIn, frameCountInThisIteration, pConverter->config.channelsIn, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pTempBufferIn, pConverter->resampler.format, pRunningFramesIn, pConverter->formatIn, frameCountInThisIteration, pConverter->channelsIn, pConverter->ditherMode); pResampleBufferIn = pTempBufferIn; } else { pResampleBufferIn = NULL; @@ -35149,7 +40145,7 @@ static ma_result ma_data_converter_process_pcm_frames__resampling_first(ma_data_ /* Finally we do post format conversion. */ if (pConverter->hasPostFormatConversion) { - ma_convert_pcm_frames_format(pRunningFramesOut, pConverter->config.formatOut, pChannelsBufferOut, pConverter->channelConverter.format, frameCountOutThisIteration, pConverter->channelConverter.channelsOut, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pRunningFramesOut, pConverter->formatOut, pChannelsBufferOut, pConverter->channelConverter.format, frameCountOutThisIteration, pConverter->channelConverter.channelsOut, pConverter->ditherMode); } } @@ -35190,9 +40186,9 @@ static ma_result ma_data_converter_process_pcm_frames__channels_first(ma_data_co ma_uint64 tempBufferOutCap; MA_ASSERT(pConverter != NULL); - MA_ASSERT(pConverter->resampler.config.format == pConverter->channelConverter.format); - MA_ASSERT(pConverter->resampler.config.channels == pConverter->channelConverter.channelsOut); - MA_ASSERT(pConverter->resampler.config.channels < pConverter->channelConverter.channelsIn); + MA_ASSERT(pConverter->resampler.format == pConverter->channelConverter.format); + MA_ASSERT(pConverter->resampler.channels == pConverter->channelConverter.channelsOut); + MA_ASSERT(pConverter->resampler.channels < pConverter->channelConverter.channelsIn); frameCountIn = 0; if (pFrameCountIn != NULL) { @@ -35209,7 +40205,7 @@ static ma_result ma_data_converter_process_pcm_frames__channels_first(ma_data_co tempBufferInCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->channelConverter.format, pConverter->channelConverter.channelsIn); tempBufferMidCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->channelConverter.format, pConverter->channelConverter.channelsOut); - tempBufferOutCap = sizeof(pTempBufferOut) / ma_get_bytes_per_frame(pConverter->resampler.config.format, pConverter->resampler.config.channels); + tempBufferOutCap = sizeof(pTempBufferOut) / ma_get_bytes_per_frame(pConverter->resampler.format, pConverter->resampler.channels); while (framesProcessedOut < frameCountOut) { ma_uint64 frameCountInThisIteration; @@ -35220,22 +40216,67 @@ static ma_result ma_data_converter_process_pcm_frames__channels_first(ma_data_co void* pResampleBufferOut; if (pFramesIn != NULL) { - pRunningFramesIn = ma_offset_ptr(pFramesIn, framesProcessedIn * ma_get_bytes_per_frame(pConverter->config.formatIn, pConverter->config.channelsIn)); + pRunningFramesIn = ma_offset_ptr(pFramesIn, framesProcessedIn * ma_get_bytes_per_frame(pConverter->formatIn, pConverter->channelsIn)); } if (pFramesOut != NULL) { - pRunningFramesOut = ma_offset_ptr(pFramesOut, framesProcessedOut * ma_get_bytes_per_frame(pConverter->config.formatOut, pConverter->config.channelsOut)); + pRunningFramesOut = ma_offset_ptr(pFramesOut, framesProcessedOut * ma_get_bytes_per_frame(pConverter->formatOut, pConverter->channelsOut)); } - /* Run input data through the channel converter and output it to the temporary buffer. */ - frameCountInThisIteration = (frameCountIn - framesProcessedIn); + /* + Before doing any processing we need to determine how many frames we should try processing + this iteration, for both input and output. The resampler requires us to perform format and + channel conversion before passing any data into it. If we get our input count wrong, we'll + end up peforming redundant pre-processing. This isn't the end of the world, but it does + result in some inefficiencies proportionate to how far our estimates are off. + If the resampler has a means to calculate exactly how much we'll need, we'll use that. + Otherwise we'll make a best guess. In order to do this, we'll need to calculate the output + frame count first. + */ + frameCountOutThisIteration = (frameCountOut - framesProcessedOut); + if (frameCountOutThisIteration > tempBufferMidCap) { + frameCountOutThisIteration = tempBufferMidCap; + } + + if (pConverter->hasPostFormatConversion) { + if (frameCountOutThisIteration > tempBufferOutCap) { + frameCountOutThisIteration = tempBufferOutCap; + } + } + + /* Now that we have the output frame count we can determine the input frame count. */ + frameCountInThisIteration = (frameCountIn - framesProcessedIn); if (pConverter->hasPreFormatConversion) { if (frameCountInThisIteration > tempBufferInCap) { frameCountInThisIteration = tempBufferInCap; } + } + if (frameCountInThisIteration > tempBufferMidCap) { + frameCountInThisIteration = tempBufferMidCap; + } + + #if 1 + { + ma_uint64 requiredInputFrameCount; + + result = ma_resampler_get_required_input_frame_count(&pConverter->resampler, frameCountOutThisIteration, &requiredInputFrameCount); + if (result != MA_SUCCESS) { + /* Fall back to a best guess. */ + requiredInputFrameCount = (frameCountOutThisIteration * pConverter->resampler.sampleRateIn) / pConverter->resampler.sampleRateOut; + } + + if (frameCountInThisIteration > requiredInputFrameCount) { + frameCountInThisIteration = requiredInputFrameCount; + } + } + #endif + + + /* Pre format conversion. */ + if (pConverter->hasPreFormatConversion) { if (pRunningFramesIn != NULL) { - ma_convert_pcm_frames_format(pTempBufferIn, pConverter->channelConverter.format, pRunningFramesIn, pConverter->config.formatIn, frameCountInThisIteration, pConverter->config.channelsIn, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pTempBufferIn, pConverter->channelConverter.format, pRunningFramesIn, pConverter->formatIn, frameCountInThisIteration, pConverter->channelsIn, pConverter->ditherMode); pChannelsBufferIn = pTempBufferIn; } else { pChannelsBufferIn = NULL; @@ -35244,43 +40285,15 @@ static ma_result ma_data_converter_process_pcm_frames__channels_first(ma_data_co pChannelsBufferIn = pRunningFramesIn; } - /* - We can't convert more frames than will fit in the output buffer. We shouldn't actually need to do this check because the channel count is always reduced - in this case which means we should always have capacity, but I'm leaving it here just for safety for future maintenance. - */ - if (frameCountInThisIteration > tempBufferMidCap) { - frameCountInThisIteration = tempBufferMidCap; - } - - /* - Make sure we don't read any more input frames than we need to fill the output frame count. If we do this we will end up in a situation where we lose some - input samples and will end up glitching. - */ - frameCountOutThisIteration = (frameCountOut - framesProcessedOut); - if (frameCountOutThisIteration > tempBufferMidCap) { - frameCountOutThisIteration = tempBufferMidCap; - } - - if (pConverter->hasPostFormatConversion) { - ma_uint64 requiredInputFrameCount; - - if (frameCountOutThisIteration > tempBufferOutCap) { - frameCountOutThisIteration = tempBufferOutCap; - } - - requiredInputFrameCount = ma_resampler_get_required_input_frame_count(&pConverter->resampler, frameCountOutThisIteration); - if (frameCountInThisIteration > requiredInputFrameCount) { - frameCountInThisIteration = requiredInputFrameCount; - } - } + /* Channel conversion. */ result = ma_channel_converter_process_pcm_frames(&pConverter->channelConverter, pTempBufferMid, pChannelsBufferIn, frameCountInThisIteration); if (result != MA_SUCCESS) { return result; } - /* At this point we have converted the channels to the output channel count which we now need to resample. */ + /* Resampling. */ if (pConverter->hasPostFormatConversion) { pResampleBufferOut = pTempBufferOut; } else { @@ -35292,13 +40305,15 @@ static ma_result ma_data_converter_process_pcm_frames__channels_first(ma_data_co return result; } - /* Finally we can do the post format conversion. */ + + /* Post format conversion. */ if (pConverter->hasPostFormatConversion) { if (pRunningFramesOut != NULL) { - ma_convert_pcm_frames_format(pRunningFramesOut, pConverter->config.formatOut, pResampleBufferOut, pConverter->resampler.config.format, frameCountOutThisIteration, pConverter->config.channelsOut, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pRunningFramesOut, pConverter->formatOut, pResampleBufferOut, pConverter->resampler.format, frameCountOutThisIteration, pConverter->channelsOut, pConverter->ditherMode); } } + framesProcessedIn += frameCountInThisIteration; framesProcessedOut += frameCountOutThisIteration; @@ -35326,46 +40341,15 @@ MA_API ma_result ma_data_converter_process_pcm_frames(ma_data_converter* pConver return MA_INVALID_ARGS; } - if (pConverter->isPassthrough) { - return ma_data_converter_process_pcm_frames__passthrough(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } - - /* - Here is where the real work is done. Getting here means we're not using a passthrough and we need to move the data through each of the relevant stages. The order - of our stages depends on the input and output channel count. If the input channels is less than the output channels we want to do sample rate conversion first so - that it has less work (resampling is the most expensive part of format conversion). - */ - if (pConverter->config.channelsIn < pConverter->config.channelsOut) { - /* Do resampling first, if necessary. */ - MA_ASSERT(pConverter->hasChannelConverter == MA_TRUE); - - if (pConverter->hasResampler) { - /* Resampling first. */ - return ma_data_converter_process_pcm_frames__resampling_first(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } else { - /* Resampling not required. */ - return ma_data_converter_process_pcm_frames__channels_only(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } - } else { - /* Do channel conversion first, if necessary. */ - if (pConverter->hasChannelConverter) { - if (pConverter->hasResampler) { - /* Channel routing first. */ - return ma_data_converter_process_pcm_frames__channels_first(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } else { - /* Resampling not required. */ - return ma_data_converter_process_pcm_frames__channels_only(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } - } else { - /* Channel routing not required. */ - if (pConverter->hasResampler) { - /* Resampling only. */ - return ma_data_converter_process_pcm_frames__resample_only(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } else { - /* No channel routing nor resampling required. Just format conversion. */ - return ma_data_converter_process_pcm_frames__format_only(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } - } + switch (pConverter->executionPath) + { + case ma_data_converter_execution_path_passthrough: return ma_data_converter_process_pcm_frames__passthrough(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); + case ma_data_converter_execution_path_format_only: return ma_data_converter_process_pcm_frames__format_only(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); + case ma_data_converter_execution_path_channels_only: return ma_data_converter_process_pcm_frames__channels_only(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); + case ma_data_converter_execution_path_resample_only: return ma_data_converter_process_pcm_frames__resample_only(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); + case ma_data_converter_execution_path_resample_first: return ma_data_converter_process_pcm_frames__resample_first(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); + case ma_data_converter_execution_path_channels_first: return ma_data_converter_process_pcm_frames__channels_first(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); + default: return MA_INVALID_OPERATION; /* Should never hit this. */ } } @@ -35395,32 +40379,6 @@ MA_API ma_result ma_data_converter_set_rate_ratio(ma_data_converter* pConverter, return ma_resampler_set_rate_ratio(&pConverter->resampler, ratioInOut); } -MA_API ma_uint64 ma_data_converter_get_required_input_frame_count(const ma_data_converter* pConverter, ma_uint64 outputFrameCount) -{ - if (pConverter == NULL) { - return 0; - } - - if (pConverter->hasResampler) { - return ma_resampler_get_required_input_frame_count(&pConverter->resampler, outputFrameCount); - } else { - return outputFrameCount; /* 1:1 */ - } -} - -MA_API ma_uint64 ma_data_converter_get_expected_output_frame_count(const ma_data_converter* pConverter, ma_uint64 inputFrameCount) -{ - if (pConverter == NULL) { - return 0; - } - - if (pConverter->hasResampler) { - return ma_resampler_get_expected_output_frame_count(&pConverter->resampler, inputFrameCount); - } else { - return inputFrameCount; /* 1:1 */ - } -} - MA_API ma_uint64 ma_data_converter_get_input_latency(const ma_data_converter* pConverter) { if (pConverter == NULL) { @@ -35447,6 +40405,76 @@ MA_API ma_uint64 ma_data_converter_get_output_latency(const ma_data_converter* p return 0; /* No latency without a resampler. */ } +MA_API ma_result ma_data_converter_get_required_input_frame_count(const ma_data_converter* pConverter, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount) +{ + if (pInputFrameCount == NULL) { + return MA_INVALID_ARGS; + } + + *pInputFrameCount = 0; + + if (pConverter == NULL) { + return MA_INVALID_ARGS; + } + + if (pConverter->hasResampler) { + return ma_resampler_get_required_input_frame_count(&pConverter->resampler, outputFrameCount, pInputFrameCount); + } else { + *pInputFrameCount = outputFrameCount; /* 1:1 */ + return MA_SUCCESS; + } +} + +MA_API ma_result ma_data_converter_get_expected_output_frame_count(const ma_data_converter* pConverter, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount) +{ + if (pOutputFrameCount == NULL) { + return MA_INVALID_ARGS; + } + + *pOutputFrameCount = 0; + + if (pConverter == NULL) { + return MA_INVALID_ARGS; + } + + if (pConverter->hasResampler) { + return ma_resampler_get_expected_output_frame_count(&pConverter->resampler, inputFrameCount, pOutputFrameCount); + } else { + *pOutputFrameCount = inputFrameCount; /* 1:1 */ + return MA_SUCCESS; + } +} + +MA_API ma_result ma_data_converter_get_input_channel_map(const ma_data_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap) +{ + if (pConverter == NULL || pChannelMap == NULL) { + return MA_INVALID_ARGS; + } + + if (pConverter->hasChannelConverter) { + ma_channel_converter_get_output_channel_map(&pConverter->channelConverter, pChannelMap, channelMapCap); + } else { + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pConverter->channelsOut); + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_data_converter_get_output_channel_map(const ma_data_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap) +{ + if (pConverter == NULL || pChannelMap == NULL) { + return MA_INVALID_ARGS; + } + + if (pConverter->hasChannelConverter) { + ma_channel_converter_get_input_channel_map(&pConverter->channelConverter, pChannelMap, channelMapCap); + } else { + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pConverter->channelsIn); + } + + return MA_SUCCESS; +} + /************************************************************************************************************************************************************** @@ -35454,7 +40482,32 @@ MA_API ma_uint64 ma_data_converter_get_output_latency(const ma_data_converter* p Channel Maps **************************************************************************************************************************************************************/ -MA_API ma_channel ma_channel_map_get_default_channel(ma_uint32 channelCount, ma_uint32 channelIndex) +static ma_channel ma_channel_map_init_standard_channel(ma_standard_channel_map standardChannelMap, ma_uint32 channelCount, ma_uint32 channelIndex); + +MA_API ma_channel ma_channel_map_get_channel(const ma_channel* pChannelMap, ma_uint32 channelCount, ma_uint32 channelIndex) +{ + if (pChannelMap == NULL) { + return ma_channel_map_init_standard_channel(ma_standard_channel_map_default, channelCount, channelIndex); + } else { + if (channelIndex >= channelCount) { + return MA_CHANNEL_NONE; + } + + return pChannelMap[channelIndex]; + } +} + +MA_API void ma_channel_map_init_blank(ma_channel* pChannelMap, ma_uint32 channels) +{ + if (pChannelMap == NULL) { + return; + } + + MA_ZERO_MEMORY(pChannelMap, sizeof(*pChannelMap) * channels); +} + + +static ma_channel ma_channel_map_init_standard_channel_microsoft(ma_uint32 channelCount, ma_uint32 channelIndex) { if (channelCount == 0 || channelIndex >= channelCount) { return MA_CHANNEL_NONE; @@ -35463,7 +40516,7 @@ MA_API ma_channel ma_channel_map_get_default_channel(ma_uint32 channelCount, ma_ /* This is the Microsoft channel map. Based off the speaker configurations mentioned here: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ksmedia/ns-ksmedia-ksaudio_channel_config */ switch (channelCount) { - case 0: return MA_CHANNEL_NONE; + case 0: return MA_CHANNEL_NONE; case 1: { @@ -35568,645 +40621,619 @@ MA_API ma_channel ma_channel_map_get_default_channel(ma_uint32 channelCount, ma_ return MA_CHANNEL_NONE; } -MA_API ma_channel ma_channel_map_get_channel(const ma_channel* pChannelMap, ma_uint32 channelCount, ma_uint32 channelIndex) +static ma_channel ma_channel_map_init_standard_channel_alsa(ma_uint32 channelCount, ma_uint32 channelIndex) { - if (pChannelMap == NULL) { - return ma_channel_map_get_default_channel(channelCount, channelIndex); - } else { - if (channelIndex >= channelCount) { - return MA_CHANNEL_NONE; - } - - return pChannelMap[channelIndex]; - } -} - - -MA_API void ma_channel_map_init_blank(ma_uint32 channels, ma_channel* pChannelMap) -{ - if (pChannelMap == NULL) { - return; - } - - MA_ZERO_MEMORY(pChannelMap, sizeof(*pChannelMap) * channels); -} - -static void ma_get_standard_channel_map_microsoft(ma_uint32 channels, ma_channel* pChannelMap) -{ - /* Based off the speaker configurations mentioned here: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ksmedia/ns-ksmedia-ksaudio_channel_config */ - switch (channels) + switch (channelCount) { + case 0: return MA_CHANNEL_NONE; + case 1: { - pChannelMap[0] = MA_CHANNEL_MONO; + return MA_CHANNEL_MONO; } break; case 2: { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + } } break; - case 3: /* Not defined, but best guess. */ + case 3: { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + } } break; case 4: { -#ifndef MA_USE_QUAD_MICROSOFT_CHANNEL_MAP - /* Surround. Using the Surround profile has the advantage of the 3rd channel (MA_CHANNEL_FRONT_CENTER) mapping nicely with higher channel counts. */ - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_BACK_CENTER; -#else - /* Quad. */ - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; -#endif + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 5: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + case 4: return MA_CHANNEL_FRONT_CENTER; + } + } break; + + case 6: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + case 4: return MA_CHANNEL_FRONT_CENTER; + case 5: return MA_CHANNEL_LFE; + } + } break; + + case 7: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + case 4: return MA_CHANNEL_FRONT_CENTER; + case 5: return MA_CHANNEL_LFE; + case 6: return MA_CHANNEL_BACK_CENTER; + } + } break; + + case 8: + default: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + case 4: return MA_CHANNEL_FRONT_CENTER; + case 5: return MA_CHANNEL_LFE; + case 6: return MA_CHANNEL_SIDE_LEFT; + case 7: return MA_CHANNEL_SIDE_RIGHT; + } + } break; + } + + if (channelCount > 8) { + if (channelIndex < 32) { /* We have 32 AUX channels. */ + return (ma_channel)(MA_CHANNEL_AUX_0 + (channelIndex - 8)); + } + } + + /* Getting here means we don't know how to map the channel position so just return MA_CHANNEL_NONE. */ + return MA_CHANNEL_NONE; +} + +static ma_channel ma_channel_map_init_standard_channel_rfc3551(ma_uint32 channelCount, ma_uint32 channelIndex) +{ + switch (channelCount) + { + case 0: return MA_CHANNEL_NONE; + + case 1: + { + return MA_CHANNEL_MONO; + } break; + + case 2: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + } + } break; + + case 3: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + } + } break; + + case 4: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_BACK_CENTER; + } + } break; + + case 5: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 3: return MA_CHANNEL_BACK_LEFT; + case 4: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 6: + default: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_SIDE_LEFT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 3: return MA_CHANNEL_FRONT_RIGHT; + case 4: return MA_CHANNEL_SIDE_RIGHT; + case 5: return MA_CHANNEL_BACK_CENTER; + } + } break; + } + + if (channelCount > 6) { + if (channelIndex < 32) { /* We have 32 AUX channels. */ + return (ma_channel)(MA_CHANNEL_AUX_0 + (channelIndex - 6)); + } + } + + /* Getting here means we don't know how to map the channel position so just return MA_CHANNEL_NONE. */ + return MA_CHANNEL_NONE; +} + +static ma_channel ma_channel_map_init_standard_channel_flac(ma_uint32 channelCount, ma_uint32 channelIndex) +{ + switch (channelCount) + { + case 0: return MA_CHANNEL_NONE; + + case 1: + { + return MA_CHANNEL_MONO; + } break; + + case 2: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + } + } break; + + case 3: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + } + } break; + + case 4: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 5: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 3: return MA_CHANNEL_BACK_LEFT; + case 4: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 6: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 3: return MA_CHANNEL_LFE; + case 4: return MA_CHANNEL_BACK_LEFT; + case 5: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 7: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 3: return MA_CHANNEL_LFE; + case 4: return MA_CHANNEL_BACK_CENTER; + case 5: return MA_CHANNEL_SIDE_LEFT; + case 6: return MA_CHANNEL_SIDE_RIGHT; + } + } break; + + case 8: + default: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 3: return MA_CHANNEL_LFE; + case 4: return MA_CHANNEL_BACK_LEFT; + case 5: return MA_CHANNEL_BACK_RIGHT; + case 6: return MA_CHANNEL_SIDE_LEFT; + case 7: return MA_CHANNEL_SIDE_RIGHT; + } + } break; + } + + if (channelCount > 8) { + if (channelIndex < 32) { /* We have 32 AUX channels. */ + return (ma_channel)(MA_CHANNEL_AUX_0 + (channelIndex - 8)); + } + } + + /* Getting here means we don't know how to map the channel position so just return MA_CHANNEL_NONE. */ + return MA_CHANNEL_NONE; +} + +static ma_channel ma_channel_map_init_standard_channel_vorbis(ma_uint32 channelCount, ma_uint32 channelIndex) +{ + switch (channelCount) + { + case 0: return MA_CHANNEL_NONE; + + case 1: + { + return MA_CHANNEL_MONO; + } break; + + case 2: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + } + } break; + + case 3: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + } + } break; + + case 4: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 5: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_BACK_LEFT; + case 4: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 6: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_BACK_LEFT; + case 4: return MA_CHANNEL_BACK_RIGHT; + case 5: return MA_CHANNEL_LFE; + } + } break; + + case 7: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_SIDE_LEFT; + case 4: return MA_CHANNEL_SIDE_RIGHT; + case 5: return MA_CHANNEL_BACK_CENTER; + case 6: return MA_CHANNEL_LFE; + } + } break; + + case 8: + default: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_SIDE_LEFT; + case 4: return MA_CHANNEL_SIDE_RIGHT; + case 5: return MA_CHANNEL_BACK_LEFT; + case 6: return MA_CHANNEL_BACK_RIGHT; + case 7: return MA_CHANNEL_LFE; + } + } break; + } + + if (channelCount > 8) { + if (channelIndex < 32) { /* We have 32 AUX channels. */ + return (ma_channel)(MA_CHANNEL_AUX_0 + (channelIndex - 8)); + } + } + + /* Getting here means we don't know how to map the channel position so just return MA_CHANNEL_NONE. */ + return MA_CHANNEL_NONE; +} + +static ma_channel ma_channel_map_init_standard_channel_sound4(ma_uint32 channelCount, ma_uint32 channelIndex) +{ + switch (channelCount) + { + case 0: return MA_CHANNEL_NONE; + + case 1: + { + return MA_CHANNEL_MONO; + } break; + + case 2: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + } + } break; + + case 3: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + } + } break; + + case 4: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 5: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 3: return MA_CHANNEL_BACK_LEFT; + case 4: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 6: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_BACK_LEFT; + case 4: return MA_CHANNEL_BACK_RIGHT; + case 5: return MA_CHANNEL_LFE; + } + } break; + + case 7: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_SIDE_LEFT; + case 4: return MA_CHANNEL_SIDE_RIGHT; + case 5: return MA_CHANNEL_BACK_CENTER; + case 6: return MA_CHANNEL_LFE; + } + } break; + + case 8: + default: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_SIDE_LEFT; + case 4: return MA_CHANNEL_SIDE_RIGHT; + case 5: return MA_CHANNEL_BACK_LEFT; + case 6: return MA_CHANNEL_BACK_RIGHT; + case 7: return MA_CHANNEL_LFE; + } + } break; + } + + if (channelCount > 8) { + if (channelIndex < 32) { /* We have 32 AUX channels. */ + return (ma_channel)(MA_CHANNEL_AUX_0 + (channelIndex - 8)); + } + } + + /* Getting here means we don't know how to map the channel position so just return MA_CHANNEL_NONE. */ + return MA_CHANNEL_NONE; +} + +static ma_channel ma_channel_map_init_standard_channel_sndio(ma_uint32 channelCount, ma_uint32 channelIndex) +{ + switch (channelCount) + { + case 0: return MA_CHANNEL_NONE; + + case 1: + { + return MA_CHANNEL_MONO; + } break; + + case 2: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + } + } break; + + case 3: /* No defined, but best guess. */ + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + } + } break; + + case 4: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + } } break; case 5: /* Not defined, but best guess. */ { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_BACK_LEFT; - pChannelMap[4] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 6: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_LFE; - pChannelMap[4] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[5] = MA_CHANNEL_SIDE_RIGHT; - } break; - - case 7: /* Not defined, but best guess. */ - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_LFE; - pChannelMap[4] = MA_CHANNEL_BACK_CENTER; - pChannelMap[5] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[6] = MA_CHANNEL_SIDE_RIGHT; - } break; - - case 8: - default: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_LFE; - pChannelMap[4] = MA_CHANNEL_BACK_LEFT; - pChannelMap[5] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[6] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[7] = MA_CHANNEL_SIDE_RIGHT; - } break; - } - - /* Remainder. */ - if (channels > 8) { - ma_uint32 iChannel; - for (iChannel = 8; iChannel < channels; ++iChannel) { - if (iChannel < MA_MAX_CHANNELS) { - pChannelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-8)); - } else { - pChannelMap[iChannel] = MA_CHANNEL_NONE; + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + case 4: return MA_CHANNEL_FRONT_CENTER; } - } - } -} - -static void ma_get_standard_channel_map_alsa(ma_uint32 channels, ma_channel* pChannelMap) -{ - switch (channels) - { - case 1: - { - pChannelMap[0] = MA_CHANNEL_MONO; - } break; - - case 2: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - } break; - - case 3: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - } break; - - case 4: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 5: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - } break; - - case 6: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[5] = MA_CHANNEL_LFE; - } break; - - case 7: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[5] = MA_CHANNEL_LFE; - pChannelMap[6] = MA_CHANNEL_BACK_CENTER; - } break; - - case 8: - default: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[5] = MA_CHANNEL_LFE; - pChannelMap[6] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[7] = MA_CHANNEL_SIDE_RIGHT; - } break; - } - - /* Remainder. */ - if (channels > 8) { - ma_uint32 iChannel; - for (iChannel = 8; iChannel < channels; ++iChannel) { - if (iChannel < MA_MAX_CHANNELS) { - pChannelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-8)); - } else { - pChannelMap[iChannel] = MA_CHANNEL_NONE; - } - } - } -} - -static void ma_get_standard_channel_map_rfc3551(ma_uint32 channels, ma_channel* pChannelMap) -{ - switch (channels) - { - case 1: - { - pChannelMap[0] = MA_CHANNEL_MONO; - } break; - - case 2: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - } break; - - case 3: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - } break; - - case 4: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[2] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[3] = MA_CHANNEL_BACK_CENTER; - } break; - - case 5: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_BACK_LEFT; - pChannelMap[4] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 6: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[4] = MA_CHANNEL_SIDE_RIGHT; - pChannelMap[5] = MA_CHANNEL_BACK_CENTER; - } break; - } - - /* Remainder. */ - if (channels > 8) { - ma_uint32 iChannel; - for (iChannel = 6; iChannel < channels; ++iChannel) { - if (iChannel < MA_MAX_CHANNELS) { - pChannelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-6)); - } else { - pChannelMap[iChannel] = MA_CHANNEL_NONE; - } - } - } -} - -static void ma_get_standard_channel_map_flac(ma_uint32 channels, ma_channel* pChannelMap) -{ - switch (channels) - { - case 1: - { - pChannelMap[0] = MA_CHANNEL_MONO; - } break; - - case 2: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - } break; - - case 3: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - } break; - - case 4: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 5: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_BACK_LEFT; - pChannelMap[4] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 6: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_LFE; - pChannelMap[4] = MA_CHANNEL_BACK_LEFT; - pChannelMap[5] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 7: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_LFE; - pChannelMap[4] = MA_CHANNEL_BACK_CENTER; - pChannelMap[5] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[6] = MA_CHANNEL_SIDE_RIGHT; - } break; - - case 8: - default: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_LFE; - pChannelMap[4] = MA_CHANNEL_BACK_LEFT; - pChannelMap[5] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[6] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[7] = MA_CHANNEL_SIDE_RIGHT; - } break; - } - - /* Remainder. */ - if (channels > 8) { - ma_uint32 iChannel; - for (iChannel = 8; iChannel < channels; ++iChannel) { - if (iChannel < MA_MAX_CHANNELS) { - pChannelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-8)); - } else { - pChannelMap[iChannel] = MA_CHANNEL_NONE; - } - } - } -} - -static void ma_get_standard_channel_map_vorbis(ma_uint32 channels, ma_channel* pChannelMap) -{ - /* In Vorbis' type 0 channel mapping, the first two channels are not always the standard left/right - it will have the center speaker where the right usually goes. Why?! */ - switch (channels) - { - case 1: - { - pChannelMap[0] = MA_CHANNEL_MONO; - } break; - - case 2: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - } break; - - case 3: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[2] = MA_CHANNEL_FRONT_RIGHT; - } break; - - case 4: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 5: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[2] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[3] = MA_CHANNEL_BACK_LEFT; - pChannelMap[4] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 6: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[2] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[3] = MA_CHANNEL_BACK_LEFT; - pChannelMap[4] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[5] = MA_CHANNEL_LFE; - } break; - - case 7: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[2] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[3] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[4] = MA_CHANNEL_SIDE_RIGHT; - pChannelMap[5] = MA_CHANNEL_BACK_CENTER; - pChannelMap[6] = MA_CHANNEL_LFE; - } break; - - case 8: - default: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[2] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[3] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[4] = MA_CHANNEL_SIDE_RIGHT; - pChannelMap[5] = MA_CHANNEL_BACK_LEFT; - pChannelMap[6] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[7] = MA_CHANNEL_LFE; - } break; - } - - /* Remainder. */ - if (channels > 8) { - ma_uint32 iChannel; - for (iChannel = 8; iChannel < channels; ++iChannel) { - if (iChannel < MA_MAX_CHANNELS) { - pChannelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-8)); - } else { - pChannelMap[iChannel] = MA_CHANNEL_NONE; - } - } - } -} - -static void ma_get_standard_channel_map_sound4(ma_uint32 channels, ma_channel* pChannelMap) -{ - switch (channels) - { - case 1: - { - pChannelMap[0] = MA_CHANNEL_MONO; - } break; - - case 2: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - } break; - - case 3: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_CENTER; - } break; - - case 4: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 5: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - } break; - - case 6: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[5] = MA_CHANNEL_LFE; - } break; - - case 7: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[5] = MA_CHANNEL_BACK_CENTER; - pChannelMap[6] = MA_CHANNEL_LFE; - } break; - - case 8: - default: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[5] = MA_CHANNEL_LFE; - pChannelMap[6] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[7] = MA_CHANNEL_SIDE_RIGHT; - } break; - } - - /* Remainder. */ - if (channels > 8) { - ma_uint32 iChannel; - for (iChannel = 8; iChannel < MA_MAX_CHANNELS; ++iChannel) { - if (iChannel < MA_MAX_CHANNELS) { - pChannelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-8)); - } else { - pChannelMap[iChannel] = MA_CHANNEL_NONE; - } - } - } -} - -static void ma_get_standard_channel_map_sndio(ma_uint32 channels, ma_channel* pChannelMap) -{ - switch (channels) - { - case 1: - { - pChannelMap[0] = MA_CHANNEL_MONO; - } break; - - case 2: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - } break; - - case 3: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - } break; - - case 4: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 5: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; } break; case 6: default: { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[5] = MA_CHANNEL_LFE; + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + case 4: return MA_CHANNEL_FRONT_CENTER; + case 5: return MA_CHANNEL_LFE; + } } break; } - /* Remainder. */ - if (channels > 6) { - ma_uint32 iChannel; - for (iChannel = 6; iChannel < channels && iChannel < MA_MAX_CHANNELS; ++iChannel) { - if (iChannel < MA_MAX_CHANNELS) { - pChannelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-6)); - } else { - pChannelMap[iChannel] = MA_CHANNEL_NONE; - } + if (channelCount > 6) { + if (channelIndex < 32) { /* We have 32 AUX channels. */ + return (ma_channel)(MA_CHANNEL_AUX_0 + (channelIndex - 6)); } } + + /* Getting here means we don't know how to map the channel position so just return MA_CHANNEL_NONE. */ + return MA_CHANNEL_NONE; } -MA_API void ma_get_standard_channel_map(ma_standard_channel_map standardChannelMap, ma_uint32 channels, ma_channel* pChannelMap) + +static ma_channel ma_channel_map_init_standard_channel(ma_standard_channel_map standardChannelMap, ma_uint32 channelCount, ma_uint32 channelIndex) { + if (channelCount == 0 || channelIndex >= channelCount) { + return MA_CHANNEL_NONE; + } + switch (standardChannelMap) { case ma_standard_channel_map_alsa: { - ma_get_standard_channel_map_alsa(channels, pChannelMap); + return ma_channel_map_init_standard_channel_alsa(channelCount, channelIndex); } break; case ma_standard_channel_map_rfc3551: { - ma_get_standard_channel_map_rfc3551(channels, pChannelMap); + return ma_channel_map_init_standard_channel_rfc3551(channelCount, channelIndex); } break; case ma_standard_channel_map_flac: { - ma_get_standard_channel_map_flac(channels, pChannelMap); + return ma_channel_map_init_standard_channel_flac(channelCount, channelIndex); } break; case ma_standard_channel_map_vorbis: { - ma_get_standard_channel_map_vorbis(channels, pChannelMap); + return ma_channel_map_init_standard_channel_vorbis(channelCount, channelIndex); } break; case ma_standard_channel_map_sound4: { - ma_get_standard_channel_map_sound4(channels, pChannelMap); + return ma_channel_map_init_standard_channel_sound4(channelCount, channelIndex); } break; case ma_standard_channel_map_sndio: { - ma_get_standard_channel_map_sndio(channels, pChannelMap); + return ma_channel_map_init_standard_channel_sndio(channelCount, channelIndex); } break; case ma_standard_channel_map_microsoft: /* Also default. */ /*case ma_standard_channel_map_default;*/ default: { - ma_get_standard_channel_map_microsoft(channels, pChannelMap); + return ma_channel_map_init_standard_channel_microsoft(channelCount, channelIndex); } break; } } +MA_API void ma_channel_map_init_standard(ma_standard_channel_map standardChannelMap, ma_channel* pChannelMap, size_t channelMapCap, ma_uint32 channels) +{ + ma_uint32 iChannel; + + if (pChannelMap == NULL || channelMapCap == 0 || channels == 0) { + return; + } + + for (iChannel = 0; iChannel < channels; iChannel += 1) { + if (channelMapCap == 0) { + break; /* Ran out of room. */ + } + + pChannelMap[0] = ma_channel_map_init_standard_channel(standardChannelMap, channels, iChannel); + pChannelMap += 1; + channelMapCap -= 1; + } +} + MA_API void ma_channel_map_copy(ma_channel* pOut, const ma_channel* pIn, ma_uint32 channels) { if (pOut != NULL && pIn != NULL && channels > 0) { @@ -36214,7 +41241,7 @@ MA_API void ma_channel_map_copy(ma_channel* pOut, const ma_channel* pIn, ma_uint } } -MA_API void ma_channel_map_copy_or_default(ma_channel* pOut, const ma_channel* pIn, ma_uint32 channels) +MA_API void ma_channel_map_copy_or_default(ma_channel* pOut, size_t channelMapCapOut, const ma_channel* pIn, ma_uint32 channels) { if (pOut == NULL || channels == 0) { return; @@ -36223,16 +41250,12 @@ MA_API void ma_channel_map_copy_or_default(ma_channel* pOut, const ma_channel* p if (pIn != NULL) { ma_channel_map_copy(pOut, pIn, channels); } else { - ma_get_standard_channel_map(ma_standard_channel_map_default, channels, pOut); + ma_channel_map_init_standard(ma_standard_channel_map_default, pOut, channelMapCapOut, channels); } } -MA_API ma_bool32 ma_channel_map_valid(ma_uint32 channels, const ma_channel* pChannelMap) +MA_API ma_bool32 ma_channel_map_is_valid(const ma_channel* pChannelMap, ma_uint32 channels) { - if (pChannelMap == NULL) { - return MA_FALSE; - } - /* A channel count of 0 is invalid. */ if (channels == 0) { return MA_FALSE; @@ -36242,7 +41265,7 @@ MA_API ma_bool32 ma_channel_map_valid(ma_uint32 channels, const ma_channel* pCha if (channels > 1) { ma_uint32 iChannel; for (iChannel = 0; iChannel < channels; ++iChannel) { - if (pChannelMap[iChannel] == MA_CHANNEL_MONO) { + if (ma_channel_map_get_channel(pChannelMap, channels, iChannel) == MA_CHANNEL_MONO) { return MA_FALSE; } } @@ -36251,7 +41274,7 @@ MA_API ma_bool32 ma_channel_map_valid(ma_uint32 channels, const ma_channel* pCha return MA_TRUE; } -MA_API ma_bool32 ma_channel_map_equal(ma_uint32 channels, const ma_channel* pChannelMapA, const ma_channel* pChannelMapB) +MA_API ma_bool32 ma_channel_map_is_equal(const ma_channel* pChannelMapA, const ma_channel* pChannelMapB, ma_uint32 channels) { ma_uint32 iChannel; @@ -36268,7 +41291,7 @@ MA_API ma_bool32 ma_channel_map_equal(ma_uint32 channels, const ma_channel* pCha return MA_TRUE; } -MA_API ma_bool32 ma_channel_map_blank(ma_uint32 channels, const ma_channel* pChannelMap) +MA_API ma_bool32 ma_channel_map_is_blank(const ma_channel* pChannelMap, ma_uint32 channels) { ma_uint32 iChannel; @@ -36311,8 +41334,6 @@ MA_API ma_uint64 ma_convert_frames(void* pOut, ma_uint64 frameCountOut, ma_forma ma_data_converter_config config; config = ma_data_converter_config_init(formatIn, formatOut, channelsIn, channelsOut, sampleRateIn, sampleRateOut); - ma_get_standard_channel_map(ma_standard_channel_map_default, channelsOut, config.channelMapOut); - ma_get_standard_channel_map(ma_standard_channel_map_default, channelsIn, config.channelMapIn); config.resampling.linear.lpfOrder = ma_min(MA_DEFAULT_RESAMPLER_LPF_ORDER, MA_MAX_FILTER_ORDER); return ma_convert_frames_ex(pOut, frameCountOut, pIn, frameCountIn, &config); @@ -36327,13 +41348,31 @@ MA_API ma_uint64 ma_convert_frames_ex(void* pOut, ma_uint64 frameCountOut, const return 0; } - result = ma_data_converter_init(pConfig, &converter); + result = ma_data_converter_init(pConfig, NULL, &converter); if (result != MA_SUCCESS) { return 0; /* Failed to initialize the data converter. */ } if (pOut == NULL) { - frameCountOut = ma_data_converter_get_expected_output_frame_count(&converter, frameCountIn); + result = ma_data_converter_get_expected_output_frame_count(&converter, frameCountIn, &frameCountOut); + if (result != MA_SUCCESS) { + if (result == MA_NOT_IMPLEMENTED) { + /* No way to calculate the number of frames, so we'll need to brute force it and loop. */ + frameCountOut = 0; + + while (frameCountIn > 0) { + ma_uint64 framesProcessedIn = frameCountIn; + ma_uint64 framesProcessedOut = 0xFFFFFFFF; + + result = ma_data_converter_process_pcm_frames(&converter, pIn, &framesProcessedIn, NULL, &framesProcessedOut); + if (result != MA_SUCCESS) { + break; + } + + frameCountIn -= framesProcessedIn; + } + } + } } else { result = ma_data_converter_process_pcm_frames(&converter, pIn, &frameCountIn, pOut, &frameCountOut); if (result != MA_SUCCESS) { @@ -36341,7 +41380,7 @@ MA_API ma_uint64 ma_convert_frames_ex(void* pOut, ma_uint64 frameCountOut, const } } - ma_data_converter_uninit(&converter); + ma_data_converter_uninit(&converter, NULL); return frameCountOut; } @@ -36510,7 +41549,7 @@ MA_API ma_result ma_rb_acquire_read(ma_rb* pRB, size_t* pSizeInBytes, void** ppB return MA_SUCCESS; } -MA_API ma_result ma_rb_commit_read(ma_rb* pRB, size_t sizeInBytes, void* pBufferOut) +MA_API ma_result ma_rb_commit_read(ma_rb* pRB, size_t sizeInBytes) { ma_uint32 readOffset; ma_uint32 readOffsetInBytes; @@ -36522,11 +41561,6 @@ MA_API ma_result ma_rb_commit_read(ma_rb* pRB, size_t sizeInBytes, void* pBuffer return MA_INVALID_ARGS; } - /* Validate the buffer. */ - if (pBufferOut != ma_rb__get_read_ptr(pRB)) { - return MA_INVALID_ARGS; - } - readOffset = c89atomic_load_32(&pRB->encodedReadOffset); ma_rb__deconstruct_offset(readOffset, &readOffsetInBytes, &readOffsetLoopFlag); @@ -36601,7 +41635,7 @@ MA_API ma_result ma_rb_acquire_write(ma_rb* pRB, size_t* pSizeInBytes, void** pp return MA_SUCCESS; } -MA_API ma_result ma_rb_commit_write(ma_rb* pRB, size_t sizeInBytes, void* pBufferOut) +MA_API ma_result ma_rb_commit_write(ma_rb* pRB, size_t sizeInBytes) { ma_uint32 writeOffset; ma_uint32 writeOffsetInBytes; @@ -36613,11 +41647,6 @@ MA_API ma_result ma_rb_commit_write(ma_rb* pRB, size_t sizeInBytes, void* pBuffe return MA_INVALID_ARGS; } - /* Validate the buffer. */ - if (pBufferOut != ma_rb__get_write_ptr(pRB)) { - return MA_INVALID_ARGS; - } - writeOffset = c89atomic_load_32(&pRB->encodedWriteOffset); ma_rb__deconstruct_offset(writeOffset, &writeOffsetInBytes, &writeOffsetLoopFlag); @@ -36901,13 +41930,13 @@ MA_API ma_result ma_pcm_rb_acquire_read(ma_pcm_rb* pRB, ma_uint32* pSizeInFrames return MA_SUCCESS; } -MA_API ma_result ma_pcm_rb_commit_read(ma_pcm_rb* pRB, ma_uint32 sizeInFrames, void* pBufferOut) +MA_API ma_result ma_pcm_rb_commit_read(ma_pcm_rb* pRB, ma_uint32 sizeInFrames) { if (pRB == NULL) { return MA_INVALID_ARGS; } - return ma_rb_commit_read(&pRB->rb, sizeInFrames * ma_pcm_rb_get_bpf(pRB), pBufferOut); + return ma_rb_commit_read(&pRB->rb, sizeInFrames * ma_pcm_rb_get_bpf(pRB)); } MA_API ma_result ma_pcm_rb_acquire_write(ma_pcm_rb* pRB, ma_uint32* pSizeInFrames, void** ppBufferOut) @@ -36930,13 +41959,13 @@ MA_API ma_result ma_pcm_rb_acquire_write(ma_pcm_rb* pRB, ma_uint32* pSizeInFrame return MA_SUCCESS; } -MA_API ma_result ma_pcm_rb_commit_write(ma_pcm_rb* pRB, ma_uint32 sizeInFrames, void* pBufferOut) +MA_API ma_result ma_pcm_rb_commit_write(ma_pcm_rb* pRB, ma_uint32 sizeInFrames) { if (pRB == NULL) { return MA_INVALID_ARGS; } - return ma_rb_commit_write(&pRB->rb, sizeInFrames * ma_pcm_rb_get_bpf(pRB), pBufferOut); + return ma_rb_commit_write(&pRB->rb, sizeInFrames * ma_pcm_rb_get_bpf(pRB)); } MA_API ma_result ma_pcm_rb_seek_read(ma_pcm_rb* pRB, ma_uint32 offsetInFrames) @@ -37137,19 +42166,33 @@ MA_API const char* ma_result_description(ma_result result) MA_API void* ma_malloc(size_t sz, const ma_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks != NULL) { - return ma__malloc_from_callbacks(sz, pAllocationCallbacks); + if (pAllocationCallbacks->onMalloc != NULL) { + return pAllocationCallbacks->onMalloc(sz, pAllocationCallbacks->pUserData); + } else { + return NULL; /* Do not fall back to the default implementation. */ + } } else { return ma__malloc_default(sz, NULL); } } +MA_API void* ma_calloc(size_t sz, const ma_allocation_callbacks* pAllocationCallbacks) +{ + void* p = ma_malloc(sz, pAllocationCallbacks); + if (p != NULL) { + MA_ZERO_MEMORY(p, sz); + } + + return p; +} + MA_API void* ma_realloc(void* p, size_t sz, const ma_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks != NULL) { if (pAllocationCallbacks->onRealloc != NULL) { return pAllocationCallbacks->onRealloc(p, sz, pAllocationCallbacks->pUserData); } else { - return NULL; /* This requires a native implementation of realloc(). */ + return NULL; /* Do not fall back to the default implementation. */ } } else { return ma__realloc_default(p, sz, NULL); @@ -37158,8 +42201,16 @@ MA_API void* ma_realloc(void* p, size_t sz, const ma_allocation_callbacks* pAllo MA_API void ma_free(void* p, const ma_allocation_callbacks* pAllocationCallbacks) { + if (p == NULL) { + return; + } + if (pAllocationCallbacks != NULL) { - ma__free_from_callbacks(p, pAllocationCallbacks); + if (pAllocationCallbacks->onFree != NULL) { + pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); + } else { + return; /* Do no fall back to the default implementation. */ + } } else { ma__free_default(p, NULL); } @@ -37264,11 +42315,6 @@ MA_API ma_result ma_data_source_init(const ma_data_source_config* pConfig, ma_da pDataSourceBase->pNext = NULL; pDataSourceBase->onGetNext = NULL; - /* Compatibility: Need to make a copy of the callbacks. This will be removed in version 0.11. */ - if (pConfig->vtable != NULL) { - pDataSourceBase->cb = *pConfig->vtable; - } - return MA_SUCCESS; } @@ -37284,7 +42330,6 @@ MA_API void ma_data_source_uninit(ma_data_source* pDataSource) */ } -#if defined(MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING) static ma_result ma_data_source_resolve_current(ma_data_source* pDataSource, ma_data_source** ppCurrentDataSource) { ma_data_source_base* pCurrentDataSource = (ma_data_source_base*)pDataSource; @@ -37311,63 +42356,76 @@ static ma_result ma_data_source_resolve_current(ma_data_source* pDataSource, ma_ return MA_SUCCESS; } -static ma_result ma_data_source_read_pcm_frames_within_range(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead, ma_bool32 loop) +static ma_result ma_data_source_read_pcm_frames_within_range(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; - + ma_result result; + ma_uint64 framesRead = 0; + ma_bool32 loop = ma_data_source_is_looping(pDataSource); + if (pDataSourceBase == NULL) { return MA_AT_END; } - if (pDataSourceBase->rangeEndInFrames == ~((ma_uint64)0) && (pDataSourceBase->loopEndInFrames == ~((ma_uint64)0) || loop == MA_FALSE)) { - /* No range is set - just read like normal. The data source itself will tell us when the end is reached. */ - return pDataSourceBase->cb.onRead(pDataSourceBase, pFramesOut, frameCount, pFramesRead); + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + + if ((pDataSourceBase->vtable->flags & MA_DATA_SOURCE_SELF_MANAGED_RANGE_AND_LOOP_POINT) != 0 || (pDataSourceBase->rangeEndInFrames == ~((ma_uint64)0) && (pDataSourceBase->loopEndInFrames == ~((ma_uint64)0) || loop == MA_FALSE))) { + /* Either the data source is self-managing the range, or no range is set - just read like normal. The data source itself will tell us when the end is reached. */ + result = pDataSourceBase->vtable->onRead(pDataSourceBase, pFramesOut, frameCount, &framesRead); } else { /* Need to clamp to within the range. */ - ma_result result; ma_uint64 cursor; - ma_uint64 framesRead = 0; - ma_uint64 rangeEnd; result = ma_data_source_get_cursor_in_pcm_frames(pDataSourceBase, &cursor); if (result != MA_SUCCESS) { /* Failed to retrieve the cursor. Cannot read within a range or loop points. Just read like normal - this may happen for things like noise data sources where it doesn't really matter. */ - return pDataSourceBase->cb.onRead(pDataSourceBase, pFramesOut, frameCount, pFramesRead); - } + result = pDataSourceBase->vtable->onRead(pDataSourceBase, pFramesOut, frameCount, &framesRead); + } else { + ma_uint64 rangeEnd; - /* We have the cursor. We need to make sure we don't read beyond our range. */ - rangeEnd = pDataSourceBase->rangeEndInFrames; + /* We have the cursor. We need to make sure we don't read beyond our range. */ + rangeEnd = pDataSourceBase->rangeEndInFrames; - /* If looping, make sure we're within range. */ - if (loop) { - if (pDataSourceBase->loopEndInFrames != ~((ma_uint64)0)) { - rangeEnd = ma_min(rangeEnd, pDataSourceBase->rangeBegInFrames + pDataSourceBase->loopEndInFrames); + /* If looping, make sure we're within range. */ + if (loop) { + if (pDataSourceBase->loopEndInFrames != ~((ma_uint64)0)) { + rangeEnd = ma_min(rangeEnd, pDataSourceBase->rangeBegInFrames + pDataSourceBase->loopEndInFrames); + } + } + + if (frameCount > (rangeEnd - cursor) && rangeEnd != ~((ma_uint64)0)) { + frameCount = (rangeEnd - cursor); + } + + /* + If the cursor is sitting on the end of the range the frame count will be set to 0 which can + result in MA_INVALID_ARGS. In this case, we don't want to try reading, but instead return + MA_AT_END so the higher level function can know about it. + */ + if (frameCount > 0) { + result = pDataSourceBase->vtable->onRead(pDataSourceBase, pFramesOut, frameCount, &framesRead); + } else { + result = MA_AT_END; /* The cursor is sitting on the end of the range which means we're at the end. */ } } - - if (frameCount > (rangeEnd - cursor) && rangeEnd != ~((ma_uint64)0)) { - frameCount = (rangeEnd - cursor); - } - - result = pDataSourceBase->cb.onRead(pDataSourceBase, pFramesOut, frameCount, &framesRead); - - if (pFramesRead != NULL) { - *pFramesRead = framesRead; - } - - /* We need to make sure MA_AT_END is returned if we hit the end of the range. */ - if (result != MA_AT_END && framesRead == 0) { - result = MA_AT_END; - } - - return result; } -} -#endif -MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead, ma_bool32 loop) + if (pFramesRead != NULL) { + *pFramesRead = framesRead; + } + + /* We need to make sure MA_AT_END is returned if we hit the end of the range. */ + if (result == MA_SUCCESS && framesRead == 0) { + result = MA_AT_END; + } + + return result; +} + +MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { -#if defined(MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING) ma_result result = MA_SUCCESS; ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; ma_data_source_base* pCurrentDataSource; @@ -37376,26 +42434,33 @@ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, voi ma_format format; ma_uint32 channels; ma_uint32 emptyLoopCounter = 0; /* Keeps track of how many times 0 frames have been read. For infinite loop detection of sounds with no audio data. */ + ma_bool32 loop; if (pFramesRead != NULL) { *pFramesRead = 0; } + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pDataSourceBase == NULL) { return MA_INVALID_ARGS; } + loop = ma_data_source_is_looping(pDataSource); + /* We need to know the data format so we can advance the output buffer as we read frames. If this fails, chaining will not work and we'll just read as much as we can from the current source. */ - if (ma_data_source_get_data_format(pDataSource, &format, &channels, NULL) != MA_SUCCESS) { + if (ma_data_source_get_data_format(pDataSource, &format, &channels, NULL, NULL, 0) != MA_SUCCESS) { result = ma_data_source_resolve_current(pDataSource, (ma_data_source**)&pCurrentDataSource); if (result != MA_SUCCESS) { return result; } - return ma_data_source_read_pcm_frames_within_range(pCurrentDataSource, pFramesOut, frameCount, pFramesRead, loop); + return ma_data_source_read_pcm_frames_within_range(pCurrentDataSource, pFramesOut, frameCount, pFramesRead); } /* @@ -37418,7 +42483,7 @@ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, voi break; } - result = ma_data_source_read_pcm_frames_within_range(pCurrentDataSource, pRunningFramesOut, framesRemaining, &framesProcessed, loop); + result = ma_data_source_read_pcm_frames_within_range(pCurrentDataSource, pRunningFramesOut, framesRemaining, &framesProcessed); totalFramesProcessed += framesProcessed; /* @@ -37430,8 +42495,8 @@ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, voi } /* - We can determine if we've reached the end by checking the return value of the onRead() - callback. To loop back to the start, all we need to do is seek back to the first frame. + We can determine if we've reached the end by checking if ma_data_source_read_pcm_frames_within_range() returned + MA_AT_END. To loop back to the start, all we need to do is seek back to the first frame. */ if (result == MA_AT_END) { /* @@ -37449,7 +42514,8 @@ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, voi emptyLoopCounter = 0; } - if (ma_data_source_seek_to_pcm_frame(pCurrentDataSource, pCurrentDataSource->loopBegInFrames) != MA_SUCCESS) { + result = ma_data_source_seek_to_pcm_frame(pCurrentDataSource, pCurrentDataSource->loopBegInFrames); + if (result != MA_SUCCESS) { break; /* Failed to loop. Abort. */ } @@ -37469,7 +42535,10 @@ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, voi } /* The next data source needs to be rewound to ensure data is read in looping scenarios. */ - ma_data_source_seek_to_pcm_frame(pDataSourceBase->pCurrent, 0); + result = ma_data_source_seek_to_pcm_frame(pDataSourceBase->pCurrent, 0); + if (result != MA_SUCCESS) { + break; + } /* We need to make sure we clear the MA_AT_END result so we don't accidentally return @@ -37489,93 +42558,27 @@ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, voi *pFramesRead = totalFramesProcessed; } + if (result == MA_SUCCESS && totalFramesProcessed == 0) { + result = MA_AT_END; + } + return result; -#else - ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource; - - /* Safety. */ - if (pFramesRead != NULL) { - *pFramesRead = 0; - } - - if (pCallbacks == NULL) { - return MA_INVALID_ARGS; - } - - if (pCallbacks->onRead == NULL) { - return MA_NOT_IMPLEMENTED; - } - - /* A very small optimization for the non looping case. */ - if (loop == MA_FALSE) { - return pCallbacks->onRead(pDataSource, pFramesOut, frameCount, pFramesRead); - } else { - ma_format format; - ma_uint32 channels; - ma_uint32 sampleRate; - if (ma_data_source_get_data_format(pDataSource, &format, &channels, &sampleRate) != MA_SUCCESS) { - return pCallbacks->onRead(pDataSource, pFramesOut, frameCount, pFramesRead); /* We don't have a way to retrieve the data format which means we don't know how to offset the output buffer. Just read as much as we can. */ - } else { - ma_result result = MA_SUCCESS; - ma_uint64 totalFramesProcessed; - void* pRunningFramesOut = pFramesOut; - - totalFramesProcessed = 0; - while (totalFramesProcessed < frameCount) { - ma_uint64 framesProcessed; - ma_uint64 framesRemaining = frameCount - totalFramesProcessed; - - result = pCallbacks->onRead(pDataSource, pRunningFramesOut, framesRemaining, &framesProcessed); - totalFramesProcessed += framesProcessed; - - /* - If we encounted an error from the read callback, make sure it's propagated to the caller. The caller may need to know whether or not MA_BUSY is returned which is - not necessarily considered an error. - */ - if (result != MA_SUCCESS && result != MA_AT_END) { - break; - } - - /* - We can determine if we've reached the end by checking the return value of the onRead() callback. If it's less than what we requested it means - we've reached the end. To loop back to the start, all we need to do is seek back to the first frame. - */ - if (framesProcessed < framesRemaining || result == MA_AT_END) { - if (ma_data_source_seek_to_pcm_frame(pDataSource, 0) != MA_SUCCESS) { - break; - } - } - - if (pRunningFramesOut != NULL) { - pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesProcessed * ma_get_bytes_per_frame(format, channels)); - } - } - - if (pFramesRead != NULL) { - *pFramesRead = totalFramesProcessed; - } - - return result; - } - } -#endif } -MA_API ma_result ma_data_source_seek_pcm_frames(ma_data_source* pDataSource, ma_uint64 frameCount, ma_uint64* pFramesSeeked, ma_bool32 loop) +MA_API ma_result ma_data_source_seek_pcm_frames(ma_data_source* pDataSource, ma_uint64 frameCount, ma_uint64* pFramesSeeked) { - return ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount, pFramesSeeked, loop); + return ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount, pFramesSeeked); } MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex) { -#if defined(MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING) ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; if (pDataSourceBase == NULL) { return MA_SUCCESS; } - if (pDataSourceBase->cb.onSeek == NULL) { + if (pDataSourceBase->vtable->onSeek == NULL) { return MA_NOT_IMPLEMENTED; } @@ -37583,78 +42586,40 @@ MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, m return MA_INVALID_OPERATION; /* Trying to seek to far forward. */ } - return pDataSourceBase->cb.onSeek(pDataSource, pDataSourceBase->rangeBegInFrames + frameIndex); -#else - ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource; - if (pCallbacks == NULL) { - return MA_INVALID_ARGS; - } - - if (pCallbacks->onSeek == NULL) { - return MA_NOT_IMPLEMENTED; - } - - return pCallbacks->onSeek(pDataSource, frameIndex); -#endif + return pDataSourceBase->vtable->onSeek(pDataSource, pDataSourceBase->rangeBegInFrames + frameIndex); } -MA_API ma_result ma_data_source_map(ma_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount) -{ - ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource; - if (pCallbacks == NULL) { - return MA_INVALID_ARGS; - } - - if (pCallbacks->onMap == NULL) { - return MA_NOT_IMPLEMENTED; - } - - return pCallbacks->onMap(pDataSource, ppFramesOut, pFrameCount); -} - -MA_API ma_result ma_data_source_unmap(ma_data_source* pDataSource, ma_uint64 frameCount) -{ - ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource; - if (pCallbacks == NULL) { - return MA_INVALID_ARGS; - } - - if (pCallbacks->onUnmap == NULL) { - return MA_NOT_IMPLEMENTED; - } - - return pCallbacks->onUnmap(pDataSource, frameCount); -} - -MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { + ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; ma_result result; ma_format format; ma_uint32 channels; ma_uint32 sampleRate; - ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource; + /* Initialize to defaults for safety just in case the data source does not implement this callback. */ if (pFormat != NULL) { *pFormat = ma_format_unknown; } - if (pChannels != NULL) { *pChannels = 0; } - if (pSampleRate != NULL) { *pSampleRate = 0; } + if (pChannelMap != NULL) { + MA_ZERO_MEMORY(pChannelMap, sizeof(*pChannelMap) * channelMapCap); + } - if (pCallbacks == NULL) { + if (pDataSourceBase == NULL) { return MA_INVALID_ARGS; } - if (pCallbacks->onGetDataFormat == NULL) { + if (pDataSourceBase->vtable->onGetDataFormat == NULL) { return MA_NOT_IMPLEMENTED; } - result = pCallbacks->onGetDataFormat(pDataSource, &format, &channels, &sampleRate); + result = pDataSourceBase->vtable->onGetDataFormat(pDataSource, &format, &channels, &sampleRate, pChannelMap, channelMapCap); if (result != MA_SUCCESS) { return result; } @@ -37669,12 +42634,13 @@ MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_ *pSampleRate = sampleRate; } + /* Channel map was passed in directly to the callback. This is safe due to the channelMapCap parameter. */ + return MA_SUCCESS; } MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor) { -#if defined(MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING) ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; ma_result result; ma_uint64 cursor; @@ -37689,11 +42655,11 @@ MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSo return MA_SUCCESS; } - if (pDataSourceBase->cb.onGetCursor == NULL) { + if (pDataSourceBase->vtable->onGetCursor == NULL) { return MA_NOT_IMPLEMENTED; } - result = pDataSourceBase->cb.onGetCursor(pDataSourceBase, &cursor); + result = pDataSourceBase->vtable->onGetCursor(pDataSourceBase, &cursor); if (result != MA_SUCCESS) { return result; } @@ -37704,32 +42670,12 @@ MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSo } else { *pCursor = cursor - pDataSourceBase->rangeBegInFrames; } - + return MA_SUCCESS; -#else - ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource; - - if (pCursor == NULL) { - return MA_INVALID_ARGS; - } - - *pCursor = 0; - - if (pCallbacks == NULL) { - return MA_INVALID_ARGS; - } - - if (pCallbacks->onGetCursor == NULL) { - return MA_NOT_IMPLEMENTED; - } - - return pCallbacks->onGetCursor(pDataSource, pCursor); -#endif } MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength) { -#if defined(MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING) ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; if (pLength == NULL) { @@ -37756,34 +42702,42 @@ MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSo Getting here means a range is not defined so we'll need to get the data source itself to tell us the length. */ - if (pDataSourceBase->cb.onGetLength == NULL) { + if (pDataSourceBase->vtable->onGetLength == NULL) { return MA_NOT_IMPLEMENTED; } - return pDataSourceBase->cb.onGetLength(pDataSource, pLength); -#else - ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource; - - if (pLength == NULL) { - return MA_INVALID_ARGS; - } - - *pLength = 0; - - if (pCallbacks == NULL) { - return MA_INVALID_ARGS; - } - - if (pCallbacks->onGetLength == NULL) { - return MA_NOT_IMPLEMENTED; - } - - return pCallbacks->onGetLength(pDataSource, pLength); -#endif + return pDataSourceBase->vtable->onGetLength(pDataSource, pLength); } +MA_API ma_result ma_data_source_set_looping(ma_data_source* pDataSource, ma_bool32 isLooping) +{ + ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; + + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + c89atomic_exchange_32(&pDataSourceBase->isLooping, isLooping); + + /* If there's no callback for this just treat it as a successful no-op. */ + if (pDataSourceBase->vtable->onSetLooping == NULL) { + return MA_SUCCESS; + } + + return pDataSourceBase->vtable->onSetLooping(pDataSource, isLooping); +} + +MA_API ma_bool32 ma_data_source_is_looping(ma_data_source* pDataSource) +{ + ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; + + if (pDataSource == NULL) { + return MA_FALSE; + } + + return c89atomic_load_32(&pDataSourceBase->isLooping); +} -#if defined(MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING) MA_API ma_result ma_data_source_set_range_in_pcm_frames(ma_data_source* pDataSource, ma_uint64 rangeBegInFrames, ma_uint64 rangeEndInFrames) { ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; @@ -37828,12 +42782,12 @@ MA_API ma_result ma_data_source_set_range_in_pcm_frames(ma_data_source* pDataSou } else { pDataSourceBase->loopEndInFrames = 0; } - + if (pDataSourceBase->loopEndInFrames > pDataSourceBase->rangeEndInFrames && pDataSourceBase->loopEndInFrames) { pDataSourceBase->loopEndInFrames = pDataSourceBase->rangeEndInFrames; } } - + /* If the new range is past the current cursor position we need to seek to it. */ result = ma_data_source_get_cursor_in_pcm_frames(pDataSource, &cursor); @@ -37983,7 +42937,6 @@ MA_API ma_data_source_get_next_proc ma_data_source_get_next_callback(ma_data_sou return pDataSourceBase->onGetNext; } -#endif static ma_result ma_audio_buffer_ref__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) @@ -38007,23 +42960,14 @@ static ma_result ma_audio_buffer_ref__data_source_on_seek(ma_data_source* pDataS return ma_audio_buffer_ref_seek_to_pcm_frame((ma_audio_buffer_ref*)pDataSource, frameIndex); } -static ma_result ma_audio_buffer_ref__data_source_on_map(ma_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount) -{ - return ma_audio_buffer_ref_map((ma_audio_buffer_ref*)pDataSource, ppFramesOut, pFrameCount); -} - -static ma_result ma_audio_buffer_ref__data_source_on_unmap(ma_data_source* pDataSource, ma_uint64 frameCount) -{ - return ma_audio_buffer_ref_unmap((ma_audio_buffer_ref*)pDataSource, frameCount); -} - -static ma_result ma_audio_buffer_ref__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_audio_buffer_ref__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { ma_audio_buffer_ref* pAudioBufferRef = (ma_audio_buffer_ref*)pDataSource; *pFormat = pAudioBufferRef->format; *pChannels = pAudioBufferRef->channels; *pSampleRate = 0; /* There is no notion of a sample rate with audio buffers. */ + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pAudioBufferRef->channels); return MA_SUCCESS; } @@ -38050,11 +42994,11 @@ static ma_data_source_vtable g_ma_audio_buffer_ref_data_source_vtable = { ma_audio_buffer_ref__data_source_on_read, ma_audio_buffer_ref__data_source_on_seek, - ma_audio_buffer_ref__data_source_on_map, - ma_audio_buffer_ref__data_source_on_unmap, ma_audio_buffer_ref__data_source_on_get_data_format, ma_audio_buffer_ref__data_source_on_get_cursor, - ma_audio_buffer_ref__data_source_on_get_length + ma_audio_buffer_ref__data_source_on_get_length, + NULL, /* onSetLooping */ + 0 }; MA_API ma_result ma_audio_buffer_ref_init(ma_format format, ma_uint32 channels, const void* pData, ma_uint64 sizeInFrames, ma_audio_buffer_ref* pAudioBufferRef) @@ -38331,7 +43275,7 @@ static ma_result ma_audio_buffer_init_ex(const ma_audio_buffer_config* pConfig, return MA_OUT_OF_MEMORY; /* Too big. */ } - pData = ma__malloc_from_callbacks((size_t)allocationSizeInBytes, &pAudioBuffer->allocationCallbacks); /* Safe cast to size_t. */ + pData = ma_malloc((size_t)allocationSizeInBytes, &pAudioBuffer->allocationCallbacks); /* Safe cast to size_t. */ if (pData == NULL) { return MA_OUT_OF_MEMORY; } @@ -38359,11 +43303,11 @@ static void ma_audio_buffer_uninit_ex(ma_audio_buffer* pAudioBuffer, ma_bool32 d } if (pAudioBuffer->ownsData && pAudioBuffer->ref.pData != &pAudioBuffer->_pExtraData[0]) { - ma__free_from_callbacks((void*)pAudioBuffer->ref.pData, &pAudioBuffer->allocationCallbacks); /* Naugty const cast, but OK in this case since we've guarded it with the ownsData check. */ + ma_free((void*)pAudioBuffer->ref.pData, &pAudioBuffer->allocationCallbacks); /* Naugty const cast, but OK in this case since we've guarded it with the ownsData check. */ } if (doFree) { - ma__free_from_callbacks(pAudioBuffer, &pAudioBuffer->allocationCallbacks); + ma_free(pAudioBuffer, &pAudioBuffer->allocationCallbacks); } ma_audio_buffer_ref_uninit(&pAudioBuffer->ref); @@ -38404,7 +43348,7 @@ MA_API ma_result ma_audio_buffer_alloc_and_init(const ma_audio_buffer_config* pC return MA_OUT_OF_MEMORY; /* Too big. */ } - pAudioBuffer = (ma_audio_buffer*)ma__malloc_from_callbacks((size_t)allocationSizeInBytes, &innerConfig.allocationCallbacks); /* Safe cast to size_t. */ + pAudioBuffer = (ma_audio_buffer*)ma_malloc((size_t)allocationSizeInBytes, &innerConfig.allocationCallbacks); /* Safe cast to size_t. */ if (pAudioBuffer == NULL) { return MA_OUT_OF_MEMORY; } @@ -38419,7 +43363,7 @@ MA_API ma_result ma_audio_buffer_alloc_and_init(const ma_audio_buffer_config* pC result = ma_audio_buffer_init_ex(&innerConfig, MA_FALSE, pAudioBuffer); if (result != MA_SUCCESS) { - ma__free_from_callbacks(pAudioBuffer, &innerConfig.allocationCallbacks); + ma_free(pAudioBuffer, &innerConfig.allocationCallbacks); return result; } @@ -38526,6 +43470,392 @@ MA_API ma_result ma_audio_buffer_get_available_frames(const ma_audio_buffer* pAu + + +MA_API ma_result ma_paged_audio_buffer_data_init(ma_format format, ma_uint32 channels, ma_paged_audio_buffer_data* pData) +{ + if (pData == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pData); + + pData->format = format; + pData->channels = channels; + pData->pTail = &pData->head; + + return MA_SUCCESS; +} + +MA_API void ma_paged_audio_buffer_data_uninit(ma_paged_audio_buffer_data* pData, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_paged_audio_buffer_page* pPage; + + if (pData == NULL) { + return; + } + + /* All pages need to be freed. */ + pPage = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pData->head.pNext); + while (pPage != NULL) { + ma_paged_audio_buffer_page* pNext = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pPage->pNext); + + ma_free(pPage, pAllocationCallbacks); + pPage = pNext; + } +} + +MA_API ma_paged_audio_buffer_page* ma_paged_audio_buffer_data_get_head(ma_paged_audio_buffer_data* pData) +{ + if (pData == NULL) { + return NULL; + } + + return &pData->head; +} + +MA_API ma_paged_audio_buffer_page* ma_paged_audio_buffer_data_get_tail(ma_paged_audio_buffer_data* pData) +{ + if (pData == NULL) { + return NULL; + } + + return pData->pTail; +} + +MA_API ma_result ma_paged_audio_buffer_data_get_length_in_pcm_frames(ma_paged_audio_buffer_data* pData, ma_uint64* pLength) +{ + ma_paged_audio_buffer_page* pPage; + + if (pLength == NULL) { + return MA_INVALID_ARGS; + } + + *pLength = 0; + + if (pData == NULL) { + return MA_INVALID_ARGS; + } + + /* Calculate the length from the linked list. */ + for (pPage = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pData->head.pNext); pPage != NULL; pPage = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pPage->pNext)) { + *pLength += pPage->sizeInFrames; + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_paged_audio_buffer_data_allocate_page(ma_paged_audio_buffer_data* pData, ma_uint64 pageSizeInFrames, const void* pInitialData, const ma_allocation_callbacks* pAllocationCallbacks, ma_paged_audio_buffer_page** ppPage) +{ + ma_paged_audio_buffer_page* pPage; + ma_uint64 allocationSize; + + if (ppPage == NULL) { + return MA_INVALID_ARGS; + } + + *ppPage = NULL; + + if (pData == NULL) { + return MA_INVALID_ARGS; + } + + allocationSize = sizeof(*pPage) + (pageSizeInFrames * ma_get_bytes_per_frame(pData->format, pData->channels)); + if (allocationSize > MA_SIZE_MAX) { + return MA_OUT_OF_MEMORY; /* Too big. */ + } + + pPage = (ma_paged_audio_buffer_page*)ma_malloc((size_t)allocationSize, pAllocationCallbacks); /* Safe cast to size_t. */ + if (pPage == NULL) { + return MA_OUT_OF_MEMORY; + } + + pPage->pNext = NULL; + pPage->sizeInFrames = pageSizeInFrames; + + if (pInitialData != NULL) { + ma_copy_pcm_frames(pPage->pAudioData, pInitialData, pageSizeInFrames, pData->format, pData->channels); + } + + *ppPage = pPage; + + return MA_SUCCESS; +} + +MA_API ma_result ma_paged_audio_buffer_data_free_page(ma_paged_audio_buffer_data* pData, ma_paged_audio_buffer_page* pPage, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pData == NULL || pPage == NULL) { + return MA_INVALID_ARGS; + } + + /* It's assumed the page is not attached to the list. */ + ma_free(pPage, pAllocationCallbacks); + + return MA_SUCCESS; +} + +MA_API ma_result ma_paged_audio_buffer_data_append_page(ma_paged_audio_buffer_data* pData, ma_paged_audio_buffer_page* pPage) +{ + if (pData == NULL || pPage == NULL) { + return MA_INVALID_ARGS; + } + + /* This function assumes the page has been filled with audio data by this point. As soon as we append, the page will be available for reading. */ + + /* First thing to do is update the tail. */ + for (;;) { + ma_paged_audio_buffer_page* pOldTail = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pData->pTail); + ma_paged_audio_buffer_page* pNewTail = pPage; + + if (c89atomic_compare_exchange_weak_ptr((volatile void**)&pData->pTail, (void**)&pOldTail, pNewTail)) { + /* Here is where we append the page to the list. After this, the page is attached to the list and ready to be read from. */ + c89atomic_exchange_ptr(&pOldTail->pNext, pPage); + break; /* Done. */ + } + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_paged_audio_buffer_data_allocate_and_append_page(ma_paged_audio_buffer_data* pData, ma_uint32 pageSizeInFrames, const void* pInitialData, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_result result; + ma_paged_audio_buffer_page* pPage; + + result = ma_paged_audio_buffer_data_allocate_page(pData, pageSizeInFrames, pInitialData, pAllocationCallbacks, &pPage); + if (result != MA_SUCCESS) { + return result; + } + + return ma_paged_audio_buffer_data_append_page(pData, pPage); /* <-- Should never fail. */ +} + + +MA_API ma_paged_audio_buffer_config ma_paged_audio_buffer_config_init(ma_paged_audio_buffer_data* pData) +{ + ma_paged_audio_buffer_config config; + + MA_ZERO_OBJECT(&config); + config.pData = pData; + + return config; +} + + +static ma_result ma_paged_audio_buffer__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + return ma_paged_audio_buffer_read_pcm_frames((ma_paged_audio_buffer*)pDataSource, pFramesOut, frameCount, pFramesRead); +} + +static ma_result ma_paged_audio_buffer__data_source_on_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) +{ + return ma_paged_audio_buffer_seek_to_pcm_frame((ma_paged_audio_buffer*)pDataSource, frameIndex); +} + +static ma_result ma_paged_audio_buffer__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + ma_paged_audio_buffer* pPagedAudioBuffer = (ma_paged_audio_buffer*)pDataSource; + + *pFormat = pPagedAudioBuffer->pData->format; + *pChannels = pPagedAudioBuffer->pData->channels; + *pSampleRate = 0; /* There is no notion of a sample rate with audio buffers. */ + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pPagedAudioBuffer->pData->channels); + + return MA_SUCCESS; +} + +static ma_result ma_paged_audio_buffer__data_source_on_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) +{ + return ma_paged_audio_buffer_get_cursor_in_pcm_frames((ma_paged_audio_buffer*)pDataSource, pCursor); +} + +static ma_result ma_paged_audio_buffer__data_source_on_get_length(ma_data_source* pDataSource, ma_uint64* pLength) +{ + return ma_paged_audio_buffer_get_length_in_pcm_frames((ma_paged_audio_buffer*)pDataSource, pLength); +} + +static ma_data_source_vtable g_ma_paged_audio_buffer_data_source_vtable = +{ + ma_paged_audio_buffer__data_source_on_read, + ma_paged_audio_buffer__data_source_on_seek, + ma_paged_audio_buffer__data_source_on_get_data_format, + ma_paged_audio_buffer__data_source_on_get_cursor, + ma_paged_audio_buffer__data_source_on_get_length, + NULL, /* onSetLooping */ + 0 +}; + +MA_API ma_result ma_paged_audio_buffer_init(const ma_paged_audio_buffer_config* pConfig, ma_paged_audio_buffer* pPagedAudioBuffer) +{ + ma_result result; + ma_data_source_config dataSourceConfig; + + if (pPagedAudioBuffer == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pPagedAudioBuffer); + + /* A config is required for the format and channel count. */ + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->pData == NULL) { + return MA_INVALID_ARGS; /* No underlying data specified. */ + } + + dataSourceConfig = ma_data_source_config_init(); + dataSourceConfig.vtable = &g_ma_paged_audio_buffer_data_source_vtable; + + result = ma_data_source_init(&dataSourceConfig, &pPagedAudioBuffer->ds); + if (result != MA_SUCCESS) { + return result; + } + + pPagedAudioBuffer->pData = pConfig->pData; + pPagedAudioBuffer->pCurrent = ma_paged_audio_buffer_data_get_head(pConfig->pData); + pPagedAudioBuffer->relativeCursor = 0; + pPagedAudioBuffer->absoluteCursor = 0; + + return MA_SUCCESS; +} + +MA_API void ma_paged_audio_buffer_uninit(ma_paged_audio_buffer* pPagedAudioBuffer) +{ + if (pPagedAudioBuffer == NULL) { + return; + } + + /* Nothing to do. The data needs to be deleted separately. */ +} + +MA_API ma_result ma_paged_audio_buffer_read_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + ma_result result = MA_SUCCESS; + ma_uint64 totalFramesRead = 0; + ma_format format; + ma_uint32 channels; + + if (pPagedAudioBuffer == NULL) { + return MA_INVALID_ARGS; + } + + format = pPagedAudioBuffer->pData->format; + channels = pPagedAudioBuffer->pData->channels; + + while (totalFramesRead < frameCount) { + /* Read from the current page. The buffer should never be in a state where this is NULL. */ + ma_uint64 framesRemainingInCurrentPage; + ma_uint64 framesRemainingToRead = frameCount - totalFramesRead; + ma_uint64 framesToReadThisIteration; + + MA_ASSERT(pPagedAudioBuffer->pCurrent != NULL); + + framesRemainingInCurrentPage = pPagedAudioBuffer->pCurrent->sizeInFrames - pPagedAudioBuffer->relativeCursor; + + framesToReadThisIteration = ma_min(framesRemainingInCurrentPage, framesRemainingToRead); + ma_copy_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, format, channels), ma_offset_pcm_frames_ptr(pPagedAudioBuffer->pCurrent->pAudioData, pPagedAudioBuffer->relativeCursor, format, channels), framesToReadThisIteration, format, channels); + totalFramesRead += framesToReadThisIteration; + + pPagedAudioBuffer->absoluteCursor += framesToReadThisIteration; + pPagedAudioBuffer->relativeCursor += framesToReadThisIteration; + + /* Move to the next page if necessary. If there's no more pages, we need to return MA_AT_END. */ + MA_ASSERT(pPagedAudioBuffer->relativeCursor <= pPagedAudioBuffer->pCurrent->sizeInFrames); + + if (pPagedAudioBuffer->relativeCursor == pPagedAudioBuffer->pCurrent->sizeInFrames) { + /* We reached the end of the page. Need to move to the next. If there's no more pages, we're done. */ + ma_paged_audio_buffer_page* pNext = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pPagedAudioBuffer->pCurrent->pNext); + if (pNext == NULL) { + result = MA_AT_END; + break; /* We've reached the end. */ + } else { + pPagedAudioBuffer->pCurrent = pNext; + pPagedAudioBuffer->relativeCursor = 0; + } + } + } + + if (pFramesRead != NULL) { + *pFramesRead = totalFramesRead; + } + + return result; +} + +MA_API ma_result ma_paged_audio_buffer_seek_to_pcm_frame(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64 frameIndex) +{ + if (pPagedAudioBuffer == NULL) { + return MA_INVALID_ARGS; + } + + if (frameIndex == pPagedAudioBuffer->absoluteCursor) { + return MA_SUCCESS; /* Nothing to do. */ + } + + if (frameIndex < pPagedAudioBuffer->absoluteCursor) { + /* Moving backwards. Need to move the cursor back to the start, and then move forward. */ + pPagedAudioBuffer->pCurrent = ma_paged_audio_buffer_data_get_head(pPagedAudioBuffer->pData); + pPagedAudioBuffer->absoluteCursor = 0; + pPagedAudioBuffer->relativeCursor = 0; + + /* Fall through to the forward seeking section below. */ + } + + if (frameIndex > pPagedAudioBuffer->absoluteCursor) { + /* Moving forward. */ + ma_paged_audio_buffer_page* pPage; + ma_uint64 runningCursor = 0; + + for (pPage = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&ma_paged_audio_buffer_data_get_head(pPagedAudioBuffer->pData)->pNext); pPage != NULL; pPage = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pPage->pNext)) { + ma_uint64 pageRangeBeg = runningCursor; + ma_uint64 pageRangeEnd = pageRangeBeg + pPage->sizeInFrames; + + if (frameIndex >= pageRangeBeg) { + if (frameIndex < pageRangeEnd || (frameIndex == pageRangeEnd && pPage == (ma_paged_audio_buffer_page*)c89atomic_load_ptr(ma_paged_audio_buffer_data_get_tail(pPagedAudioBuffer->pData)))) { /* A small edge case - allow seeking to the very end of the buffer. */ + /* We found the page. */ + pPagedAudioBuffer->pCurrent = pPage; + pPagedAudioBuffer->absoluteCursor = frameIndex; + pPagedAudioBuffer->relativeCursor = frameIndex - pageRangeBeg; + return MA_SUCCESS; + } + } + + runningCursor = pageRangeEnd; + } + + /* Getting here means we tried seeking too far forward. Don't change any state. */ + return MA_BAD_SEEK; + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_paged_audio_buffer_get_cursor_in_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64* pCursor) +{ + if (pCursor == NULL) { + return MA_INVALID_ARGS; + } + + *pCursor = 0; /* Safety. */ + + if (pPagedAudioBuffer == NULL) { + return MA_INVALID_ARGS; + } + + *pCursor = pPagedAudioBuffer->absoluteCursor; + + return MA_SUCCESS; +} + +MA_API ma_result ma_paged_audio_buffer_get_length_in_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64* pLength) +{ + return ma_paged_audio_buffer_data_get_length_in_pcm_frames(pPagedAudioBuffer->pData, pLength); +} + + + /************************************************************************************************************************************************************** VFS @@ -38591,6 +43921,8 @@ MA_API ma_result ma_vfs_close(ma_vfs* pVFS, ma_vfs_file file) MA_API ma_result ma_vfs_read(ma_vfs* pVFS, ma_vfs_file file, void* pDst, size_t sizeInBytes, size_t* pBytesRead) { ma_vfs_callbacks* pCallbacks = (ma_vfs_callbacks*)pVFS; + ma_result result; + size_t bytesRead; if (pBytesRead != NULL) { *pBytesRead = 0; @@ -38604,7 +43936,17 @@ MA_API ma_result ma_vfs_read(ma_vfs* pVFS, ma_vfs_file file, void* pDst, size_t return MA_NOT_IMPLEMENTED; } - return pCallbacks->onRead(pVFS, file, pDst, sizeInBytes, pBytesRead); + result = pCallbacks->onRead(pVFS, file, pDst, sizeInBytes, &bytesRead); + + if (pBytesRead != NULL) { + *pBytesRead = bytesRead; + } + + if (result == MA_SUCCESS && bytesRead == 0 && sizeInBytes > 0) { + result = MA_AT_END; + } + + return result; } MA_API ma_result ma_vfs_write(ma_vfs* pVFS, ma_vfs_file file, const void* pSrc, size_t sizeInBytes, size_t* pBytesWritten) @@ -38684,7 +44026,7 @@ MA_API ma_result ma_vfs_info(ma_vfs* pVFS, ma_vfs_file file, ma_file_info* pInfo } -static ma_result ma_vfs_open_and_read_file_ex(ma_vfs* pVFS, const char* pFilePath, const wchar_t* pFilePathW, void** ppData, size_t* pSize, const ma_allocation_callbacks* pAllocationCallbacks, ma_uint32 allocationType) +static ma_result ma_vfs_open_and_read_file_ex(ma_vfs* pVFS, const char* pFilePath, const wchar_t* pFilePathW, void** ppData, size_t* pSize, const ma_allocation_callbacks* pAllocationCallbacks) { ma_result result; ma_vfs_file file; @@ -38692,8 +44034,6 @@ static ma_result ma_vfs_open_and_read_file_ex(ma_vfs* pVFS, const char* pFilePat void* pData; size_t bytesRead; - (void)allocationType; - if (ppData != NULL) { *ppData = NULL; } @@ -38725,7 +44065,7 @@ static ma_result ma_vfs_open_and_read_file_ex(ma_vfs* pVFS, const char* pFilePat return MA_TOO_BIG; } - pData = ma__malloc_from_callbacks((size_t)info.sizeInBytes, pAllocationCallbacks); /* Safe cast. */ + pData = ma_malloc((size_t)info.sizeInBytes, pAllocationCallbacks); /* Safe cast. */ if (pData == NULL) { ma_vfs_close(pVFS, file); return result; @@ -38735,7 +44075,7 @@ static ma_result ma_vfs_open_and_read_file_ex(ma_vfs* pVFS, const char* pFilePat ma_vfs_close(pVFS, file); if (result != MA_SUCCESS) { - ma__free_from_callbacks(pData, pAllocationCallbacks); + ma_free(pData, pAllocationCallbacks); return result; } @@ -38751,12 +44091,12 @@ static ma_result ma_vfs_open_and_read_file_ex(ma_vfs* pVFS, const char* pFilePat MA_API ma_result ma_vfs_open_and_read_file(ma_vfs* pVFS, const char* pFilePath, void** ppData, size_t* pSize, const ma_allocation_callbacks* pAllocationCallbacks) { - return ma_vfs_open_and_read_file_ex(pVFS, pFilePath, NULL, ppData, pSize, pAllocationCallbacks, 0 /*MA_ALLOCATION_TYPE_GENERAL*/); + return ma_vfs_open_and_read_file_ex(pVFS, pFilePath, NULL, ppData, pSize, pAllocationCallbacks); } MA_API ma_result ma_vfs_open_and_read_file_w(ma_vfs* pVFS, const wchar_t* pFilePath, void** ppData, size_t* pSize, const ma_allocation_callbacks* pAllocationCallbacks) { - return ma_vfs_open_and_read_file_ex(pVFS, NULL, pFilePath, ppData, pSize, pAllocationCallbacks, 0 /*MA_ALLOCATION_TYPE_GENERAL*/); + return ma_vfs_open_and_read_file_ex(pVFS, NULL, pFilePath, ppData, pSize, pAllocationCallbacks); } @@ -39999,7 +45339,7 @@ extern "C" { #define DRFLAC_XSTRINGIFY(x) DRFLAC_STRINGIFY(x) #define DRFLAC_VERSION_MAJOR 0 #define DRFLAC_VERSION_MINOR 12 -#define DRFLAC_VERSION_REVISION 31 +#define DRFLAC_VERSION_REVISION 32 #define DRFLAC_VERSION_STRING DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION) #include typedef signed char drflac_int8; @@ -40008,7 +45348,7 @@ typedef signed short drflac_int16; typedef unsigned short drflac_uint16; typedef signed int drflac_int32; typedef unsigned int drflac_uint32; -#if defined(_MSC_VER) +#if defined(_MSC_VER) && !defined(__clang__) typedef signed __int64 drflac_int64; typedef unsigned __int64 drflac_uint64; #else @@ -40360,7 +45700,7 @@ extern "C" { #define DRMP3_XSTRINGIFY(x) DRMP3_STRINGIFY(x) #define DRMP3_VERSION_MAJOR 0 #define DRMP3_VERSION_MINOR 6 -#define DRMP3_VERSION_REVISION 31 +#define DRMP3_VERSION_REVISION 32 #define DRMP3_VERSION_STRING DRMP3_XSTRINGIFY(DRMP3_VERSION_MAJOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_MINOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_REVISION) #include typedef signed char drmp3_int8; @@ -40369,7 +45709,7 @@ typedef signed short drmp3_int16; typedef unsigned short drmp3_uint16; typedef signed int drmp3_int32; typedef unsigned int drmp3_uint32; -#if defined(_MSC_VER) +#if defined(_MSC_VER) && !defined(__clang__) typedef signed __int64 drmp3_int64; typedef unsigned __int64 drmp3_uint64; #else @@ -40607,43 +45947,22 @@ Decoding static ma_result ma_decoder_read_bytes(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead, size_t* pBytesRead) { - size_t bytesRead; + MA_ASSERT(pDecoder != NULL); - MA_ASSERT(pDecoder != NULL); - MA_ASSERT(pBufferOut != NULL); - MA_ASSERT(bytesToRead > 0); /* It's an error to call this with a byte count of zero. */ - - bytesRead = pDecoder->onRead(pDecoder, pBufferOut, bytesToRead); - - if (pBytesRead != NULL) { - *pBytesRead = bytesRead; - } - - if (bytesRead == 0) { - return MA_AT_END; - } - - return MA_SUCCESS; + return pDecoder->onRead(pDecoder, pBufferOut, bytesToRead, pBytesRead); } static ma_result ma_decoder_seek_bytes(ma_decoder* pDecoder, ma_int64 byteOffset, ma_seek_origin origin) { - ma_bool32 wasSuccessful; - MA_ASSERT(pDecoder != NULL); - wasSuccessful = pDecoder->onSeek(pDecoder, byteOffset, origin); - if (wasSuccessful) { - return MA_SUCCESS; - } else { - return MA_ERROR; - } + return pDecoder->onSeek(pDecoder, byteOffset, origin); } static ma_result ma_decoder_tell_bytes(ma_decoder* pDecoder, ma_int64* pCursor) { MA_ASSERT(pDecoder != NULL); - + if (pDecoder->onTell == NULL) { return MA_NOT_IMPLEMENTED; } @@ -40652,12 +45971,13 @@ static ma_result ma_decoder_tell_bytes(ma_decoder* pDecoder, ma_int64* pCursor) } -MA_API ma_decoding_backend_config ma_decoding_backend_config_init(ma_format preferredFormat) +MA_API ma_decoding_backend_config ma_decoding_backend_config_init(ma_format preferredFormat, ma_uint32 seekPointCount) { ma_decoding_backend_config config; MA_ZERO_OBJECT(&config); config.preferredFormat = preferredFormat; + config.seekPointCount = seekPointCount; return config; } @@ -40667,12 +45987,10 @@ MA_API ma_decoder_config ma_decoder_config_init(ma_format outputFormat, ma_uint3 { ma_decoder_config config; MA_ZERO_OBJECT(&config); - config.format = outputFormat; - config.channels = ma_min(outputChannels, ma_countof(config.channelMap)); - config.sampleRate = outputSampleRate; - config.resampling.algorithm = ma_resample_algorithm_linear; - config.resampling.linear.lpfOrder = ma_min(MA_DEFAULT_RESAMPLER_LPF_ORDER, MA_MAX_FILTER_ORDER); - config.resampling.speex.quality = 3; + config.format = outputFormat; + config.channels = outputChannels; + config.sampleRate = outputSampleRate; + config.resampling = ma_resampler_config_init(ma_format_unknown, 0, 0, 0, ma_resample_algorithm_linear); /* Format/channels/rate doesn't matter here. */ config.encodingFormat = ma_encoding_format_unknown; /* Note that we are intentionally leaving the channel map empty here which will cause the default channel map to be used. */ @@ -40709,19 +46027,11 @@ static ma_result ma_decoder__init_data_converter(ma_decoder* pDecoder, const ma_ MA_ASSERT(pDecoder != NULL); MA_ASSERT(pConfig != NULL); - result = ma_data_source_get_data_format(pDecoder->pBackend, &internalFormat, &internalChannels, &internalSampleRate); + result = ma_data_source_get_data_format(pDecoder->pBackend, &internalFormat, &internalChannels, &internalSampleRate, internalChannelMap, ma_countof(internalChannelMap)); if (result != MA_SUCCESS) { return result; /* Failed to retrieve the internal data format. */ } - /* Channel map needs to be retrieved separately. */ - if (pDecoder->pBackendVTable != NULL && pDecoder->pBackendVTable->onGetChannelMap != NULL) { - pDecoder->pBackendVTable->onGetChannelMap(pDecoder->pBackendUserData, pDecoder->pBackend, internalChannelMap, ma_countof(internalChannelMap)); - } else { - ma_get_standard_channel_map(ma_standard_channel_map_default, ma_min(internalChannels, ma_countof(internalChannelMap)), internalChannelMap); - } - - /* Make sure we're not asking for too many channels. */ if (pConfig->channels > MA_MAX_CHANNELS) { @@ -40753,28 +46063,57 @@ static ma_result ma_decoder__init_data_converter(ma_decoder* pDecoder, const ma_ pDecoder->outputSampleRate = pConfig->sampleRate; } - if (ma_channel_map_blank(pDecoder->outputChannels, pConfig->channelMap)) { - ma_get_standard_channel_map(ma_standard_channel_map_default, pDecoder->outputChannels, pDecoder->outputChannelMap); - } else { - MA_COPY_MEMORY(pDecoder->outputChannelMap, pConfig->channelMap, sizeof(pConfig->channelMap)); - } - - converterConfig = ma_data_converter_config_init( internalFormat, pDecoder->outputFormat, internalChannels, pDecoder->outputChannels, internalSampleRate, pDecoder->outputSampleRate ); - ma_channel_map_copy(converterConfig.channelMapIn, internalChannelMap, internalChannels); - ma_channel_map_copy(converterConfig.channelMapOut, pDecoder->outputChannelMap, pDecoder->outputChannels); - converterConfig.channelMixMode = pConfig->channelMixMode; - converterConfig.ditherMode = pConfig->ditherMode; - converterConfig.resampling.allowDynamicSampleRate = MA_FALSE; /* Never allow dynamic sample rate conversion. Setting this to true will disable passthrough optimizations. */ - converterConfig.resampling.algorithm = pConfig->resampling.algorithm; - converterConfig.resampling.linear.lpfOrder = pConfig->resampling.linear.lpfOrder; - converterConfig.resampling.speex.quality = pConfig->resampling.speex.quality; + converterConfig.pChannelMapIn = internalChannelMap; + converterConfig.pChannelMapOut = pConfig->pChannelMap; + converterConfig.channelMixMode = pConfig->channelMixMode; + converterConfig.ditherMode = pConfig->ditherMode; + converterConfig.allowDynamicSampleRate = MA_FALSE; /* Never allow dynamic sample rate conversion. Setting this to true will disable passthrough optimizations. */ + converterConfig.resampling = pConfig->resampling; - return ma_data_converter_init(&converterConfig, &pDecoder->converter); + result = ma_data_converter_init(&converterConfig, &pDecoder->allocationCallbacks, &pDecoder->converter); + if (result != MA_SUCCESS) { + return result; + } + + /* + Now that we have the decoder we need to determine whether or not we need a heap-allocated cache. We'll + need this if the data converter does not support calculation of the required input frame count. To + determine support for this we'll just run a test. + */ + { + ma_uint64 unused; + + result = ma_data_converter_get_required_input_frame_count(&pDecoder->converter, 1, &unused); + if (result != MA_SUCCESS) { + /* + We were unable to calculate the required input frame count which means we'll need to use + a heap-allocated cache. + */ + ma_uint64 inputCacheCapSizeInBytes; + + pDecoder->inputCacheCap = MA_DATA_CONVERTER_STACK_BUFFER_SIZE / ma_get_bytes_per_frame(internalFormat, internalChannels); + + /* Not strictly necessary, but keeping here for safety in case we change the default value of pDecoder->inputCacheCap. */ + inputCacheCapSizeInBytes = pDecoder->inputCacheCap * ma_get_bytes_per_frame(internalFormat, internalChannels); + if (inputCacheCapSizeInBytes > MA_SIZE_MAX) { + ma_data_converter_uninit(&pDecoder->converter, &pDecoder->allocationCallbacks); + return MA_OUT_OF_MEMORY; + } + + pDecoder->pInputCache = ma_malloc((size_t)inputCacheCapSizeInBytes, &pDecoder->allocationCallbacks); /* Safe cast to size_t. */ + if (pDecoder->pInputCache == NULL) { + ma_data_converter_uninit(&pDecoder->converter, &pDecoder->allocationCallbacks); + return MA_OUT_OF_MEMORY; + } + } + } + + return MA_SUCCESS; } @@ -40818,7 +46157,7 @@ static ma_result ma_decoder_init_from_vtable(const ma_decoding_backend_vtable* p return MA_NOT_IMPLEMENTED; } - backendConfig = ma_decoding_backend_config_init(pConfig->format); + backendConfig = ma_decoding_backend_config_init(pConfig->format, pConfig->seekPointCount); result = pVTable->onInit(pVTableUserData, ma_decoder_internal_on_read__custom, ma_decoder_internal_on_seek__custom, ma_decoder_internal_on_tell__custom, pDecoder, &backendConfig, &pDecoder->allocationCallbacks, &pBackend); if (result != MA_SUCCESS) { @@ -40910,9 +46249,9 @@ static ma_result ma_wav_ds_seek(ma_data_source* pDataSource, ma_uint64 frameInde return ma_wav_seek_to_pcm_frame((ma_wav*)pDataSource, frameIndex); } -static ma_result ma_wav_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_wav_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { - return ma_wav_get_data_format((ma_wav*)pDataSource, pFormat, pChannels, pSampleRate, NULL, 0); + return ma_wav_get_data_format((ma_wav*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); } static ma_result ma_wav_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) @@ -40929,11 +46268,11 @@ static ma_data_source_vtable g_ma_wav_ds_vtable = { ma_wav_ds_read, ma_wav_ds_seek, - NULL, /* onMap() */ - NULL, /* onUnmap() */ ma_wav_ds_get_data_format, ma_wav_ds_get_cursor, - ma_wav_ds_get_length + ma_wav_ds_get_length, + NULL, /* onSetLooping */ + 0 }; @@ -41179,6 +46518,14 @@ MA_API void ma_wav_uninit(ma_wav* pWav, const ma_allocation_callbacks* pAllocati MA_API ma_result ma_wav_read_pcm_frames(ma_wav* pWav, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pWav == NULL) { return MA_INVALID_ARGS; } @@ -41226,6 +46573,10 @@ MA_API ma_result ma_wav_read_pcm_frames(ma_wav* pWav, void* pFramesOut, ma_uint6 *pFramesRead = totalFramesRead; } + if (result == MA_SUCCESS && totalFramesRead == 0) { + result = MA_AT_END; + } + return result; } #else @@ -41251,7 +46602,7 @@ MA_API ma_result ma_wav_seek_to_pcm_frame(ma_wav* pWav, ma_uint64 frameIndex) #if !defined(MA_NO_WAV) { drwav_bool32 wavResult; - + wavResult = drwav_seek_to_pcm_frame(&pWav->dr, frameIndex); if (wavResult != DRWAV_TRUE) { return MA_ERROR; @@ -41306,7 +46657,7 @@ MA_API ma_result ma_wav_get_data_format(ma_wav* pWav, ma_format* pFormat, ma_uin } if (pChannelMap != NULL) { - ma_get_standard_channel_map(ma_standard_channel_map_microsoft, (ma_uint32)ma_min(pWav->dr.channels, channelMapCap), pChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_microsoft, pChannelMap, channelMapCap, pWav->dr.channels); } return MA_SUCCESS; @@ -41487,23 +46838,13 @@ static void ma_decoding_backend_uninit__wav(void* pUserData, ma_data_source* pBa ma_free(pWav, pAllocationCallbacks); } -static ma_result ma_decoding_backend_get_channel_map__wav(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap) -{ - ma_wav* pWav = (ma_wav*)pBackend; - - (void)pUserData; - - return ma_wav_get_data_format(pWav, NULL, NULL, NULL, pChannelMap, channelMapCap); -} - static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_wav = { ma_decoding_backend_init__wav, ma_decoding_backend_init_file__wav, ma_decoding_backend_init_file_w__wav, ma_decoding_backend_init_memory__wav, - ma_decoding_backend_uninit__wav, - ma_decoding_backend_get_channel_map__wav + ma_decoding_backend_uninit__wav }; static ma_result ma_decoder_init_wav__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) @@ -41551,9 +46892,9 @@ static ma_result ma_flac_ds_seek(ma_data_source* pDataSource, ma_uint64 frameInd return ma_flac_seek_to_pcm_frame((ma_flac*)pDataSource, frameIndex); } -static ma_result ma_flac_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_flac_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { - return ma_flac_get_data_format((ma_flac*)pDataSource, pFormat, pChannels, pSampleRate, NULL, 0); + return ma_flac_get_data_format((ma_flac*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); } static ma_result ma_flac_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) @@ -41570,11 +46911,11 @@ static ma_data_source_vtable g_ma_flac_ds_vtable = { ma_flac_ds_read, ma_flac_ds_seek, - NULL, /* onMap() */ - NULL, /* onUnmap() */ ma_flac_ds_get_data_format, ma_flac_ds_get_cursor, - ma_flac_ds_get_length + ma_flac_ds_get_length, + NULL, /* onSetLooping */ + 0 }; @@ -41816,6 +47157,14 @@ MA_API void ma_flac_uninit(ma_flac* pFlac, const ma_allocation_callbacks* pAlloc MA_API ma_result ma_flac_read_pcm_frames(ma_flac* pFlac, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pFlac == NULL) { return MA_INVALID_ARGS; } @@ -41864,6 +47213,10 @@ MA_API ma_result ma_flac_read_pcm_frames(ma_flac* pFlac, void* pFramesOut, ma_ui *pFramesRead = totalFramesRead; } + if (result == MA_SUCCESS && totalFramesRead == 0) { + result = MA_AT_END; + } + return result; } #else @@ -41889,7 +47242,7 @@ MA_API ma_result ma_flac_seek_to_pcm_frame(ma_flac* pFlac, ma_uint64 frameIndex) #if !defined(MA_NO_FLAC) { drflac_bool32 flacResult; - + flacResult = drflac_seek_to_pcm_frame(pFlac->dr, frameIndex); if (flacResult != DRFLAC_TRUE) { return MA_ERROR; @@ -41944,7 +47297,7 @@ MA_API ma_result ma_flac_get_data_format(ma_flac* pFlac, ma_format* pFormat, ma_ } if (pChannelMap != NULL) { - ma_get_standard_channel_map(ma_standard_channel_map_microsoft, (ma_uint32)ma_min(pFlac->dr->channels, channelMapCap), pChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_microsoft, pChannelMap, channelMapCap, pFlac->dr->channels); } return MA_SUCCESS; @@ -42119,23 +47472,13 @@ static void ma_decoding_backend_uninit__flac(void* pUserData, ma_data_source* pB ma_free(pFlac, pAllocationCallbacks); } -static ma_result ma_decoding_backend_get_channel_map__flac(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap) -{ - ma_flac* pFlac = (ma_flac*)pBackend; - - (void)pUserData; - - return ma_flac_get_data_format(pFlac, NULL, NULL, NULL, pChannelMap, channelMapCap); -} - static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_flac = { ma_decoding_backend_init__flac, ma_decoding_backend_init_file__flac, ma_decoding_backend_init_file_w__flac, ma_decoding_backend_init_memory__flac, - ma_decoding_backend_uninit__flac, - ma_decoding_backend_get_channel_map__flac + ma_decoding_backend_uninit__flac }; static ma_result ma_decoder_init_flac__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) @@ -42158,6 +47501,8 @@ typedef struct ma_format format; /* Can be f32 or s16. */ #if !defined(MA_NO_MP3) drmp3 dr; + drmp3_uint32 seekPointCount; + drmp3_seek_point* pSeekPoints; /* Only used if seek table generation is used. */ #endif } ma_mp3; @@ -42183,9 +47528,9 @@ static ma_result ma_mp3_ds_seek(ma_data_source* pDataSource, ma_uint64 frameInde return ma_mp3_seek_to_pcm_frame((ma_mp3*)pDataSource, frameIndex); } -static ma_result ma_mp3_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_mp3_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { - return ma_mp3_get_data_format((ma_mp3*)pDataSource, pFormat, pChannels, pSampleRate, NULL, 0); + return ma_mp3_get_data_format((ma_mp3*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); } static ma_result ma_mp3_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) @@ -42202,11 +47547,11 @@ static ma_data_source_vtable g_ma_mp3_ds_vtable = { ma_mp3_ds_read, ma_mp3_ds_seek, - NULL, /* onMap() */ - NULL, /* onUnmap() */ ma_mp3_ds_get_data_format, ma_mp3_ds_get_cursor, - ma_mp3_ds_get_length + ma_mp3_ds_get_length, + NULL, /* onSetLooping */ + 0 }; @@ -42295,6 +47640,40 @@ static ma_result ma_mp3_init_internal(const ma_decoding_backend_config* pConfig, return MA_SUCCESS; } +static ma_result ma_mp3_generate_seek_table(ma_mp3* pMP3, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks) +{ + drmp3_bool32 mp3Result; + drmp3_uint32 seekPointCount = 0; + drmp3_seek_point* pSeekPoints = NULL; + + MA_ASSERT(pMP3 != NULL); + MA_ASSERT(pConfig != NULL); + + seekPointCount = pConfig->seekPointCount; + if (seekPointCount > 0) { + pSeekPoints = (drmp3_seek_point*)ma_malloc(sizeof(*pMP3->pSeekPoints) * seekPointCount, pAllocationCallbacks); + if (pSeekPoints == NULL) { + return MA_OUT_OF_MEMORY; + } + } + + mp3Result = drmp3_calculate_seek_points(&pMP3->dr, &seekPointCount, pSeekPoints); + if (mp3Result != MA_TRUE) { + return MA_ERROR; + } + + mp3Result = drmp3_bind_seek_table(&pMP3->dr, seekPointCount, pSeekPoints); + if (mp3Result != MA_TRUE) { + ma_free(pSeekPoints, pAllocationCallbacks); + return MA_ERROR; + } + + pMP3->seekPointCount = seekPointCount; + pMP3->pSeekPoints = pSeekPoints; + + return MA_SUCCESS; +} + MA_API ma_result ma_mp3_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_mp3* pMP3) { ma_result result; @@ -42323,6 +47702,8 @@ MA_API ma_result ma_mp3_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_p return MA_INVALID_FILE; } + ma_mp3_generate_seek_table(pMP3, pConfig, pAllocationCallbacks); + return MA_SUCCESS; } #else @@ -42353,6 +47734,8 @@ MA_API ma_result ma_mp3_init_file(const char* pFilePath, const ma_decoding_backe return MA_INVALID_FILE; } + ma_mp3_generate_seek_table(pMP3, pConfig, pAllocationCallbacks); + return MA_SUCCESS; } #else @@ -42384,6 +47767,8 @@ MA_API ma_result ma_mp3_init_file_w(const wchar_t* pFilePath, const ma_decoding_ return MA_INVALID_FILE; } + ma_mp3_generate_seek_table(pMP3, pConfig, pAllocationCallbacks); + return MA_SUCCESS; } #else @@ -42415,6 +47800,8 @@ MA_API ma_result ma_mp3_init_memory(const void* pData, size_t dataSize, const ma return MA_INVALID_FILE; } + ma_mp3_generate_seek_table(pMP3, pConfig, pAllocationCallbacks); + return MA_SUCCESS; } #else @@ -42434,8 +47821,6 @@ MA_API void ma_mp3_uninit(ma_mp3* pMP3, const ma_allocation_callbacks* pAllocati return; } - (void)pAllocationCallbacks; - #if !defined(MA_NO_MP3) { drmp3_uninit(&pMP3->dr); @@ -42447,11 +47832,22 @@ MA_API void ma_mp3_uninit(ma_mp3* pMP3, const ma_allocation_callbacks* pAllocati } #endif + /* Seek points need to be freed after the MP3 decoder has been uninitialized to ensure they're no longer being referenced. */ + ma_free(pMP3->pSeekPoints, pAllocationCallbacks); + ma_data_source_uninit(&pMP3->ds); } MA_API ma_result ma_mp3_read_pcm_frames(ma_mp3* pMP3, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pMP3 == NULL) { return MA_INVALID_ARGS; } @@ -42521,7 +47917,7 @@ MA_API ma_result ma_mp3_seek_to_pcm_frame(ma_mp3* pMP3, ma_uint64 frameIndex) #if !defined(MA_NO_MP3) { drmp3_bool32 mp3Result; - + mp3Result = drmp3_seek_to_pcm_frame(&pMP3->dr, frameIndex); if (mp3Result != DRMP3_TRUE) { return MA_ERROR; @@ -42576,7 +47972,7 @@ MA_API ma_result ma_mp3_get_data_format(ma_mp3* pMP3, ma_format* pFormat, ma_uin } if (pChannelMap != NULL) { - ma_get_standard_channel_map(ma_standard_channel_map_default, (ma_uint32)ma_min(pMP3->dr.channels, channelMapCap), pChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pMP3->dr.channels); } return MA_SUCCESS; @@ -42751,23 +48147,13 @@ static void ma_decoding_backend_uninit__mp3(void* pUserData, ma_data_source* pBa ma_free(pMP3, pAllocationCallbacks); } -static ma_result ma_decoding_backend_get_channel_map__mp3(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap) -{ - ma_mp3* pMP3 = (ma_mp3*)pBackend; - - (void)pUserData; - - return ma_mp3_get_data_format(pMP3, NULL, NULL, NULL, pChannelMap, channelMapCap); -} - static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_mp3 = { ma_decoding_backend_init__mp3, ma_decoding_backend_init_file__mp3, ma_decoding_backend_init_file_w__mp3, ma_decoding_backend_init_memory__mp3, - ma_decoding_backend_uninit__mp3, - ma_decoding_backend_get_channel_map__mp3 + ma_decoding_backend_uninit__mp3 }; static ma_result ma_decoder_init_mp3__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) @@ -42831,9 +48217,9 @@ static ma_result ma_stbvorbis_ds_seek(ma_data_source* pDataSource, ma_uint64 fra return ma_stbvorbis_seek_to_pcm_frame((ma_stbvorbis*)pDataSource, frameIndex); } -static ma_result ma_stbvorbis_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_stbvorbis_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { - return ma_stbvorbis_get_data_format((ma_stbvorbis*)pDataSource, pFormat, pChannels, pSampleRate, NULL, 0); + return ma_stbvorbis_get_data_format((ma_stbvorbis*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); } static ma_result ma_stbvorbis_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) @@ -42850,11 +48236,11 @@ static ma_data_source_vtable g_ma_stbvorbis_ds_vtable = { ma_stbvorbis_ds_read, ma_stbvorbis_ds_seek, - NULL, /* onMap() */ - NULL, /* onUnmap() */ ma_stbvorbis_ds_get_data_format, ma_stbvorbis_ds_get_cursor, - ma_stbvorbis_ds_get_length + ma_stbvorbis_ds_get_length, + NULL, /* onSetLooping */ + 0 }; @@ -43117,6 +48503,14 @@ MA_API void ma_stbvorbis_uninit(ma_stbvorbis* pVorbis, const ma_allocation_callb MA_API ma_result ma_stbvorbis_read_pcm_frames(ma_stbvorbis* pVorbis, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pVorbis == NULL) { return MA_INVALID_ARGS; } @@ -43225,7 +48619,7 @@ MA_API ma_result ma_stbvorbis_read_pcm_frames(ma_stbvorbis* pVorbis, void* pFram while (totalFramesRead < frameCount) { ma_uint64 framesRemaining = (frameCount - totalFramesRead); int framesRead; - + if (framesRemaining > INT_MAX) { framesRemaining = INT_MAX; } @@ -43233,7 +48627,7 @@ MA_API ma_result ma_stbvorbis_read_pcm_frames(ma_stbvorbis* pVorbis, void* pFram framesRead = stb_vorbis_get_samples_float_interleaved(pVorbis->stb, channels, (float*)ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, format, channels), (int)framesRemaining * channels); /* Safe cast. */ totalFramesRead += framesRead; - if (framesRead < framesRemaining) { + if (framesRead < (int)framesRemaining) { break; /* Nothing left to read. Get out. */ } } @@ -43252,6 +48646,10 @@ MA_API ma_result ma_stbvorbis_read_pcm_frames(ma_stbvorbis* pVorbis, void* pFram *pFramesRead = totalFramesRead; } + if (result == MA_SUCCESS && totalFramesRead == 0) { + result = MA_AT_END; + } + return result; } #else @@ -43382,7 +48780,7 @@ MA_API ma_result ma_stbvorbis_get_data_format(ma_stbvorbis* pVorbis, ma_format* } if (pChannelMap != NULL) { - ma_get_standard_channel_map(ma_standard_channel_map_vorbis, (ma_uint32)ma_min(pVorbis->channels, channelMapCap), pChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_vorbis, pChannelMap, channelMapCap, pVorbis->channels); } return MA_SUCCESS; @@ -43442,7 +48840,7 @@ MA_API ma_result ma_stbvorbis_get_length_in_pcm_frames(ma_stbvorbis* pVorbis, ma } else { *pLength = stb_vorbis_stream_length_in_samples(pVorbis->stb); } - + return MA_SUCCESS; } #else @@ -43537,23 +48935,13 @@ static void ma_decoding_backend_uninit__stbvorbis(void* pUserData, ma_data_sourc ma_free(pVorbis, pAllocationCallbacks); } -static ma_result ma_decoding_backend_get_channel_map__stbvorbis(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap) -{ - ma_stbvorbis* pVorbis = (ma_stbvorbis*)pBackend; - - (void)pUserData; - - return ma_stbvorbis_get_data_format(pVorbis, NULL, NULL, NULL, pChannelMap, channelMapCap); -} - static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_stbvorbis = { ma_decoding_backend_init__stbvorbis, ma_decoding_backend_init_file__stbvorbis, NULL, /* onInitFileW() */ ma_decoding_backend_init_memory__stbvorbis, - ma_decoding_backend_uninit__stbvorbis, - ma_decoding_backend_get_channel_map__stbvorbis + ma_decoding_backend_uninit__stbvorbis }; static ma_result ma_decoder_init_vorbis__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) @@ -43578,17 +48966,7 @@ static ma_result ma_decoder__init_allocation_callbacks(const ma_decoder_config* static ma_result ma_decoder__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { - ma_uint64 framesRead = ma_decoder_read_pcm_frames((ma_decoder*)pDataSource, pFramesOut, frameCount); - - if (pFramesRead != NULL) { - *pFramesRead = framesRead; - } - - if (framesRead == 0) { - return MA_AT_END; - } - - return MA_SUCCESS; + return ma_decoder_read_pcm_frames((ma_decoder*)pDataSource, pFramesOut, frameCount, pFramesRead); } static ma_result ma_decoder__data_source_on_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) @@ -43596,45 +48974,30 @@ static ma_result ma_decoder__data_source_on_seek(ma_data_source* pDataSource, ma return ma_decoder_seek_to_pcm_frame((ma_decoder*)pDataSource, frameIndex); } -static ma_result ma_decoder__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_decoder__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { - ma_decoder* pDecoder = (ma_decoder*)pDataSource; - - *pFormat = pDecoder->outputFormat; - *pChannels = pDecoder->outputChannels; - *pSampleRate = pDecoder->outputSampleRate; - - return MA_SUCCESS; + return ma_decoder_get_data_format((ma_decoder*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); } static ma_result ma_decoder__data_source_on_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) { - ma_decoder* pDecoder = (ma_decoder*)pDataSource; - - return ma_decoder_get_cursor_in_pcm_frames(pDecoder, pCursor); + return ma_decoder_get_cursor_in_pcm_frames((ma_decoder*)pDataSource, pCursor); } static ma_result ma_decoder__data_source_on_get_length(ma_data_source* pDataSource, ma_uint64* pLength) { - ma_decoder* pDecoder = (ma_decoder*)pDataSource; - - *pLength = ma_decoder_get_length_in_pcm_frames(pDecoder); - if (*pLength == 0) { - return MA_NOT_IMPLEMENTED; - } - - return MA_SUCCESS; + return ma_decoder_get_length_in_pcm_frames((ma_decoder*)pDataSource, pLength); } static ma_data_source_vtable g_ma_decoder_data_source_vtable = { ma_decoder__data_source_on_read, ma_decoder__data_source_on_seek, - NULL, /* onMap */ - NULL, /* onUnmap */ ma_decoder__data_source_on_get_data_format, ma_decoder__data_source_on_get_cursor, - ma_decoder__data_source_on_get_length + ma_decoder__data_source_on_get_length, + NULL, /* onSetLooping */ + 0 }; static ma_result ma_decoder__preinit(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, ma_decoder_tell_proc onTell, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) @@ -43678,22 +49041,9 @@ static ma_result ma_decoder__preinit(ma_decoder_read_proc onRead, ma_decoder_see static ma_result ma_decoder__postinit(const ma_decoder_config* pConfig, ma_decoder* pDecoder) { - ma_result result = MA_SUCCESS; + ma_result result; - /* Basic validation in case the internal decoder supports different limits to miniaudio. */ - { - /* TODO: Remove this block once we remove MA_MIN_CHANNELS and MA_MAX_CHANNELS. */ - ma_uint32 internalChannels; - ma_data_source_get_data_format(pDecoder->pBackend, NULL, &internalChannels, NULL); - - if (internalChannels < MA_MIN_CHANNELS || internalChannels > MA_MAX_CHANNELS) { - result = MA_INVALID_DATA; - } - } - - if (result == MA_SUCCESS) { - result = ma_decoder__init_data_converter(pDecoder, pConfig); - } + result = ma_decoder__init_data_converter(pDecoder, pConfig); /* If we failed post initialization we need to uninitialize the decoder before returning to prevent a memory leak. */ if (result != MA_SUCCESS) { @@ -43704,83 +49054,6 @@ static ma_result ma_decoder__postinit(const ma_decoder_config* pConfig, ma_decod return result; } -MA_API ma_result ma_decoder_init_wav(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_WAV - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_wav; - - return ma_decoder_init(onRead, onSeek, pUserData, &config, pDecoder); -#else - (void)onRead; - (void)onSeek; - (void)pUserData; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_flac(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_FLAC - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_flac; - - return ma_decoder_init(onRead, onSeek, pUserData, &config, pDecoder); -#else - (void)onRead; - (void)onSeek; - (void)pUserData; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_mp3(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_MP3 - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_mp3; - - return ma_decoder_init(onRead, onSeek, pUserData, &config, pDecoder); -#else - (void)onRead; - (void)onSeek; - (void)pUserData; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_vorbis(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_VORBIS - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_vorbis; - - return ma_decoder_init(onRead, onSeek, pUserData, &config, pDecoder); -#else - (void)onRead; - (void)onSeek; - (void)pUserData; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - - static ma_result ma_decoder_init__internal(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) { @@ -43903,29 +49176,41 @@ MA_API ma_result ma_decoder_init(ma_decoder_read_proc onRead, ma_decoder_seek_pr } -static size_t ma_decoder__on_read_memory(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead) +static ma_result ma_decoder__on_read_memory(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead, size_t* pBytesRead) { size_t bytesRemaining; MA_ASSERT(pDecoder->data.memory.dataSize >= pDecoder->data.memory.currentReadPos); + if (pBytesRead != NULL) { + *pBytesRead = 0; + } + bytesRemaining = pDecoder->data.memory.dataSize - pDecoder->data.memory.currentReadPos; if (bytesToRead > bytesRemaining) { bytesToRead = bytesRemaining; } + if (bytesRemaining == 0) { + return MA_AT_END; + } + if (bytesToRead > 0) { MA_COPY_MEMORY(pBufferOut, pDecoder->data.memory.pData + pDecoder->data.memory.currentReadPos, bytesToRead); pDecoder->data.memory.currentReadPos += bytesToRead; } - return bytesToRead; + if (pBytesRead != NULL) { + *pBytesRead = bytesToRead; + } + + return MA_SUCCESS; } -static ma_bool32 ma_decoder__on_seek_memory(ma_decoder* pDecoder, ma_int64 byteOffset, ma_seek_origin origin) +static ma_result ma_decoder__on_seek_memory(ma_decoder* pDecoder, ma_int64 byteOffset, ma_seek_origin origin) { if (byteOffset > 0 && (ma_uint64)byteOffset > MA_SIZE_MAX) { - return MA_FALSE; /* Too far. */ + return MA_BAD_SEEK; } if (origin == ma_seek_origin_current) { @@ -43962,7 +49247,7 @@ static ma_bool32 ma_decoder__on_seek_memory(ma_decoder* pDecoder, ma_int64 byteO } } - return MA_TRUE; + return MA_SUCCESS; } static ma_result ma_decoder__on_tell_memory(ma_decoder* pDecoder, ma_int64* pCursor) @@ -44009,79 +49294,6 @@ MA_API ma_result ma_decoder_init_memory(const void* pData, size_t dataSize, cons return ma_decoder_init__internal(ma_decoder__on_read_memory, ma_decoder__on_seek_memory, NULL, &config, pDecoder); } -MA_API ma_result ma_decoder_init_memory_wav(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_WAV - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); /* Make sure the config is not NULL. */ - config.encodingFormat = ma_encoding_format_wav; - - return ma_decoder_init_memory(pData, dataSize, &config, pDecoder); -#else - (void)pData; - (void)dataSize; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_memory_flac(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_FLAC - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); /* Make sure the config is not NULL. */ - config.encodingFormat = ma_encoding_format_flac; - - return ma_decoder_init_memory(pData, dataSize, &config, pDecoder); -#else - (void)pData; - (void)dataSize; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_memory_mp3(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_MP3 - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); /* Make sure the config is not NULL. */ - config.encodingFormat = ma_encoding_format_mp3; - - return ma_decoder_init_memory(pData, dataSize, &config, pDecoder); -#else - (void)pData; - (void)dataSize; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_memory_vorbis(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_VORBIS - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); /* Make sure the config is not NULL. */ - config.encodingFormat = ma_encoding_format_vorbis; - - return ma_decoder_init_memory(pData, dataSize, &config, pDecoder); -#else - (void)pData; - (void)dataSize; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - - #if defined(MA_HAS_WAV) || \ defined(MA_HAS_MP3) || \ @@ -44262,30 +49474,19 @@ static ma_bool32 ma_path_extension_equal_w(const wchar_t* path, const wchar_t* e -static size_t ma_decoder__on_read_vfs(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead) +static ma_result ma_decoder__on_read_vfs(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead, size_t* pBytesRead) { - size_t bytesRead; - MA_ASSERT(pDecoder != NULL); MA_ASSERT(pBufferOut != NULL); - ma_vfs_or_default_read(pDecoder->data.vfs.pVFS, pDecoder->data.vfs.file, pBufferOut, bytesToRead, &bytesRead); - - return bytesRead; + return ma_vfs_or_default_read(pDecoder->data.vfs.pVFS, pDecoder->data.vfs.file, pBufferOut, bytesToRead, pBytesRead); } -static ma_bool32 ma_decoder__on_seek_vfs(ma_decoder* pDecoder, ma_int64 offset, ma_seek_origin origin) +static ma_result ma_decoder__on_seek_vfs(ma_decoder* pDecoder, ma_int64 offset, ma_seek_origin origin) { - ma_result result; - MA_ASSERT(pDecoder != NULL); - result = ma_vfs_or_default_seek(pDecoder->data.vfs.pVFS, pDecoder->data.vfs.file, offset, origin); - if (result != MA_SUCCESS) { - return MA_FALSE; - } - - return MA_TRUE; + return ma_vfs_or_default_seek(pDecoder->data.vfs.pVFS, pDecoder->data.vfs.file, offset, origin); } static ma_result ma_decoder__on_tell_vfs(ma_decoder* pDecoder, ma_int64* pCursor) @@ -44427,79 +49628,6 @@ MA_API ma_result ma_decoder_init_vfs(ma_vfs* pVFS, const char* pFilePath, const return MA_SUCCESS; } -MA_API ma_result ma_decoder_init_vfs_wav(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_WAV - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_wav; - - return ma_decoder_init_vfs(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_vfs_flac(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_FLAC - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_flac; - - return ma_decoder_init_vfs(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_vfs_mp3(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_MP3 - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_mp3; - - return ma_decoder_init_vfs(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_vfs_vorbis(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_VORBIS - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_vorbis; - - return ma_decoder_init_vfs(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - - static ma_result ma_decoder__preinit_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) { @@ -44630,132 +49758,16 @@ MA_API ma_result ma_decoder_init_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePath, c return MA_SUCCESS; } -MA_API ma_result ma_decoder_init_vfs_wav_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_WAV - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_wav; - - return ma_decoder_init_vfs_w(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_vfs_flac_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_FLAC - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_flac; - - return ma_decoder_init_vfs_w(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_vfs_mp3_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_MP3 - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_mp3; - - return ma_decoder_init_vfs_w(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_vfs_vorbis_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_VORBIS - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_vorbis; - - return ma_decoder_init_vfs_w(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - - - MA_API ma_result ma_decoder_init_file(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) { return ma_decoder_init_vfs(NULL, pFilePath, pConfig, pDecoder); } -MA_API ma_result ma_decoder_init_file_wav(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_wav(NULL, pFilePath, pConfig, pDecoder); -} - -MA_API ma_result ma_decoder_init_file_flac(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_flac(NULL, pFilePath, pConfig, pDecoder); -} - -MA_API ma_result ma_decoder_init_file_mp3(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_mp3(NULL, pFilePath, pConfig, pDecoder); -} - -MA_API ma_result ma_decoder_init_file_vorbis(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_vorbis(NULL, pFilePath, pConfig, pDecoder); -} - - - MA_API ma_result ma_decoder_init_file_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) { return ma_decoder_init_vfs_w(NULL, pFilePath, pConfig, pDecoder); } -MA_API ma_result ma_decoder_init_file_wav_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_wav_w(NULL, pFilePath, pConfig, pDecoder); -} - -MA_API ma_result ma_decoder_init_file_flac_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_flac_w(NULL, pFilePath, pConfig, pDecoder); -} - -MA_API ma_result ma_decoder_init_file_mp3_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_mp3_w(NULL, pFilePath, pConfig, pDecoder); -} - -MA_API ma_result ma_decoder_init_file_vorbis_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_vorbis_w(NULL, pFilePath, pConfig, pDecoder); -} - MA_API ma_result ma_decoder_uninit(ma_decoder* pDecoder) { if (pDecoder == NULL) { @@ -44768,147 +49780,162 @@ MA_API ma_result ma_decoder_uninit(ma_decoder* pDecoder) } } - /* Legacy. */ if (pDecoder->onRead == ma_decoder__on_read_vfs) { ma_vfs_or_default_close(pDecoder->data.vfs.pVFS, pDecoder->data.vfs.file); pDecoder->data.vfs.file = NULL; } - ma_data_converter_uninit(&pDecoder->converter); + ma_data_converter_uninit(&pDecoder->converter, &pDecoder->allocationCallbacks); ma_data_source_uninit(&pDecoder->ds); - return MA_SUCCESS; -} - -MA_API ma_result ma_decoder_get_cursor_in_pcm_frames(ma_decoder* pDecoder, ma_uint64* pCursor) -{ - if (pCursor == NULL) { - return MA_INVALID_ARGS; + if (pDecoder->pInputCache != NULL) { + ma_free(pDecoder->pInputCache, &pDecoder->allocationCallbacks); } - *pCursor = 0; - - if (pDecoder == NULL) { - return MA_INVALID_ARGS; - } - - *pCursor = pDecoder->readPointerInPCMFrames; - return MA_SUCCESS; } -MA_API ma_uint64 ma_decoder_get_length_in_pcm_frames(ma_decoder* pDecoder) +MA_API ma_result ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { - if (pDecoder == NULL) { - return 0; - } - - if (pDecoder->pBackend != NULL) { - ma_result result; - ma_uint64 nativeLengthInPCMFrames; - ma_uint32 internalSampleRate; - - ma_data_source_get_length_in_pcm_frames(pDecoder->pBackend, &nativeLengthInPCMFrames); - - result = ma_data_source_get_data_format(pDecoder->pBackend, NULL, NULL, &internalSampleRate); - if (result != MA_SUCCESS) { - return 0; /* Failed to retrieve the internal sample rate. */ - } - - if (internalSampleRate == pDecoder->outputSampleRate) { - return nativeLengthInPCMFrames; - } else { - return ma_calculate_frame_count_after_resampling(pDecoder->outputSampleRate, internalSampleRate, nativeLengthInPCMFrames); - } - } - - return 0; -} - -MA_API ma_uint64 ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount) -{ - ma_result result; + ma_result result = MA_SUCCESS; ma_uint64 totalFramesReadOut; - ma_uint64 totalFramesReadIn; void* pRunningFramesOut; + if (pFramesRead != NULL) { + *pFramesRead = 0; /* Safety. */ + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pDecoder == NULL) { - return 0; + return MA_INVALID_ARGS; } if (pDecoder->pBackend == NULL) { - return 0; + return MA_INVALID_OPERATION; } /* Fast path. */ if (pDecoder->converter.isPassthrough) { - result = ma_data_source_read_pcm_frames(pDecoder->pBackend, pFramesOut, frameCount, &totalFramesReadOut, MA_FALSE); + result = ma_data_source_read_pcm_frames(pDecoder->pBackend, pFramesOut, frameCount, &totalFramesReadOut); } else { /* Getting here means we need to do data conversion. If we're seeking forward and are _not_ doing resampling we can run this in a fast path. If we're doing resampling we need to run through each sample because we need to ensure it's internal cache is updated. */ if (pFramesOut == NULL && pDecoder->converter.hasResampler == MA_FALSE) { - result = ma_data_source_read_pcm_frames(pDecoder->pBackend, NULL, frameCount, &totalFramesReadOut, MA_FALSE); + result = ma_data_source_read_pcm_frames(pDecoder->pBackend, NULL, frameCount, &totalFramesReadOut); } else { /* Slow path. Need to run everything through the data converter. */ ma_format internalFormat; ma_uint32 internalChannels; totalFramesReadOut = 0; - totalFramesReadIn = 0; pRunningFramesOut = pFramesOut; - result = ma_data_source_get_data_format(pDecoder->pBackend, &internalFormat, &internalChannels, NULL); + result = ma_data_source_get_data_format(pDecoder->pBackend, &internalFormat, &internalChannels, NULL, NULL, 0); if (result != MA_SUCCESS) { - return 0; /* Failed to retrieve the internal format and channel count. */ + return result; /* Failed to retrieve the internal format and channel count. */ } - while (totalFramesReadOut < frameCount) { - ma_uint8 pIntermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; /* In internal format. */ - ma_uint64 intermediaryBufferCap = sizeof(pIntermediaryBuffer) / ma_get_bytes_per_frame(internalFormat, internalChannels); - ma_uint64 framesToReadThisIterationIn; - ma_uint64 framesReadThisIterationIn; - ma_uint64 framesToReadThisIterationOut; - ma_uint64 framesReadThisIterationOut; - ma_uint64 requiredInputFrameCount; + /* + We run a different path depending on whether or not we are using a heap-allocated + intermediary buffer or not. If the data converter does not support the calculation of + the required number of input frames, we'll use the heap-allocated path. Otherwise we'll + use the stack-allocated path. + */ + if (pDecoder->pInputCache != NULL) { + /* We don't have a way of determining the required number of input frames, so need to persistently store input data in a cache. */ + while (totalFramesReadOut < frameCount) { + ma_uint64 framesToReadThisIterationIn; + ma_uint64 framesToReadThisIterationOut; - framesToReadThisIterationOut = (frameCount - totalFramesReadOut); - framesToReadThisIterationIn = framesToReadThisIterationOut; - if (framesToReadThisIterationIn > intermediaryBufferCap) { - framesToReadThisIterationIn = intermediaryBufferCap; + /* If there's any data available in the cache, that needs to get processed first. */ + if (pDecoder->inputCacheRemaining > 0) { + framesToReadThisIterationOut = (frameCount - totalFramesReadOut); + framesToReadThisIterationIn = framesToReadThisIterationOut; + if (framesToReadThisIterationIn > pDecoder->inputCacheRemaining) { + framesToReadThisIterationIn = pDecoder->inputCacheRemaining; + } + + result = ma_data_converter_process_pcm_frames(&pDecoder->converter, ma_offset_pcm_frames_ptr(pDecoder->pInputCache, pDecoder->inputCacheConsumed, internalFormat, internalChannels), &framesToReadThisIterationIn, pRunningFramesOut, &framesToReadThisIterationOut); + if (result != MA_SUCCESS) { + break; + } + + pDecoder->inputCacheConsumed += framesToReadThisIterationIn; + pDecoder->inputCacheRemaining -= framesToReadThisIterationIn; + + totalFramesReadOut += framesToReadThisIterationOut; + + if (pRunningFramesOut != NULL) { + pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesToReadThisIterationOut * ma_get_bytes_per_frame(pDecoder->outputFormat, pDecoder->outputChannels)); + } + + if (framesToReadThisIterationIn == 0 && framesToReadThisIterationOut == 0) { + break; /* We're done. */ + } + } + + /* Getting here means there's no data in the cache and we need to fill it up from the data source. */ + if (pDecoder->inputCacheRemaining == 0) { + pDecoder->inputCacheConsumed = 0; + + result = ma_data_source_read_pcm_frames(pDecoder->pBackend, pDecoder->pInputCache, pDecoder->inputCacheCap, &pDecoder->inputCacheRemaining); + if (result != MA_SUCCESS) { + break; + } + } } + } else { + /* We have a way of determining the required number of input frames so just use the stack. */ + while (totalFramesReadOut < frameCount) { + ma_uint8 pIntermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; /* In internal format. */ + ma_uint64 intermediaryBufferCap = sizeof(pIntermediaryBuffer) / ma_get_bytes_per_frame(internalFormat, internalChannels); + ma_uint64 framesToReadThisIterationIn; + ma_uint64 framesReadThisIterationIn; + ma_uint64 framesToReadThisIterationOut; + ma_uint64 framesReadThisIterationOut; + ma_uint64 requiredInputFrameCount; - requiredInputFrameCount = ma_data_converter_get_required_input_frame_count(&pDecoder->converter, framesToReadThisIterationOut); - if (framesToReadThisIterationIn > requiredInputFrameCount) { - framesToReadThisIterationIn = requiredInputFrameCount; - } + framesToReadThisIterationOut = (frameCount - totalFramesReadOut); + framesToReadThisIterationIn = framesToReadThisIterationOut; + if (framesToReadThisIterationIn > intermediaryBufferCap) { + framesToReadThisIterationIn = intermediaryBufferCap; + } - if (requiredInputFrameCount > 0) { - result = ma_data_source_read_pcm_frames(pDecoder->pBackend, pIntermediaryBuffer, framesToReadThisIterationIn, &framesReadThisIterationIn, MA_FALSE); - totalFramesReadIn += framesReadThisIterationIn; - } else { - framesReadThisIterationIn = 0; - } + ma_data_converter_get_required_input_frame_count(&pDecoder->converter, framesToReadThisIterationOut, &requiredInputFrameCount); + if (framesToReadThisIterationIn > requiredInputFrameCount) { + framesToReadThisIterationIn = requiredInputFrameCount; + } - /* - At this point we have our decoded data in input format and now we need to convert to output format. Note that even if we didn't read any - input frames, we still want to try processing frames because there may some output frames generated from cached input data. - */ - framesReadThisIterationOut = framesToReadThisIterationOut; - result = ma_data_converter_process_pcm_frames(&pDecoder->converter, pIntermediaryBuffer, &framesReadThisIterationIn, pRunningFramesOut, &framesReadThisIterationOut); - if (result != MA_SUCCESS) { - break; - } + if (requiredInputFrameCount > 0) { + result = ma_data_source_read_pcm_frames(pDecoder->pBackend, pIntermediaryBuffer, framesToReadThisIterationIn, &framesReadThisIterationIn); + } else { + framesReadThisIterationIn = 0; + } - totalFramesReadOut += framesReadThisIterationOut; + /* + At this point we have our decoded data in input format and now we need to convert to output format. Note that even if we didn't read any + input frames, we still want to try processing frames because there may some output frames generated from cached input data. + */ + framesReadThisIterationOut = framesToReadThisIterationOut; + result = ma_data_converter_process_pcm_frames(&pDecoder->converter, pIntermediaryBuffer, &framesReadThisIterationIn, pRunningFramesOut, &framesReadThisIterationOut); + if (result != MA_SUCCESS) { + break; + } - if (pRunningFramesOut != NULL) { - pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesReadThisIterationOut * ma_get_bytes_per_frame(pDecoder->outputFormat, pDecoder->outputChannels)); - } + totalFramesReadOut += framesReadThisIterationOut; - if (framesReadThisIterationIn == 0 && framesReadThisIterationOut == 0) { - break; /* We're done. */ + if (pRunningFramesOut != NULL) { + pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesReadThisIterationOut * ma_get_bytes_per_frame(pDecoder->outputFormat, pDecoder->outputChannels)); + } + + if (framesReadThisIterationIn == 0 && framesReadThisIterationOut == 0) { + break; /* We're done. */ + } } } } @@ -44916,7 +49943,15 @@ MA_API ma_uint64 ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesO pDecoder->readPointerInPCMFrames += totalFramesReadOut; - return totalFramesReadOut; + if (pFramesRead != NULL) { + *pFramesRead = totalFramesReadOut; + } + + if (result == MA_SUCCESS && totalFramesReadOut == 0) { + result = MA_AT_END; + } + + return result; } MA_API ma_result ma_decoder_seek_to_pcm_frame(ma_decoder* pDecoder, ma_uint64 frameIndex) @@ -44930,7 +49965,7 @@ MA_API ma_result ma_decoder_seek_to_pcm_frame(ma_decoder* pDecoder, ma_uint64 fr ma_uint64 internalFrameIndex; ma_uint32 internalSampleRate; - result = ma_data_source_get_data_format(pDecoder->pBackend, NULL, NULL, &internalSampleRate); + result = ma_data_source_get_data_format(pDecoder->pBackend, NULL, NULL, &internalSampleRate, NULL, 0); if (result != MA_SUCCESS) { return result; /* Failed to retrieve the internal sample rate. */ } @@ -44953,8 +49988,90 @@ MA_API ma_result ma_decoder_seek_to_pcm_frame(ma_decoder* pDecoder, ma_uint64 fr return MA_INVALID_ARGS; } +MA_API ma_result ma_decoder_get_data_format(ma_decoder* pDecoder, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + if (pDecoder == NULL) { + return MA_INVALID_ARGS; + } + + if (pFormat != NULL) { + *pFormat = pDecoder->outputFormat; + } + + if (pChannels != NULL) { + *pChannels = pDecoder->outputChannels; + } + + if (pSampleRate != NULL) { + *pSampleRate = pDecoder->outputSampleRate; + } + + if (pChannelMap != NULL) { + ma_data_converter_get_output_channel_map(&pDecoder->converter, pChannelMap, channelMapCap); + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_decoder_get_cursor_in_pcm_frames(ma_decoder* pDecoder, ma_uint64* pCursor) +{ + if (pCursor == NULL) { + return MA_INVALID_ARGS; + } + + *pCursor = 0; + + if (pDecoder == NULL) { + return MA_INVALID_ARGS; + } + + *pCursor = pDecoder->readPointerInPCMFrames; + + return MA_SUCCESS; +} + +MA_API ma_result ma_decoder_get_length_in_pcm_frames(ma_decoder* pDecoder, ma_uint64* pLength) +{ + if (pLength == NULL) { + return MA_INVALID_ARGS; + } + + *pLength = 0; + + if (pDecoder == NULL) { + return MA_INVALID_ARGS; + } + + if (pDecoder->pBackend != NULL) { + ma_result result; + ma_uint64 internalLengthInPCMFrames; + ma_uint32 internalSampleRate; + + result = ma_data_source_get_length_in_pcm_frames(pDecoder->pBackend, &internalLengthInPCMFrames); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the internal length. */ + } + + result = ma_data_source_get_data_format(pDecoder->pBackend, NULL, NULL, &internalSampleRate, NULL, 0); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the internal sample rate. */ + } + + if (internalSampleRate == pDecoder->outputSampleRate) { + *pLength = internalLengthInPCMFrames; + } else { + *pLength = ma_calculate_frame_count_after_resampling(pDecoder->outputSampleRate, internalSampleRate, internalLengthInPCMFrames); + } + + return MA_SUCCESS; + } else { + return MA_NO_BACKEND; + } +} + MA_API ma_result ma_decoder_get_available_frames(ma_decoder* pDecoder, ma_uint64* pAvailableFrames) { + ma_result result; ma_uint64 totalFrameCount; if (pAvailableFrames == NULL) { @@ -44967,9 +50084,9 @@ MA_API ma_result ma_decoder_get_available_frames(ma_decoder* pDecoder, ma_uint64 return MA_INVALID_ARGS; } - totalFrameCount = ma_decoder_get_length_in_pcm_frames(pDecoder); - if (totalFrameCount == 0) { - return MA_NOT_IMPLEMENTED; + result = ma_decoder_get_length_in_pcm_frames(pDecoder, &totalFrameCount); + if (result != MA_SUCCESS) { + return result; } if (totalFrameCount <= pDecoder->readPointerInPCMFrames) { @@ -44978,12 +50095,13 @@ MA_API ma_result ma_decoder_get_available_frames(ma_decoder* pDecoder, ma_uint64 *pAvailableFrames = totalFrameCount - pDecoder->readPointerInPCMFrames; } - return MA_SUCCESS; /* No frames available. */ + return MA_SUCCESS; } static ma_result ma_decoder__full_decode_and_uninit(ma_decoder* pDecoder, ma_decoder_config* pConfigOut, ma_uint64* pFrameCountOut, void** ppPCMFramesOut) { + ma_result result; ma_uint64 totalFrameCount; ma_uint64 bpf; ma_uint64 dataCapInFrames; @@ -45004,21 +50122,19 @@ static ma_result ma_decoder__full_decode_and_uninit(ma_decoder* pDecoder, ma_dec /* Make room if there's not enough. */ if (totalFrameCount == dataCapInFrames) { void* pNewPCMFramesOut; - ma_uint64 oldDataCapInFrames = dataCapInFrames; ma_uint64 newDataCapInFrames = dataCapInFrames*2; if (newDataCapInFrames == 0) { newDataCapInFrames = 4096; } if ((newDataCapInFrames * bpf) > MA_SIZE_MAX) { - ma__free_from_callbacks(pPCMFramesOut, &pDecoder->allocationCallbacks); + ma_free(pPCMFramesOut, &pDecoder->allocationCallbacks); return MA_TOO_BIG; } - - pNewPCMFramesOut = (void*)ma__realloc_from_callbacks(pPCMFramesOut, (size_t)(newDataCapInFrames * bpf), (size_t)(oldDataCapInFrames * bpf), &pDecoder->allocationCallbacks); + pNewPCMFramesOut = (void*)ma_realloc(pPCMFramesOut, (size_t)(newDataCapInFrames * bpf), &pDecoder->allocationCallbacks); if (pNewPCMFramesOut == NULL) { - ma__free_from_callbacks(pPCMFramesOut, &pDecoder->allocationCallbacks); + ma_free(pPCMFramesOut, &pDecoder->allocationCallbacks); return MA_OUT_OF_MEMORY; } @@ -45029,9 +50145,13 @@ static ma_result ma_decoder__full_decode_and_uninit(ma_decoder* pDecoder, ma_dec frameCountToTryReading = dataCapInFrames - totalFrameCount; MA_ASSERT(frameCountToTryReading > 0); - framesJustRead = ma_decoder_read_pcm_frames(pDecoder, (ma_uint8*)pPCMFramesOut + (totalFrameCount * bpf), frameCountToTryReading); + result = ma_decoder_read_pcm_frames(pDecoder, (ma_uint8*)pPCMFramesOut + (totalFrameCount * bpf), frameCountToTryReading, &framesJustRead); totalFrameCount += framesJustRead; + if (result != MA_SUCCESS) { + break; + } + if (framesJustRead < frameCountToTryReading) { break; } @@ -45039,16 +50159,15 @@ static ma_result ma_decoder__full_decode_and_uninit(ma_decoder* pDecoder, ma_dec if (pConfigOut != NULL) { - pConfigOut->format = pDecoder->outputFormat; - pConfigOut->channels = pDecoder->outputChannels; + pConfigOut->format = pDecoder->outputFormat; + pConfigOut->channels = pDecoder->outputChannels; pConfigOut->sampleRate = pDecoder->outputSampleRate; - ma_channel_map_copy(pConfigOut->channelMap, pDecoder->outputChannelMap, pDecoder->outputChannels); } if (ppPCMFramesOut != NULL) { *ppPCMFramesOut = pPCMFramesOut; } else { - ma__free_from_callbacks(pPCMFramesOut, &pDecoder->allocationCallbacks); + ma_free(pPCMFramesOut, &pDecoder->allocationCallbacks); } if (pFrameCountOut != NULL) { @@ -45145,7 +50264,7 @@ static ma_result ma_encoder__on_init_wav(ma_encoder* pEncoder) MA_ASSERT(pEncoder != NULL); - pWav = (drwav*)ma__malloc_from_callbacks(sizeof(*pWav), &pEncoder->config.allocationCallbacks); + pWav = (drwav*)ma_malloc(sizeof(*pWav), &pEncoder->config.allocationCallbacks); if (pWav == NULL) { return MA_OUT_OF_MEMORY; } @@ -45184,28 +50303,35 @@ static void ma_encoder__on_uninit_wav(ma_encoder* pEncoder) MA_ASSERT(pWav != NULL); drwav_uninit(pWav); - ma__free_from_callbacks(pWav, &pEncoder->config.allocationCallbacks); + ma_free(pWav, &pEncoder->config.allocationCallbacks); } -static ma_uint64 ma_encoder__on_write_pcm_frames_wav(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount) +static ma_result ma_encoder__on_write_pcm_frames_wav(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount, ma_uint64* pFramesWritten) { drwav* pWav; + ma_uint64 framesWritten; MA_ASSERT(pEncoder != NULL); pWav = (drwav*)pEncoder->pInternalEncoder; MA_ASSERT(pWav != NULL); - return drwav_write_pcm_frames(pWav, frameCount, pFramesIn); + framesWritten = drwav_write_pcm_frames(pWav, frameCount, pFramesIn); + + if (pFramesWritten != NULL) { + *pFramesWritten = framesWritten; + } + + return MA_SUCCESS; } #endif -MA_API ma_encoder_config ma_encoder_config_init(ma_resource_format resourceFormat, ma_format format, ma_uint32 channels, ma_uint32 sampleRate) +MA_API ma_encoder_config ma_encoder_config_init(ma_encoding_format encodingFormat, ma_format format, ma_uint32 channels, ma_uint32 sampleRate) { ma_encoder_config config; MA_ZERO_OBJECT(&config); - config.resourceFormat = resourceFormat; + config.encodingFormat = encodingFormat; config.format = format; config.channels = channels; config.sampleRate = sampleRate; @@ -45256,9 +50382,9 @@ MA_API ma_result ma_encoder_init__internal(ma_encoder_write_proc onWrite, ma_enc pEncoder->onSeek = onSeek; pEncoder->pUserData = pUserData; - switch (pEncoder->config.resourceFormat) + switch (pEncoder->config.encodingFormat) { - case ma_resource_format_wav: + case ma_encoding_format_wav: { #if defined(MA_HAS_WAV) pEncoder->onInit = ma_encoder__on_init_wav; @@ -45368,13 +50494,17 @@ MA_API void ma_encoder_uninit(ma_encoder* pEncoder) } -MA_API ma_uint64 ma_encoder_write_pcm_frames(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount) +MA_API ma_result ma_encoder_write_pcm_frames(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount, ma_uint64* pFramesWritten) { - if (pEncoder == NULL || pFramesIn == NULL) { - return 0; + if (pFramesWritten != NULL) { + *pFramesWritten = 0; } - return pEncoder->onWritePCMFrames(pEncoder, pFramesIn, frameCount); + if (pEncoder == NULL || pFramesIn == NULL) { + return MA_INVALID_ARGS; + } + + return pEncoder->onWritePCMFrames(pEncoder, pFramesIn, frameCount, pFramesWritten); } #endif /* MA_NO_ENCODING */ @@ -45403,17 +50533,7 @@ MA_API ma_waveform_config ma_waveform_config_init(ma_format format, ma_uint32 ch static ma_result ma_waveform__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { - ma_uint64 framesRead = ma_waveform_read_pcm_frames((ma_waveform*)pDataSource, pFramesOut, frameCount); - - if (pFramesRead != NULL) { - *pFramesRead = framesRead; - } - - if (framesRead == 0) { - return MA_AT_END; - } - - return MA_SUCCESS; + return ma_waveform_read_pcm_frames((ma_waveform*)pDataSource, pFramesOut, frameCount, pFramesRead); } static ma_result ma_waveform__data_source_on_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) @@ -45421,13 +50541,14 @@ static ma_result ma_waveform__data_source_on_seek(ma_data_source* pDataSource, m return ma_waveform_seek_to_pcm_frame((ma_waveform*)pDataSource, frameIndex); } -static ma_result ma_waveform__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_waveform__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { ma_waveform* pWaveform = (ma_waveform*)pDataSource; *pFormat = pWaveform->config.format; *pChannels = pWaveform->config.channels; *pSampleRate = pWaveform->config.sampleRate; + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pWaveform->config.channels); return MA_SUCCESS; } @@ -45455,11 +50576,11 @@ static ma_data_source_vtable g_ma_waveform_data_source_vtable = { ma_waveform__data_source_on_read, ma_waveform__data_source_on_seek, - NULL, /* onMap */ - NULL, /* onUnmap */ ma_waveform__data_source_on_get_data_format, ma_waveform__data_source_on_get_cursor, - NULL /* onGetLength. There's no notion of a length in waveforms. */ + NULL, /* onGetLength. There's no notion of a length in waveforms. */ + NULL, /* onSetLooping */ + 0 }; MA_API ma_result ma_waveform_init(const ma_waveform_config* pConfig, ma_waveform* pWaveform) @@ -45768,10 +50889,18 @@ static void ma_waveform_read_pcm_frames__sawtooth(ma_waveform* pWaveform, void* } } -MA_API ma_uint64 ma_waveform_read_pcm_frames(ma_waveform* pWaveform, void* pFramesOut, ma_uint64 frameCount) +MA_API ma_result ma_waveform_read_pcm_frames(ma_waveform* pWaveform, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pWaveform == NULL) { - return 0; + return MA_INVALID_ARGS; } if (pFramesOut != NULL) { @@ -45797,13 +50926,17 @@ MA_API ma_uint64 ma_waveform_read_pcm_frames(ma_waveform* pWaveform, void* pFram ma_waveform_read_pcm_frames__sawtooth(pWaveform, pFramesOut, frameCount); } break; - default: return 0; + default: return MA_INVALID_OPERATION; /* Unknown waveform type. */ } } else { pWaveform->time += pWaveform->advance * (ma_int64)frameCount; /* Cast to int64 required for VC6. Won't affect anything in practice. */ } - return frameCount; + if (pFramesRead != NULL) { + *pFramesRead = frameCount; + } + + return MA_SUCCESS; } MA_API ma_result ma_waveform_seek_to_pcm_frame(ma_waveform* pWaveform, ma_uint64 frameIndex) @@ -45839,17 +50972,7 @@ MA_API ma_noise_config ma_noise_config_init(ma_format format, ma_uint32 channels static ma_result ma_noise__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { - ma_uint64 framesRead = ma_noise_read_pcm_frames((ma_noise*)pDataSource, pFramesOut, frameCount); - - if (pFramesRead != NULL) { - *pFramesRead = framesRead; - } - - if (framesRead == 0) { - return MA_AT_END; - } - - return MA_SUCCESS; + return ma_noise_read_pcm_frames((ma_noise*)pDataSource, pFramesOut, frameCount, pFramesRead); } static ma_result ma_noise__data_source_on_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) @@ -45860,13 +50983,14 @@ static ma_result ma_noise__data_source_on_seek(ma_data_source* pDataSource, ma_u return MA_SUCCESS; } -static ma_result ma_noise__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_noise__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { ma_noise* pNoise = (ma_noise*)pDataSource; *pFormat = pNoise->config.format; *pChannels = pNoise->config.channels; *pSampleRate = 0; /* There is no notion of sample rate with noise generation. */ + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pNoise->config.channels); return MA_SUCCESS; } @@ -45875,17 +50999,105 @@ static ma_data_source_vtable g_ma_noise_data_source_vtable = { ma_noise__data_source_on_read, ma_noise__data_source_on_seek, /* No-op for noise. */ - NULL, /* onMap */ - NULL, /* onUnmap */ ma_noise__data_source_on_get_data_format, NULL, /* onGetCursor. No notion of a cursor for noise. */ - NULL /* onGetLength. No notion of a length for noise. */ + NULL, /* onGetLength. No notion of a length for noise. */ + NULL, /* onSetLooping */ + 0 }; -MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, ma_noise* pNoise) + +#ifndef MA_PINK_NOISE_BIN_SIZE +#define MA_PINK_NOISE_BIN_SIZE 16 +#endif + +typedef struct +{ + size_t sizeInBytes; + struct + { + size_t binOffset; + size_t accumulationOffset; + size_t counterOffset; + } pink; + struct + { + size_t accumulationOffset; + } brownian; +} ma_noise_heap_layout; + +static ma_result ma_noise_get_heap_layout(const ma_noise_config* pConfig, ma_noise_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channels == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* Pink. */ + if (pConfig->type == ma_noise_type_pink) { + /* bin */ + pHeapLayout->pink.binOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(double*) * pConfig->channels; + pHeapLayout->sizeInBytes += sizeof(double ) * pConfig->channels * MA_PINK_NOISE_BIN_SIZE; + + /* accumulation */ + pHeapLayout->pink.accumulationOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(double) * pConfig->channels; + + /* counter */ + pHeapLayout->pink.counterOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(ma_uint32) * pConfig->channels; + } + + /* Brownian. */ + if (pConfig->type == ma_noise_type_brownian) { + /* accumulation */ + pHeapLayout->brownian.accumulationOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(double) * pConfig->channels; + } + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_noise_get_heap_size(const ma_noise_config* pConfig, size_t* pHeapSizeInBytes) { ma_result result; + ma_noise_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_noise_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_noise_init_preallocated(const ma_noise_config* pConfig, void* pHeap, ma_noise* pNoise) +{ + ma_result result; + ma_noise_heap_layout heapLayout; ma_data_source_config dataSourceConfig; + ma_uint32 iChannel; if (pNoise == NULL) { return MA_INVALID_ARGS; @@ -45893,13 +51105,13 @@ MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, ma_noise* pNoise) MA_ZERO_OBJECT(pNoise); - if (pConfig == NULL) { - return MA_INVALID_ARGS; + result = ma_noise_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; } - if (pConfig->channels < MA_MIN_CHANNELS || pConfig->channels > MA_MAX_CHANNELS) { - return MA_INVALID_ARGS; - } + pNoise->_pHeap = pHeap; + MA_ZERO_MEMORY(pNoise->_pHeap, heapLayout.sizeInBytes); dataSourceConfig = ma_data_source_config_init(); dataSourceConfig.vtable = &g_ma_noise_data_source_vtable; @@ -45913,15 +51125,20 @@ MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, ma_noise* pNoise) ma_lcg_seed(&pNoise->lcg, pConfig->seed); if (pNoise->config.type == ma_noise_type_pink) { - ma_uint32 iChannel; + pNoise->state.pink.bin = (double** )ma_offset_ptr(pHeap, heapLayout.pink.binOffset); + pNoise->state.pink.accumulation = (double* )ma_offset_ptr(pHeap, heapLayout.pink.accumulationOffset); + pNoise->state.pink.counter = (ma_uint32*)ma_offset_ptr(pHeap, heapLayout.pink.counterOffset); + for (iChannel = 0; iChannel < pConfig->channels; iChannel += 1) { + pNoise->state.pink.bin[iChannel] = (double*)ma_offset_ptr(pHeap, heapLayout.pink.binOffset + (sizeof(double*) * pConfig->channels) + (sizeof(double) * MA_PINK_NOISE_BIN_SIZE * iChannel)); pNoise->state.pink.accumulation[iChannel] = 0; pNoise->state.pink.counter[iChannel] = 1; } } if (pNoise->config.type == ma_noise_type_brownian) { - ma_uint32 iChannel; + pNoise->state.brownian.accumulation = (double*)ma_offset_ptr(pHeap, heapLayout.brownian.accumulationOffset); + for (iChannel = 0; iChannel < pConfig->channels; iChannel += 1) { pNoise->state.brownian.accumulation[iChannel] = 0; } @@ -45930,13 +51147,47 @@ MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, ma_noise* pNoise) return MA_SUCCESS; } -MA_API void ma_noise_uninit(ma_noise* pNoise) +MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_noise* pNoise) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_noise_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_noise_init_preallocated(pConfig, pHeap, pNoise); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pNoise->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_noise_uninit(ma_noise* pNoise, const ma_allocation_callbacks* pAllocationCallbacks) { if (pNoise == NULL) { return; } ma_data_source_uninit(&pNoise->ds); + + if (pNoise->_ownsHeap) { + ma_free(pNoise->_pHeap, pAllocationCallbacks); + } } MA_API ma_result ma_noise_set_amplitude(ma_noise* pNoise, double amplitude) @@ -45985,7 +51236,7 @@ static MA_INLINE ma_uint64 ma_noise_read_pcm_frames__white(ma_noise* pNoise, voi ma_uint64 iFrame; ma_uint32 iChannel; const ma_uint32 channels = pNoise->config.channels; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); if (pNoise->config.format == ma_format_f32) { float* pFramesOutF32 = (float*)pFramesOut; @@ -46079,7 +51330,7 @@ static MA_INLINE float ma_noise_f32_pink(ma_noise* pNoise, ma_uint32 iChannel) double binNext; unsigned int ibin; - ibin = ma_tzcnt32(pNoise->state.pink.counter[iChannel]) & (ma_countof(pNoise->state.pink.bin[0]) - 1); + ibin = ma_tzcnt32(pNoise->state.pink.counter[iChannel]) & (MA_PINK_NOISE_BIN_SIZE - 1); binPrev = pNoise->state.pink.bin[iChannel][ibin]; binNext = ma_lcg_rand_f64(&pNoise->lcg); @@ -46104,7 +51355,7 @@ static MA_INLINE ma_uint64 ma_noise_read_pcm_frames__pink(ma_noise* pNoise, void ma_uint64 iFrame; ma_uint32 iChannel; const ma_uint32 channels = pNoise->config.channels; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); if (pNoise->config.format == ma_format_f32) { float* pFramesOutF32 = (float*)pFramesOut; @@ -46186,7 +51437,7 @@ static MA_INLINE ma_uint64 ma_noise_read_pcm_frames__brownian(ma_noise* pNoise, ma_uint64 iFrame; ma_uint32 iChannel; const ma_uint32 channels = pNoise->config.channels; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); if (pNoise->config.format == ma_format_f32) { float* pFramesOutF32 = (float*)pFramesOut; @@ -46244,37 +51495,10007 @@ static MA_INLINE ma_uint64 ma_noise_read_pcm_frames__brownian(ma_noise* pNoise, return frameCount; } -MA_API ma_uint64 ma_noise_read_pcm_frames(ma_noise* pNoise, void* pFramesOut, ma_uint64 frameCount) +MA_API ma_result ma_noise_read_pcm_frames(ma_noise* pNoise, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { + ma_uint64 framesRead = 0; + + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pNoise == NULL) { - return 0; + return MA_INVALID_ARGS; } /* The output buffer is allowed to be NULL. Since we aren't tracking cursors or anything we can just do nothing and pretend to be successful. */ if (pFramesOut == NULL) { - return frameCount; + framesRead = frameCount; + } else { + switch (pNoise->config.type) { + case ma_noise_type_white: framesRead = ma_noise_read_pcm_frames__white (pNoise, pFramesOut, frameCount); break; + case ma_noise_type_pink: framesRead = ma_noise_read_pcm_frames__pink (pNoise, pFramesOut, frameCount); break; + case ma_noise_type_brownian: framesRead = ma_noise_read_pcm_frames__brownian(pNoise, pFramesOut, frameCount); break; + default: return MA_INVALID_OPERATION; /* Unknown noise type. */ + } } - if (pNoise->config.type == ma_noise_type_white) { - return ma_noise_read_pcm_frames__white(pNoise, pFramesOut, frameCount); + if (pFramesRead != NULL) { + *pFramesRead = framesRead; } - if (pNoise->config.type == ma_noise_type_pink) { - return ma_noise_read_pcm_frames__pink(pNoise, pFramesOut, frameCount); - } - - if (pNoise->config.type == ma_noise_type_brownian) { - return ma_noise_read_pcm_frames__brownian(pNoise, pFramesOut, frameCount); - } - - /* Should never get here. */ - MA_ASSERT(MA_FALSE); - return 0; + return MA_SUCCESS; } #endif /* MA_NO_GENERATION */ +#ifndef MA_NO_RESOURCE_MANAGER +#ifndef MA_RESOURCE_MANAGER_PAGE_SIZE_IN_MILLISECONDS +#define MA_RESOURCE_MANAGER_PAGE_SIZE_IN_MILLISECONDS 1000 +#endif + +#ifndef MA_RESOURCE_MANAGER_JOB_QUEUE_CAPACITY +#define MA_RESOURCE_MANAGER_JOB_QUEUE_CAPACITY 1024 +#endif + +MA_API ma_resource_manager_pipeline_notifications ma_resource_manager_pipeline_notifications_init(void) +{ + ma_resource_manager_pipeline_notifications notifications; + + MA_ZERO_OBJECT(¬ifications); + + return notifications; +} + +static void ma_resource_manager_pipeline_notifications_signal_all_notifications(const ma_resource_manager_pipeline_notifications* pPipelineNotifications) +{ + if (pPipelineNotifications == NULL) { + return; + } + + if (pPipelineNotifications->init.pNotification) { ma_async_notification_signal(pPipelineNotifications->init.pNotification); } + if (pPipelineNotifications->done.pNotification) { ma_async_notification_signal(pPipelineNotifications->done.pNotification); } +} + +static void ma_resource_manager_pipeline_notifications_acquire_all_fences(const ma_resource_manager_pipeline_notifications* pPipelineNotifications) +{ + if (pPipelineNotifications == NULL) { + return; + } + + if (pPipelineNotifications->init.pFence != NULL) { ma_fence_acquire(pPipelineNotifications->init.pFence); } + if (pPipelineNotifications->done.pFence != NULL) { ma_fence_acquire(pPipelineNotifications->done.pFence); } +} + +static void ma_resource_manager_pipeline_notifications_release_all_fences(const ma_resource_manager_pipeline_notifications* pPipelineNotifications) +{ + if (pPipelineNotifications == NULL) { + return; + } + + if (pPipelineNotifications->init.pFence != NULL) { ma_fence_release(pPipelineNotifications->init.pFence); } + if (pPipelineNotifications->done.pFence != NULL) { ma_fence_release(pPipelineNotifications->done.pFence); } +} + + + +#define MA_RESOURCE_MANAGER_JOB_ID_NONE ~((ma_uint64)0) +#define MA_RESOURCE_MANAGER_JOB_SLOT_NONE (ma_uint16)(~0) + +static MA_INLINE ma_uint32 ma_resource_manager_job_extract_refcount(ma_uint64 toc) +{ + return (ma_uint32)(toc >> 32); +} + +static MA_INLINE ma_uint16 ma_resource_manager_job_extract_slot(ma_uint64 toc) +{ + return (ma_uint16)(toc & 0x0000FFFF); +} + +static MA_INLINE ma_uint16 ma_resource_manager_job_extract_code(ma_uint64 toc) +{ + return (ma_uint16)((toc & 0xFFFF0000) >> 16); +} + +static MA_INLINE ma_uint64 ma_resource_manager_job_toc_to_allocation(ma_uint64 toc) +{ + return ((ma_uint64)ma_resource_manager_job_extract_refcount(toc) << 32) | (ma_uint64)ma_resource_manager_job_extract_slot(toc); +} + +static MA_INLINE ma_uint64 ma_resource_manager_job_set_refcount(ma_uint64 toc, ma_uint32 refcount) +{ + /* Clear the reference count first. */ + toc = toc & ~((ma_uint64)0xFFFFFFFF << 32); + toc = toc | ((ma_uint64)refcount << 32); + + return toc; +} + + +MA_API ma_resource_manager_job ma_resource_manager_job_init(ma_uint16 code) +{ + ma_resource_manager_job job; + + MA_ZERO_OBJECT(&job); + job.toc.breakup.code = code; + job.toc.breakup.slot = MA_RESOURCE_MANAGER_JOB_SLOT_NONE; /* Temp value. Will be allocated when posted to a queue. */ + job.next = MA_RESOURCE_MANAGER_JOB_ID_NONE; + + return job; +} + + + +MA_API ma_resource_manager_job_queue_config ma_resource_manager_job_queue_config_init(ma_uint32 flags, ma_uint32 capacity) +{ + ma_resource_manager_job_queue_config config; + + config.flags = flags; + config.capacity = capacity; + + return config; +} + + +typedef struct +{ + size_t sizeInBytes; + size_t allocatorOffset; + size_t jobsOffset; +} ma_resource_manager_job_queue_heap_layout; + +static ma_result ma_resource_manager_job_queue_get_heap_layout(const ma_resource_manager_job_queue_config* pConfig, ma_resource_manager_job_queue_heap_layout* pHeapLayout) +{ + ma_result result; + + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->capacity == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* Allocator. */ + { + ma_slot_allocator_config allocatorConfig; + size_t allocatorHeapSizeInBytes; + + allocatorConfig = ma_slot_allocator_config_init(pConfig->capacity); + result = ma_slot_allocator_get_heap_size(&allocatorConfig, &allocatorHeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->allocatorOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += allocatorHeapSizeInBytes; + } + + /* Jobs. */ + pHeapLayout->jobsOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(pConfig->capacity * sizeof(ma_resource_manager_job)); + + return MA_SUCCESS; +} + +MA_API ma_result ma_resource_manager_job_queue_get_heap_size(const ma_resource_manager_job_queue_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_resource_manager_job_queue_heap_layout layout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_resource_manager_job_queue_get_heap_layout(pConfig, &layout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = layout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_resource_manager_job_queue_init_preallocated(const ma_resource_manager_job_queue_config* pConfig, void* pHeap, ma_resource_manager_job_queue* pQueue) +{ + ma_result result; + ma_resource_manager_job_queue_heap_layout heapLayout; + ma_slot_allocator_config allocatorConfig; + + if (pQueue == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pQueue); + + result = ma_resource_manager_job_queue_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pQueue->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pQueue->flags = pConfig->flags; + pQueue->capacity = pConfig->capacity; + pQueue->pJobs = (ma_resource_manager_job*)ma_offset_ptr(pHeap, heapLayout.jobsOffset); + + allocatorConfig = ma_slot_allocator_config_init(pConfig->capacity); + result = ma_slot_allocator_init_preallocated(&allocatorConfig, ma_offset_ptr(pHeap, heapLayout.allocatorOffset), &pQueue->allocator); + if (result != MA_SUCCESS) { + return result; + } + + /* We need a semaphore if we're running in non-blocking mode. If threading is disabled we need to return an error. */ + if ((pQueue->flags & MA_RESOURCE_MANAGER_JOB_QUEUE_FLAG_NON_BLOCKING) == 0) { + #ifndef MA_NO_THREADING + { + ma_semaphore_init(0, &pQueue->sem); + } + #else + { + /* Threading is disabled and we've requested non-blocking mode. */ + return MA_INVALID_OPERATION; + } + #endif + } + + /* + Our queue needs to be initialized with a free standing node. This should always be slot 0. Required for the lock free algorithm. The first job in the queue is + just a dummy item for giving us the first item in the list which is stored in the "next" member. + */ + ma_slot_allocator_alloc(&pQueue->allocator, &pQueue->head); /* Will never fail. */ + pQueue->pJobs[ma_resource_manager_job_extract_slot(pQueue->head)].next = MA_RESOURCE_MANAGER_JOB_ID_NONE; + pQueue->tail = pQueue->head; + + return MA_SUCCESS; +} + +MA_API ma_result ma_resource_manager_job_queue_init(const ma_resource_manager_job_queue_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_resource_manager_job_queue* pQueue) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_resource_manager_job_queue_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_resource_manager_job_queue_init_preallocated(pConfig, pHeap, pQueue); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pQueue->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_resource_manager_job_queue_uninit(ma_resource_manager_job_queue* pQueue, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pQueue == NULL) { + return; + } + + /* All we need to do is uninitialize the semaphore. */ + if ((pQueue->flags & MA_RESOURCE_MANAGER_JOB_QUEUE_FLAG_NON_BLOCKING) == 0) { + #ifndef MA_NO_THREADING + { + ma_semaphore_uninit(&pQueue->sem); + } + #else + { + MA_ASSERT(MA_FALSE); /* Should never get here. Should have been checked at initialization time. */ + } + #endif + } + + ma_slot_allocator_uninit(&pQueue->allocator, pAllocationCallbacks); + + if (pQueue->_ownsHeap) { + ma_free(pQueue->_pHeap, pAllocationCallbacks); + } +} + +static ma_bool32 ma_resource_manager_job_queue_cas(volatile ma_uint64* dst, ma_uint64 expected, ma_uint64 desired) +{ + /* The new counter is taken from the expected value. */ + return c89atomic_compare_and_swap_64(dst, expected, ma_resource_manager_job_set_refcount(desired, ma_resource_manager_job_extract_refcount(expected) + 1)) == expected; +} + +MA_API ma_result ma_resource_manager_job_queue_post(ma_resource_manager_job_queue* pQueue, const ma_resource_manager_job* pJob) +{ + /* + Lock free queue implementation based on the paper by Michael and Scott: Nonblocking Algorithms and Preemption-Safe Locking on Multiprogrammed Shared Memory Multiprocessors + */ + ma_result result; + ma_uint64 slot; + ma_uint64 tail; + ma_uint64 next; + + if (pQueue == NULL || pJob == NULL) { + return MA_INVALID_ARGS; + } + + /* We need a new slot. */ + result = ma_slot_allocator_alloc(&pQueue->allocator, &slot); + if (result != MA_SUCCESS) { + return result; /* Probably ran out of slots. If so, MA_OUT_OF_MEMORY will be returned. */ + } + + /* At this point we should have a slot to place the job. */ + MA_ASSERT(ma_resource_manager_job_extract_slot(slot) < pQueue->capacity); + + /* We need to put the job into memory before we do anything. */ + pQueue->pJobs[ma_resource_manager_job_extract_slot(slot)] = *pJob; + pQueue->pJobs[ma_resource_manager_job_extract_slot(slot)].toc.allocation = slot; /* This will overwrite the job code. */ + pQueue->pJobs[ma_resource_manager_job_extract_slot(slot)].toc.breakup.code = pJob->toc.breakup.code; /* The job code needs to be applied again because the line above overwrote it. */ + pQueue->pJobs[ma_resource_manager_job_extract_slot(slot)].next = MA_RESOURCE_MANAGER_JOB_ID_NONE; /* Reset for safety. */ + + #ifndef MA_USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE + ma_spinlock_lock(&pQueue->lock); + #endif + { + /* The job is stored in memory so now we need to add it to our linked list. We only ever add items to the end of the list. */ + for (;;) { + tail = c89atomic_load_64(&pQueue->tail); + next = c89atomic_load_64(&pQueue->pJobs[ma_resource_manager_job_extract_slot(tail)].next); + + if (ma_resource_manager_job_toc_to_allocation(tail) == ma_resource_manager_job_toc_to_allocation(c89atomic_load_64(&pQueue->tail))) { + if (ma_resource_manager_job_extract_slot(next) == 0xFFFF) { + if (ma_resource_manager_job_queue_cas(&pQueue->pJobs[ma_resource_manager_job_extract_slot(tail)].next, next, slot)) { + break; + } + } else { + ma_resource_manager_job_queue_cas(&pQueue->tail, tail, ma_resource_manager_job_extract_slot(next)); + } + } + } + ma_resource_manager_job_queue_cas(&pQueue->tail, tail, slot); + } + #ifndef MA_USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE + ma_spinlock_unlock(&pQueue->lock); + #endif + + + /* Signal the semaphore as the last step if we're using synchronous mode. */ + if ((pQueue->flags & MA_RESOURCE_MANAGER_JOB_QUEUE_FLAG_NON_BLOCKING) == 0) { + #ifndef MA_NO_THREADING + { + ma_semaphore_release(&pQueue->sem); + } + #else + { + MA_ASSERT(MA_FALSE); /* Should never get here. Should have been checked at initialization time. */ + } + #endif + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_resource_manager_job_queue_next(ma_resource_manager_job_queue* pQueue, ma_resource_manager_job* pJob) +{ + ma_uint64 head; + ma_uint64 tail; + ma_uint64 next; + + if (pQueue == NULL || pJob == NULL) { + return MA_INVALID_ARGS; + } + + /* If we're running in synchronous mode we'll need to wait on a semaphore. */ + if ((pQueue->flags & MA_RESOURCE_MANAGER_JOB_QUEUE_FLAG_NON_BLOCKING) == 0) { + #ifndef MA_NO_THREADING + { + ma_semaphore_wait(&pQueue->sem); + } + #else + { + MA_ASSERT(MA_FALSE); /* Should never get here. Should have been checked at initialization time. */ + } + #endif + } + + #ifndef MA_USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE + ma_spinlock_lock(&pQueue->lock); + #endif + { + /* + BUG: In lock-free mode, multiple threads can be in this section of code. The "head" variable in the loop below + is stored. One thread can fall through to the freeing of this item while another is still using "head" for the + retrieval of the "next" variable. + + The slot allocator might need to make use of some reference counting to ensure it's only truely freed when + there are no more references to the item. This must be fixed before removing these locks. + */ + + /* Now we need to remove the root item from the list. */ + for (;;) { + head = c89atomic_load_64(&pQueue->head); + tail = c89atomic_load_64(&pQueue->tail); + next = c89atomic_load_64(&pQueue->pJobs[ma_resource_manager_job_extract_slot(head)].next); + + if (ma_resource_manager_job_toc_to_allocation(head) == ma_resource_manager_job_toc_to_allocation(c89atomic_load_64(&pQueue->head))) { + if (ma_resource_manager_job_extract_slot(head) == ma_resource_manager_job_extract_slot(tail)) { + if (ma_resource_manager_job_extract_slot(next) == 0xFFFF) { + #ifndef MA_USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE + ma_spinlock_unlock(&pQueue->lock); + #endif + return MA_NO_DATA_AVAILABLE; + } + ma_resource_manager_job_queue_cas(&pQueue->tail, tail, ma_resource_manager_job_extract_slot(next)); + } else { + *pJob = pQueue->pJobs[ma_resource_manager_job_extract_slot(next)]; + if (ma_resource_manager_job_queue_cas(&pQueue->head, head, ma_resource_manager_job_extract_slot(next))) { + break; + } + } + } + } + } + #ifndef MA_USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE + ma_spinlock_unlock(&pQueue->lock); + #endif + + ma_slot_allocator_free(&pQueue->allocator, head); + + /* + If it's a quit job make sure it's put back on the queue to ensure other threads have an opportunity to detect it and terminate naturally. We + could instead just leave it on the queue, but that would involve fiddling with the lock-free code above and I want to keep that as simple as + possible. + */ + if (pJob->toc.breakup.code == MA_RESOURCE_MANAGER_JOB_QUIT) { + ma_resource_manager_job_queue_post(pQueue, pJob); + return MA_CANCELLED; /* Return a cancelled status just in case the thread is checking return codes and not properly checking for a quit job. */ + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_resource_manager_job_queue_free(ma_resource_manager_job_queue* pQueue, ma_resource_manager_job* pJob) +{ + if (pQueue == NULL || pJob == NULL) { + return MA_INVALID_ARGS; + } + + return ma_slot_allocator_free(&pQueue->allocator, ma_resource_manager_job_toc_to_allocation(pJob->toc.allocation)); +} + + + + + +#ifndef MA_DEFAULT_HASH_SEED +#define MA_DEFAULT_HASH_SEED 42 +#endif + +/* MurmurHash3. Based on code from https://github.com/PeterScott/murmur3/blob/master/murmur3.c (public domain). */ +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic push + #if __GNUC__ >= 7 + #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" + #endif +#endif + +static MA_INLINE ma_uint32 ma_rotl32(ma_uint32 x, ma_int8 r) +{ + return (x << r) | (x >> (32 - r)); +} + +static MA_INLINE ma_uint32 ma_hash_getblock(const ma_uint32* blocks, int i) +{ + if (ma_is_little_endian()) { + return blocks[i]; + } else { + return ma_swap_endian_uint32(blocks[i]); + } +} + +static MA_INLINE ma_uint32 ma_hash_fmix32(ma_uint32 h) +{ + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + + return h; +} + +static ma_uint32 ma_hash_32(const void* key, int len, ma_uint32 seed) +{ + const ma_uint8* data = (const ma_uint8*)key; + const ma_uint32* blocks; + const ma_uint8* tail; + const int nblocks = len / 4; + ma_uint32 h1 = seed; + ma_uint32 c1 = 0xcc9e2d51; + ma_uint32 c2 = 0x1b873593; + ma_uint32 k1; + int i; + + blocks = (const ma_uint32 *)(data + nblocks*4); + + for(i = -nblocks; i; i++) { + k1 = ma_hash_getblock(blocks,i); + + k1 *= c1; + k1 = ma_rotl32(k1, 15); + k1 *= c2; + + h1 ^= k1; + h1 = ma_rotl32(h1, 13); + h1 = h1*5 + 0xe6546b64; + } + + + tail = (const ma_uint8*)(data + nblocks*4); + + k1 = 0; + switch(len & 3) { + case 3: k1 ^= tail[2] << 16; + case 2: k1 ^= tail[1] << 8; + case 1: k1 ^= tail[0]; + k1 *= c1; k1 = ma_rotl32(k1, 15); k1 *= c2; h1 ^= k1; + }; + + + h1 ^= len; + h1 = ma_hash_fmix32(h1); + + return h1; +} + +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic push +#endif +/* End MurmurHash3 */ + +static ma_uint32 ma_hash_string_32(const char* str) +{ + return ma_hash_32(str, (int)strlen(str), MA_DEFAULT_HASH_SEED); +} + +static ma_uint32 ma_hash_string_w_32(const wchar_t* str) +{ + return ma_hash_32(str, (int)wcslen(str) * sizeof(*str), MA_DEFAULT_HASH_SEED); +} + + + + +/* +Basic BST Functions +*/ +static ma_result ma_resource_manager_data_buffer_node_search(ma_resource_manager* pResourceManager, ma_uint32 hashedName32, ma_resource_manager_data_buffer_node** ppDataBufferNode) +{ + ma_resource_manager_data_buffer_node* pCurrentNode; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(ppDataBufferNode != NULL); + + pCurrentNode = pResourceManager->pRootDataBufferNode; + while (pCurrentNode != NULL) { + if (hashedName32 == pCurrentNode->hashedName32) { + break; /* Found. */ + } else if (hashedName32 < pCurrentNode->hashedName32) { + pCurrentNode = pCurrentNode->pChildLo; + } else { + pCurrentNode = pCurrentNode->pChildHi; + } + } + + *ppDataBufferNode = pCurrentNode; + + if (pCurrentNode == NULL) { + return MA_DOES_NOT_EXIST; + } else { + return MA_SUCCESS; + } +} + +static ma_result ma_resource_manager_data_buffer_node_insert_point(ma_resource_manager* pResourceManager, ma_uint32 hashedName32, ma_resource_manager_data_buffer_node** ppInsertPoint) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager_data_buffer_node* pCurrentNode; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(ppInsertPoint != NULL); + + *ppInsertPoint = NULL; + + if (pResourceManager->pRootDataBufferNode == NULL) { + return MA_SUCCESS; /* No items. */ + } + + /* We need to find the node that will become the parent of the new node. If a node is found that already has the same hashed name we need to return MA_ALREADY_EXISTS. */ + pCurrentNode = pResourceManager->pRootDataBufferNode; + while (pCurrentNode != NULL) { + if (hashedName32 == pCurrentNode->hashedName32) { + result = MA_ALREADY_EXISTS; + break; + } else { + if (hashedName32 < pCurrentNode->hashedName32) { + if (pCurrentNode->pChildLo == NULL) { + result = MA_SUCCESS; + break; + } else { + pCurrentNode = pCurrentNode->pChildLo; + } + } else { + if (pCurrentNode->pChildHi == NULL) { + result = MA_SUCCESS; + break; + } else { + pCurrentNode = pCurrentNode->pChildHi; + } + } + } + } + + *ppInsertPoint = pCurrentNode; + return result; +} + +static ma_result ma_resource_manager_data_buffer_node_insert_at(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, ma_resource_manager_data_buffer_node* pInsertPoint) +{ + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + + /* The key must have been set before calling this function. */ + MA_ASSERT(pDataBufferNode->hashedName32 != 0); + + if (pInsertPoint == NULL) { + /* It's the first node. */ + pResourceManager->pRootDataBufferNode = pDataBufferNode; + } else { + /* It's not the first node. It needs to be inserted. */ + if (pDataBufferNode->hashedName32 < pInsertPoint->hashedName32) { + MA_ASSERT(pInsertPoint->pChildLo == NULL); + pInsertPoint->pChildLo = pDataBufferNode; + } else { + MA_ASSERT(pInsertPoint->pChildHi == NULL); + pInsertPoint->pChildHi = pDataBufferNode; + } + } + + pDataBufferNode->pParent = pInsertPoint; + + return MA_SUCCESS; +} + +#if 0 /* Unused for now. */ +static ma_result ma_resource_manager_data_buffer_node_insert(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + ma_result result; + ma_resource_manager_data_buffer_node* pInsertPoint; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + + result = ma_resource_manager_data_buffer_node_insert_point(pResourceManager, pDataBufferNode->hashedName32, &pInsertPoint); + if (result != MA_SUCCESS) { + return MA_INVALID_ARGS; + } + + return ma_resource_manager_data_buffer_node_insert_at(pResourceManager, pDataBufferNode, pInsertPoint); +} +#endif + +static MA_INLINE ma_resource_manager_data_buffer_node* ma_resource_manager_data_buffer_node_find_min(ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + ma_resource_manager_data_buffer_node* pCurrentNode; + + MA_ASSERT(pDataBufferNode != NULL); + + pCurrentNode = pDataBufferNode; + while (pCurrentNode->pChildLo != NULL) { + pCurrentNode = pCurrentNode->pChildLo; + } + + return pCurrentNode; +} + +static MA_INLINE ma_resource_manager_data_buffer_node* ma_resource_manager_data_buffer_node_find_max(ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + ma_resource_manager_data_buffer_node* pCurrentNode; + + MA_ASSERT(pDataBufferNode != NULL); + + pCurrentNode = pDataBufferNode; + while (pCurrentNode->pChildHi != NULL) { + pCurrentNode = pCurrentNode->pChildHi; + } + + return pCurrentNode; +} + +static MA_INLINE ma_resource_manager_data_buffer_node* ma_resource_manager_data_buffer_node_find_inorder_successor(ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + MA_ASSERT(pDataBufferNode != NULL); + MA_ASSERT(pDataBufferNode->pChildHi != NULL); + + return ma_resource_manager_data_buffer_node_find_min(pDataBufferNode->pChildHi); +} + +static MA_INLINE ma_resource_manager_data_buffer_node* ma_resource_manager_data_buffer_node_find_inorder_predecessor(ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + MA_ASSERT(pDataBufferNode != NULL); + MA_ASSERT(pDataBufferNode->pChildLo != NULL); + + return ma_resource_manager_data_buffer_node_find_max(pDataBufferNode->pChildLo); +} + +static ma_result ma_resource_manager_data_buffer_node_remove(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + + if (pDataBufferNode->pChildLo == NULL) { + if (pDataBufferNode->pChildHi == NULL) { + /* Simple case - deleting a buffer with no children. */ + if (pDataBufferNode->pParent == NULL) { + MA_ASSERT(pResourceManager->pRootDataBufferNode == pDataBufferNode); /* There is only a single buffer in the tree which should be equal to the root node. */ + pResourceManager->pRootDataBufferNode = NULL; + } else { + if (pDataBufferNode->pParent->pChildLo == pDataBufferNode) { + pDataBufferNode->pParent->pChildLo = NULL; + } else { + pDataBufferNode->pParent->pChildHi = NULL; + } + } + } else { + /* Node has one child - pChildHi != NULL. */ + pDataBufferNode->pChildHi->pParent = pDataBufferNode->pParent; + + if (pDataBufferNode->pParent == NULL) { + MA_ASSERT(pResourceManager->pRootDataBufferNode == pDataBufferNode); + pResourceManager->pRootDataBufferNode = pDataBufferNode->pChildHi; + } else { + if (pDataBufferNode->pParent->pChildLo == pDataBufferNode) { + pDataBufferNode->pParent->pChildLo = pDataBufferNode->pChildHi; + } else { + pDataBufferNode->pParent->pChildHi = pDataBufferNode->pChildHi; + } + } + } + } else { + if (pDataBufferNode->pChildHi == NULL) { + /* Node has one child - pChildLo != NULL. */ + pDataBufferNode->pChildLo->pParent = pDataBufferNode->pParent; + + if (pDataBufferNode->pParent == NULL) { + MA_ASSERT(pResourceManager->pRootDataBufferNode == pDataBufferNode); + pResourceManager->pRootDataBufferNode = pDataBufferNode->pChildLo; + } else { + if (pDataBufferNode->pParent->pChildLo == pDataBufferNode) { + pDataBufferNode->pParent->pChildLo = pDataBufferNode->pChildLo; + } else { + pDataBufferNode->pParent->pChildHi = pDataBufferNode->pChildLo; + } + } + } else { + /* Complex case - deleting a node with two children. */ + ma_resource_manager_data_buffer_node* pReplacementDataBufferNode; + + /* For now we are just going to use the in-order successor as the replacement, but we may want to try to keep this balanced by switching between the two. */ + pReplacementDataBufferNode = ma_resource_manager_data_buffer_node_find_inorder_successor(pDataBufferNode); + MA_ASSERT(pReplacementDataBufferNode != NULL); + + /* + Now that we have our replacement node we can make the change. The simple way to do this would be to just exchange the values, and then remove the replacement + node, however we track specific nodes via pointers which means we can't just swap out the values. We need to instead just change the pointers around. The + replacement node should have at most 1 child. Therefore, we can detach it in terms of our simpler cases above. What we're essentially doing is detaching the + replacement node and reinserting it into the same position as the deleted node. + */ + MA_ASSERT(pReplacementDataBufferNode->pParent != NULL); /* The replacement node should never be the root which means it should always have a parent. */ + MA_ASSERT(pReplacementDataBufferNode->pChildLo == NULL); /* Because we used in-order successor. This would be pChildHi == NULL if we used in-order predecessor. */ + + if (pReplacementDataBufferNode->pChildHi == NULL) { + if (pReplacementDataBufferNode->pParent->pChildLo == pReplacementDataBufferNode) { + pReplacementDataBufferNode->pParent->pChildLo = NULL; + } else { + pReplacementDataBufferNode->pParent->pChildHi = NULL; + } + } else { + pReplacementDataBufferNode->pChildHi->pParent = pReplacementDataBufferNode->pParent; + if (pReplacementDataBufferNode->pParent->pChildLo == pReplacementDataBufferNode) { + pReplacementDataBufferNode->pParent->pChildLo = pReplacementDataBufferNode->pChildHi; + } else { + pReplacementDataBufferNode->pParent->pChildHi = pReplacementDataBufferNode->pChildHi; + } + } + + + /* The replacement node has essentially been detached from the binary tree, so now we need to replace the old data buffer with it. The first thing to update is the parent */ + if (pDataBufferNode->pParent != NULL) { + if (pDataBufferNode->pParent->pChildLo == pDataBufferNode) { + pDataBufferNode->pParent->pChildLo = pReplacementDataBufferNode; + } else { + pDataBufferNode->pParent->pChildHi = pReplacementDataBufferNode; + } + } + + /* Now need to update the replacement node's pointers. */ + pReplacementDataBufferNode->pParent = pDataBufferNode->pParent; + pReplacementDataBufferNode->pChildLo = pDataBufferNode->pChildLo; + pReplacementDataBufferNode->pChildHi = pDataBufferNode->pChildHi; + + /* Now the children of the replacement node need to have their parent pointers updated. */ + if (pReplacementDataBufferNode->pChildLo != NULL) { + pReplacementDataBufferNode->pChildLo->pParent = pReplacementDataBufferNode; + } + if (pReplacementDataBufferNode->pChildHi != NULL) { + pReplacementDataBufferNode->pChildHi->pParent = pReplacementDataBufferNode; + } + + /* Now the root node needs to be updated. */ + if (pResourceManager->pRootDataBufferNode == pDataBufferNode) { + pResourceManager->pRootDataBufferNode = pReplacementDataBufferNode; + } + } + } + + return MA_SUCCESS; +} + +#if 0 /* Unused for now. */ +static ma_result ma_resource_manager_data_buffer_node_remove_by_key(ma_resource_manager* pResourceManager, ma_uint32 hashedName32) +{ + ma_result result; + ma_resource_manager_data_buffer_node* pDataBufferNode; + + result = ma_resource_manager_data_buffer_search(pResourceManager, hashedName32, &pDataBufferNode); + if (result != MA_SUCCESS) { + return result; /* Could not find the data buffer. */ + } + + return ma_resource_manager_data_buffer_remove(pResourceManager, pDataBufferNode); +} +#endif + +static ma_resource_manager_data_supply_type ma_resource_manager_data_buffer_node_get_data_supply_type(ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + return (ma_resource_manager_data_supply_type)c89atomic_load_i32(&pDataBufferNode->data.type); +} + +static void ma_resource_manager_data_buffer_node_set_data_supply_type(ma_resource_manager_data_buffer_node* pDataBufferNode, ma_resource_manager_data_supply_type supplyType) +{ + c89atomic_exchange_i32(&pDataBufferNode->data.type, supplyType); +} + +static ma_result ma_resource_manager_data_buffer_node_increment_ref(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, ma_uint32* pNewRefCount) +{ + ma_uint32 refCount; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + + (void)pResourceManager; + + refCount = c89atomic_fetch_add_32(&pDataBufferNode->refCount, 1) + 1; + + if (pNewRefCount != NULL) { + *pNewRefCount = refCount; + } + + return MA_SUCCESS; +} + +static ma_result ma_resource_manager_data_buffer_node_decrement_ref(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, ma_uint32* pNewRefCount) +{ + ma_uint32 refCount; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + + (void)pResourceManager; + + refCount = c89atomic_fetch_sub_32(&pDataBufferNode->refCount, 1) - 1; + + if (pNewRefCount != NULL) { + *pNewRefCount = refCount; + } + + return MA_SUCCESS; +} + +static void ma_resource_manager_data_buffer_node_free(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + + if (pDataBufferNode->isDataOwnedByResourceManager) { + if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBufferNode) == ma_resource_manager_data_supply_type_encoded) { + ma_free((void*)pDataBufferNode->data.backend.encoded.pData, &pResourceManager->config.allocationCallbacks); + pDataBufferNode->data.backend.encoded.pData = NULL; + pDataBufferNode->data.backend.encoded.sizeInBytes = 0; + } else if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBufferNode) == ma_resource_manager_data_supply_type_decoded) { + ma_free((void*)pDataBufferNode->data.backend.decoded.pData, &pResourceManager->config.allocationCallbacks); + pDataBufferNode->data.backend.decoded.pData = NULL; + pDataBufferNode->data.backend.decoded.totalFrameCount = 0; + } else if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBufferNode) == ma_resource_manager_data_supply_type_decoded_paged) { + ma_paged_audio_buffer_data_uninit(&pDataBufferNode->data.backend.decodedPaged.data, &pResourceManager->config.allocationCallbacks); + } else { + /* Should never hit this if the node was successfully initialized. */ + MA_ASSERT(pDataBufferNode->result != MA_SUCCESS); + } + } + + /* The data buffer itself needs to be freed. */ + ma_free(pDataBufferNode, &pResourceManager->config.allocationCallbacks); +} + +static ma_result ma_resource_manager_data_buffer_node_result(const ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + MA_ASSERT(pDataBufferNode != NULL); + + return c89atomic_load_i32((ma_result*)&pDataBufferNode->result); /* Need a naughty const-cast here. */ +} + + +static ma_bool32 ma_resource_manager_is_threading_enabled(const ma_resource_manager* pResourceManager) +{ + MA_ASSERT(pResourceManager != NULL); + + return (pResourceManager->config.flags & MA_RESOURCE_MANAGER_FLAG_NO_THREADING) == 0; +} + + +typedef struct +{ + union + { + ma_async_notification_event e; + ma_async_notification_poll p; + } backend; /* Must be the first member. */ + ma_resource_manager* pResourceManager; +} ma_resource_manager_inline_notification; + +static ma_result ma_resource_manager_inline_notification_init(ma_resource_manager* pResourceManager, ma_resource_manager_inline_notification* pNotification) +{ + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pNotification != NULL); + + pNotification->pResourceManager = pResourceManager; + + if (ma_resource_manager_is_threading_enabled(pResourceManager)) { + return ma_async_notification_event_init(&pNotification->backend.e); + } else { + return ma_async_notification_poll_init(&pNotification->backend.p); + } +} + +static void ma_resource_manager_inline_notification_uninit(ma_resource_manager_inline_notification* pNotification) +{ + MA_ASSERT(pNotification != NULL); + + if (ma_resource_manager_is_threading_enabled(pNotification->pResourceManager)) { + ma_async_notification_event_uninit(&pNotification->backend.e); + } else { + /* No need to uninitialize a polling notification. */ + } +} + +static void ma_resource_manager_inline_notification_wait(ma_resource_manager_inline_notification* pNotification) +{ + MA_ASSERT(pNotification != NULL); + + if (ma_resource_manager_is_threading_enabled(pNotification->pResourceManager)) { + ma_async_notification_event_wait(&pNotification->backend.e); + } else { + while (ma_async_notification_poll_is_signalled(&pNotification->backend.p) == MA_FALSE) { + ma_result result = ma_resource_manager_process_next_job(pNotification->pResourceManager); + if (result == MA_NO_DATA_AVAILABLE || result == MA_RESOURCE_MANAGER_JOB_QUIT) { + break; + } + } + } +} + +static void ma_resource_manager_inline_notification_wait_and_uninit(ma_resource_manager_inline_notification* pNotification) +{ + ma_resource_manager_inline_notification_wait(pNotification); + ma_resource_manager_inline_notification_uninit(pNotification); +} + + +static void ma_resource_manager_data_buffer_bst_lock(ma_resource_manager* pResourceManager) +{ + MA_ASSERT(pResourceManager != NULL); + + if (ma_resource_manager_is_threading_enabled(pResourceManager)) { + #ifndef MA_NO_THREADING + { + ma_mutex_lock(&pResourceManager->dataBufferBSTLock); + } + #else + { + MA_ASSERT(MA_FALSE); /* Should never hit this. */ + } + #endif + } else { + /* Threading not enabled. Do nothing. */ + } +} + +static void ma_resource_manager_data_buffer_bst_unlock(ma_resource_manager* pResourceManager) +{ + MA_ASSERT(pResourceManager != NULL); + + if (ma_resource_manager_is_threading_enabled(pResourceManager)) { + #ifndef MA_NO_THREADING + { + ma_mutex_unlock(&pResourceManager->dataBufferBSTLock); + } + #else + { + MA_ASSERT(MA_FALSE); /* Should never hit this. */ + } + #endif + } else { + /* Threading not enabled. Do nothing. */ + } +} + +#ifndef MA_NO_THREADING +static ma_thread_result MA_THREADCALL ma_resource_manager_job_thread(void* pUserData) +{ + ma_resource_manager* pResourceManager = (ma_resource_manager*)pUserData; + MA_ASSERT(pResourceManager != NULL); + + for (;;) { + ma_result result; + ma_resource_manager_job job; + + result = ma_resource_manager_next_job(pResourceManager, &job); + if (result != MA_SUCCESS) { + break; + } + + /* Terminate if we got a quit message. */ + if (job.toc.breakup.code == MA_RESOURCE_MANAGER_JOB_QUIT) { + break; + } + + ma_resource_manager_process_job(pResourceManager, &job); + } + + return (ma_thread_result)0; +} +#endif + +MA_API ma_resource_manager_config ma_resource_manager_config_init(void) +{ + ma_resource_manager_config config; + + MA_ZERO_OBJECT(&config); + config.decodedFormat = ma_format_unknown; + config.decodedChannels = 0; + config.decodedSampleRate = 0; + config.jobThreadCount = 1; /* A single miniaudio-managed job thread by default. */ + config.jobQueueCapacity = MA_RESOURCE_MANAGER_JOB_QUEUE_CAPACITY; + + /* Flags. */ + config.flags = 0; + #ifdef MA_NO_THREADING + { + /* Threading is disabled at compile time so disable threading at runtime as well by default. */ + config.flags |= MA_RESOURCE_MANAGER_FLAG_NO_THREADING; + config.jobThreadCount = 0; + } + #endif + + return config; +} + + +MA_API ma_result ma_resource_manager_init(const ma_resource_manager_config* pConfig, ma_resource_manager* pResourceManager) +{ + ma_result result; + ma_resource_manager_job_queue_config jobQueueConfig; + + if (pResourceManager == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pResourceManager); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + #ifndef MA_NO_THREADING + { + if (pConfig->jobThreadCount > ma_countof(pResourceManager->jobThreads)) { + return MA_INVALID_ARGS; /* Requesting too many job threads. */ + } + } + #endif + + pResourceManager->config = *pConfig; + ma_allocation_callbacks_init_copy(&pResourceManager->config.allocationCallbacks, &pConfig->allocationCallbacks); + + /* Get the log set up early so we can start using it as soon as possible. */ + if (pResourceManager->config.pLog == NULL) { + result = ma_log_init(&pResourceManager->config.allocationCallbacks, &pResourceManager->log); + if (result == MA_SUCCESS) { + pResourceManager->config.pLog = &pResourceManager->log; + } else { + pResourceManager->config.pLog = NULL; /* Logging is unavailable. */ + } + } + + if (pResourceManager->config.pVFS == NULL) { + result = ma_default_vfs_init(&pResourceManager->defaultVFS, &pResourceManager->config.allocationCallbacks); + if (result != MA_SUCCESS) { + return result; /* Failed to initialize the default file system. */ + } + + pResourceManager->config.pVFS = &pResourceManager->defaultVFS; + } + + /* If threading has been disabled at compile time, enfore it at run time as well. */ + #ifdef MA_NO_THREADING + { + pResourceManager->config.flags |= MA_RESOURCE_MANAGER_FLAG_NO_THREADING; + } + #endif + + /* We need to force MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING if MA_RESOURCE_MANAGER_FLAG_NO_THREADING is set. */ + if ((pResourceManager->config.flags & MA_RESOURCE_MANAGER_FLAG_NO_THREADING) != 0) { + pResourceManager->config.flags |= MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING; + + /* We cannot allow job threads when MA_RESOURCE_MANAGER_FLAG_NO_THREADING has been set. This is an invalid use case. */ + if (pResourceManager->config.jobThreadCount > 0) { + return MA_INVALID_ARGS; + } + } + + /* Job queue. */ + jobQueueConfig.capacity = pResourceManager->config.jobQueueCapacity; + jobQueueConfig.flags = 0; + if ((pResourceManager->config.flags & MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING) != 0) { + if (pResourceManager->config.jobThreadCount > 0) { + return MA_INVALID_ARGS; /* Non-blocking mode is only valid for self-managed job threads. */ + } + + jobQueueConfig.flags |= MA_RESOURCE_MANAGER_JOB_QUEUE_FLAG_NON_BLOCKING; + } + + result = ma_resource_manager_job_queue_init(&jobQueueConfig, &pResourceManager->config.allocationCallbacks, &pResourceManager->jobQueue); + if (result != MA_SUCCESS) { + return result; + } + + + /* Custom decoding backends. */ + if (pConfig->ppCustomDecodingBackendVTables != NULL && pConfig->customDecodingBackendCount > 0) { + size_t sizeInBytes = sizeof(*pResourceManager->config.ppCustomDecodingBackendVTables) * pConfig->customDecodingBackendCount; + + pResourceManager->config.ppCustomDecodingBackendVTables = (ma_decoding_backend_vtable**)ma_malloc(sizeInBytes, &pResourceManager->config.allocationCallbacks); + if (pResourceManager->config.ppCustomDecodingBackendVTables == NULL) { + ma_resource_manager_job_queue_uninit(&pResourceManager->jobQueue, &pResourceManager->config.allocationCallbacks); + return MA_OUT_OF_MEMORY; + } + + MA_COPY_MEMORY(pResourceManager->config.ppCustomDecodingBackendVTables, pConfig->ppCustomDecodingBackendVTables, sizeInBytes); + + pResourceManager->config.customDecodingBackendCount = pConfig->customDecodingBackendCount; + pResourceManager->config.pCustomDecodingBackendUserData = pConfig->pCustomDecodingBackendUserData; + } + + + + /* Here is where we initialize our threading stuff. We don't do this if we don't support threading. */ + if (ma_resource_manager_is_threading_enabled(pResourceManager)) { + #ifndef MA_NO_THREADING + { + ma_uint32 iJobThread; + + /* Data buffer lock. */ + result = ma_mutex_init(&pResourceManager->dataBufferBSTLock); + if (result != MA_SUCCESS) { + ma_resource_manager_job_queue_uninit(&pResourceManager->jobQueue, &pResourceManager->config.allocationCallbacks); + return result; + } + + /* Create the job threads last to ensure the threads has access to valid data. */ + for (iJobThread = 0; iJobThread < pResourceManager->config.jobThreadCount; iJobThread += 1) { + result = ma_thread_create(&pResourceManager->jobThreads[iJobThread], ma_thread_priority_normal, 0, ma_resource_manager_job_thread, pResourceManager, &pResourceManager->config.allocationCallbacks); + if (result != MA_SUCCESS) { + ma_mutex_uninit(&pResourceManager->dataBufferBSTLock); + ma_resource_manager_job_queue_uninit(&pResourceManager->jobQueue, &pResourceManager->config.allocationCallbacks); + return result; + } + } + } + #else + { + /* Threading is disabled at compile time. We should never get here because validation checks should have already been performed. */ + MA_ASSERT(MA_FALSE); + } + #endif + } + + return MA_SUCCESS; +} + + +static void ma_resource_manager_delete_all_data_buffer_nodes(ma_resource_manager* pResourceManager) +{ + MA_ASSERT(pResourceManager); + + /* If everything was done properly, there shouldn't be any active data buffers. */ + while (pResourceManager->pRootDataBufferNode != NULL) { + ma_resource_manager_data_buffer_node* pDataBufferNode = pResourceManager->pRootDataBufferNode; + ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode); + + /* The data buffer has been removed from the BST, so now we need to free it's data. */ + ma_resource_manager_data_buffer_node_free(pResourceManager, pDataBufferNode); + } +} + +MA_API void ma_resource_manager_uninit(ma_resource_manager* pResourceManager) +{ + if (pResourceManager == NULL) { + return; + } + + /* + Job threads need to be killed first. To do this we need to post a quit message to the message queue and then wait for the thread. The quit message will never be removed from the + queue which means it will never not be returned after being encounted for the first time which means all threads will eventually receive it. + */ + ma_resource_manager_post_job_quit(pResourceManager); + + /* Wait for every job to finish before continuing to ensure nothing is sill trying to access any of our objects below. */ + if (ma_resource_manager_is_threading_enabled(pResourceManager)) { + #ifndef MA_NO_THREADING + { + ma_uint32 iJobThread; + + for (iJobThread = 0; iJobThread < pResourceManager->config.jobThreadCount; iJobThread += 1) { + ma_thread_wait(&pResourceManager->jobThreads[iJobThread]); + } + } + #else + { + MA_ASSERT(MA_FALSE); /* Should never hit this. */ + } + #endif + } + + /* At this point the thread should have returned and no other thread should be accessing our data. We can now delete all data buffers. */ + ma_resource_manager_delete_all_data_buffer_nodes(pResourceManager); + + /* The job queue is no longer needed. */ + ma_resource_manager_job_queue_uninit(&pResourceManager->jobQueue, &pResourceManager->config.allocationCallbacks); + + /* We're no longer doing anything with data buffers so the lock can now be uninitialized. */ + if (ma_resource_manager_is_threading_enabled(pResourceManager)) { + #ifndef MA_NO_THREADING + { + ma_mutex_uninit(&pResourceManager->dataBufferBSTLock); + } + #else + { + MA_ASSERT(MA_FALSE); /* Should never hit this. */ + } + #endif + } + + ma_free(pResourceManager->config.ppCustomDecodingBackendVTables, &pResourceManager->config.allocationCallbacks); + + if (pResourceManager->config.pLog == &pResourceManager->log) { + ma_log_uninit(&pResourceManager->log); + } +} + +MA_API ma_log* ma_resource_manager_get_log(ma_resource_manager* pResourceManager) +{ + if (pResourceManager == NULL) { + return NULL; + } + + return pResourceManager->config.pLog; +} + + + +MA_API ma_resource_manager_data_source_config ma_resource_manager_data_source_config_init() +{ + ma_resource_manager_data_source_config config; + + MA_ZERO_OBJECT(&config); + config.rangeEndInPCMFrames = ~((ma_uint64)0); + config.loopPointEndInPCMFrames = ~((ma_uint64)0); + + return config; +} + + +static ma_decoder_config ma_resource_manager__init_decoder_config(ma_resource_manager* pResourceManager) +{ + ma_decoder_config config; + + config = ma_decoder_config_init(pResourceManager->config.decodedFormat, pResourceManager->config.decodedChannels, pResourceManager->config.decodedSampleRate); + config.allocationCallbacks = pResourceManager->config.allocationCallbacks; + config.ppCustomBackendVTables = pResourceManager->config.ppCustomDecodingBackendVTables; + config.customBackendCount = pResourceManager->config.customDecodingBackendCount; + config.pCustomBackendUserData = pResourceManager->config.pCustomDecodingBackendUserData; + + return config; +} + +static ma_result ma_resource_manager__init_decoder(ma_resource_manager* pResourceManager, const char* pFilePath, const wchar_t* pFilePathW, ma_decoder* pDecoder) +{ + ma_result result; + ma_decoder_config config; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pFilePath != NULL || pFilePathW != NULL); + MA_ASSERT(pDecoder != NULL); + + config = ma_resource_manager__init_decoder_config(pResourceManager); + + if (pFilePath != NULL) { + result = ma_decoder_init_vfs(pResourceManager->config.pVFS, pFilePath, &config, pDecoder); + if (result != MA_SUCCESS) { + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%s\". %s.\n", pFilePath, ma_result_description(result)); + return result; + } + } else { + result = ma_decoder_init_vfs_w(pResourceManager->config.pVFS, pFilePathW, &config, pDecoder); + if (result != MA_SUCCESS) { + #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(_MSC_VER) + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%ls\". %s.\n", pFilePathW, ma_result_description(result)); + #endif + return result; + } + } + + return MA_SUCCESS; +} + +static ma_data_source* ma_resource_manager_data_buffer_get_connector(ma_resource_manager_data_buffer* pDataBuffer) +{ + switch (pDataBuffer->pNode->data.type) + { + case ma_resource_manager_data_supply_type_encoded: return &pDataBuffer->connector.decoder; + case ma_resource_manager_data_supply_type_decoded: return &pDataBuffer->connector.buffer; + case ma_resource_manager_data_supply_type_decoded_paged: return &pDataBuffer->connector.pagedBuffer; + + case ma_resource_manager_data_supply_type_unknown: + default: + { + ma_log_postf(ma_resource_manager_get_log(pDataBuffer->pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to retrieve data buffer connector. Unknown data supply type.\n"); + return NULL; + }; + }; +} + +static ma_result ma_resource_manager_data_buffer_init_connector(ma_resource_manager_data_buffer* pDataBuffer, ma_async_notification* pInitNotification, ma_fence* pInitFence) +{ + ma_result result; + + MA_ASSERT(pDataBuffer != NULL); + MA_ASSERT(pDataBuffer->isConnectorInitialized == MA_FALSE); + + /* The underlying data buffer must be initialized before we'll be able to know how to initialize the backend. */ + result = ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode); + if (result != MA_SUCCESS && result != MA_BUSY) { + return result; /* The data buffer is in an erroneous state. */ + } + + /* + We need to initialize either a ma_decoder or an ma_audio_buffer depending on whether or not the backing data is encoded or decoded. These act as the + "instance" to the data and are used to form the connection between underlying data buffer and the data source. If the data buffer is decoded, we can use + an ma_audio_buffer. This enables us to use memory mapping when mixing which saves us a bit of data movement overhead. + */ + switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode)) + { + case ma_resource_manager_data_supply_type_encoded: /* Connector is a decoder. */ + { + ma_decoder_config config; + config = ma_resource_manager__init_decoder_config(pDataBuffer->pResourceManager); + result = ma_decoder_init_memory(pDataBuffer->pNode->data.backend.encoded.pData, pDataBuffer->pNode->data.backend.encoded.sizeInBytes, &config, &pDataBuffer->connector.decoder); + } break; + + case ma_resource_manager_data_supply_type_decoded: /* Connector is an audio buffer. */ + { + ma_audio_buffer_config config; + config = ma_audio_buffer_config_init(pDataBuffer->pNode->data.backend.decoded.format, pDataBuffer->pNode->data.backend.decoded.channels, pDataBuffer->pNode->data.backend.decoded.totalFrameCount, pDataBuffer->pNode->data.backend.decoded.pData, NULL); + result = ma_audio_buffer_init(&config, &pDataBuffer->connector.buffer); + } break; + + case ma_resource_manager_data_supply_type_decoded_paged: /* Connector is a paged audio buffer. */ + { + ma_paged_audio_buffer_config config; + config = ma_paged_audio_buffer_config_init(&pDataBuffer->pNode->data.backend.decodedPaged.data); + result = ma_paged_audio_buffer_init(&config, &pDataBuffer->connector.pagedBuffer); + } break; + + case ma_resource_manager_data_supply_type_unknown: + default: + { + /* Unknown data supply type. Should never happen. Need to post an error here. */ + return MA_INVALID_ARGS; + }; + } + + /* + Initialization of the connector is when we can fire the init notification. This will give the application access to + the format/channels/rate of the data source. + */ + if (result == MA_SUCCESS) { + /* + Make sure the looping state is set before returning in order to handle the case where the + loop state was set on the data buffer before the connector was initialized. + */ + ma_data_source_set_looping(ma_resource_manager_data_buffer_get_connector(pDataBuffer), ma_resource_manager_data_buffer_is_looping(pDataBuffer)); + + pDataBuffer->isConnectorInitialized = MA_TRUE; + + if (pInitNotification != NULL) { + ma_async_notification_signal(pInitNotification); + } + + if (pInitFence != NULL) { + ma_fence_release(pInitFence); + } + } + + /* At this point the backend should be initialized. We do *not* want to set pDataSource->result here - that needs to be done at a higher level to ensure it's done as the last step. */ + return result; +} + +static ma_result ma_resource_manager_data_buffer_uninit_connector(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer* pDataBuffer) +{ + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBuffer != NULL); + + switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode)) + { + case ma_resource_manager_data_supply_type_encoded: /* Connector is a decoder. */ + { + ma_decoder_uninit(&pDataBuffer->connector.decoder); + } break; + + case ma_resource_manager_data_supply_type_decoded: /* Connector is an audio buffer. */ + { + ma_audio_buffer_uninit(&pDataBuffer->connector.buffer); + } break; + + case ma_resource_manager_data_supply_type_decoded_paged: /* Connector is a paged audio buffer. */ + { + ma_paged_audio_buffer_uninit(&pDataBuffer->connector.pagedBuffer); + } break; + + case ma_resource_manager_data_supply_type_unknown: + default: + { + /* Unknown data supply type. Should never happen. Need to post an error here. */ + return MA_INVALID_ARGS; + }; + } + + return MA_SUCCESS; +} + +static ma_uint32 ma_resource_manager_data_buffer_node_next_execution_order(ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + MA_ASSERT(pDataBufferNode != NULL); + return c89atomic_fetch_add_32(&pDataBufferNode->executionCounter, 1); +} + +static ma_result ma_resource_manager_data_buffer_node_init_supply_encoded(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, const char* pFilePath, const wchar_t* pFilePathW) +{ + ma_result result; + size_t dataSizeInBytes; + void* pData; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + MA_ASSERT(pFilePath != NULL || pFilePathW != NULL); + + result = ma_vfs_open_and_read_file_ex(pResourceManager->config.pVFS, pFilePath, pFilePathW, &pData, &dataSizeInBytes, &pResourceManager->config.allocationCallbacks); + if (result != MA_SUCCESS) { + if (pFilePath != NULL) { + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%s\". %s.\n", pFilePath, ma_result_description(result)); + } else { + #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(_MSC_VER) + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%ls\". %s.\n", pFilePathW, ma_result_description(result)); + #endif + } + + return result; + } + + pDataBufferNode->data.backend.encoded.pData = pData; + pDataBufferNode->data.backend.encoded.sizeInBytes = dataSizeInBytes; + ma_resource_manager_data_buffer_node_set_data_supply_type(pDataBufferNode, ma_resource_manager_data_supply_type_encoded); /* <-- Must be set last. */ + + return MA_SUCCESS; +} + +static ma_result ma_resource_manager_data_buffer_node_init_supply_decoded(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, const char* pFilePath, const wchar_t* pFilePathW, ma_decoder** ppDecoder) +{ + ma_result result = MA_SUCCESS; + ma_decoder* pDecoder; + ma_uint64 totalFrameCount; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + MA_ASSERT(ppDecoder != NULL); + MA_ASSERT(pFilePath != NULL || pFilePathW != NULL); + + *ppDecoder = NULL; /* For safety. */ + + pDecoder = (ma_decoder*)ma_malloc(sizeof(*pDecoder), &pResourceManager->config.allocationCallbacks); + if (pDecoder == NULL) { + return MA_OUT_OF_MEMORY; + } + + result = ma_resource_manager__init_decoder(pResourceManager, pFilePath, pFilePathW, pDecoder); + if (result != MA_SUCCESS) { + ma_free(pDecoder, &pResourceManager->config.allocationCallbacks); + return result; + } + + /* + At this point we have the decoder and we now need to initialize the data supply. This will + be either a decoded buffer, or a decoded paged buffer. A regular buffer is just one big heap + allocated buffer, whereas a paged buffer is a linked list of paged-sized buffers. The latter + is used when the length of a sound is unknown until a full decode has been performed. + */ + result = ma_decoder_get_length_in_pcm_frames(pDecoder, &totalFrameCount); + if (result != MA_SUCCESS) { + return result; + } + + if (totalFrameCount > 0) { + /* It's a known length. The data supply is a regular decoded buffer. */ + ma_uint64 dataSizeInBytes; + void* pData; + + dataSizeInBytes = totalFrameCount * ma_get_bytes_per_frame(pDecoder->outputFormat, pDecoder->outputChannels); + if (dataSizeInBytes > MA_SIZE_MAX) { + ma_decoder_uninit(pDecoder); + ma_free(pDecoder, &pResourceManager->config.allocationCallbacks); + return MA_TOO_BIG; + } + + pData = ma_malloc((size_t)dataSizeInBytes, &pResourceManager->config.allocationCallbacks); + if (pData == NULL) { + ma_decoder_uninit(pDecoder); + ma_free(pDecoder, &pResourceManager->config.allocationCallbacks); + return MA_OUT_OF_MEMORY; + } + + /* The buffer needs to be initialized to silence in case the caller reads from it. */ + ma_silence_pcm_frames(pData, totalFrameCount, pDecoder->outputFormat, pDecoder->outputChannels); + + /* Data has been allocated and the data supply can now be initialized. */ + pDataBufferNode->data.backend.decoded.pData = pData; + pDataBufferNode->data.backend.decoded.totalFrameCount = totalFrameCount; + pDataBufferNode->data.backend.decoded.format = pDecoder->outputFormat; + pDataBufferNode->data.backend.decoded.channels = pDecoder->outputChannels; + pDataBufferNode->data.backend.decoded.sampleRate = pDecoder->outputSampleRate; + pDataBufferNode->data.backend.decoded.decodedFrameCount = 0; + ma_resource_manager_data_buffer_node_set_data_supply_type(pDataBufferNode, ma_resource_manager_data_supply_type_decoded); /* <-- Must be set last. */ + } else { + /* + It's an unknown length. The data supply is a paged decoded buffer. Setting this up is + actually easier than the non-paged decoded buffer because we just need to initialize + a ma_paged_audio_buffer object. + */ + result = ma_paged_audio_buffer_data_init(pDecoder->outputFormat, pDecoder->outputChannels, &pDataBufferNode->data.backend.decodedPaged.data); + if (result != MA_SUCCESS) { + ma_decoder_uninit(pDecoder); + ma_free(pDecoder, &pResourceManager->config.allocationCallbacks); + return result; + } + + pDataBufferNode->data.backend.decodedPaged.sampleRate = pDecoder->outputSampleRate; + pDataBufferNode->data.backend.decodedPaged.decodedFrameCount = 0; + ma_resource_manager_data_buffer_node_set_data_supply_type(pDataBufferNode, ma_resource_manager_data_supply_type_decoded_paged); /* <-- Must be set last. */ + } + + *ppDecoder = pDecoder; + + return MA_SUCCESS; +} + +static ma_result ma_resource_manager_data_buffer_node_decode_next_page(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, ma_decoder* pDecoder) +{ + ma_result result = MA_SUCCESS; + ma_uint64 pageSizeInFrames; + ma_uint64 framesToTryReading; + ma_uint64 framesRead; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + MA_ASSERT(pDecoder != NULL); + + /* We need to know the size of a page in frames to know how many frames to decode. */ + pageSizeInFrames = MA_RESOURCE_MANAGER_PAGE_SIZE_IN_MILLISECONDS * (pDecoder->outputSampleRate/1000); + framesToTryReading = pageSizeInFrames; + + /* + Here is where we do the decoding of the next page. We'll run a slightly different path depending + on whether or not we're using a flat or paged buffer because the allocation of the page differs + between the two. For a flat buffer it's an offset to an already-allocated buffer. For a paged + buffer, we need to allocate a new page and attach it to the linked list. + */ + switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBufferNode)) + { + case ma_resource_manager_data_supply_type_decoded: + { + /* The destination buffer is an offset to the existing buffer. Don't read more than we originally retrieved when we first initialized the decoder. */ + void* pDst; + ma_uint64 framesRemaining = pDataBufferNode->data.backend.decoded.totalFrameCount - pDataBufferNode->data.backend.decoded.decodedFrameCount; + if (framesToTryReading > framesRemaining) { + framesToTryReading = framesRemaining; + } + + if (framesToTryReading > 0) { + pDst = ma_offset_ptr( + pDataBufferNode->data.backend.decoded.pData, + pDataBufferNode->data.backend.decoded.decodedFrameCount * ma_get_bytes_per_frame(pDataBufferNode->data.backend.decoded.format, pDataBufferNode->data.backend.decoded.channels) + ); + MA_ASSERT(pDst != NULL); + + result = ma_decoder_read_pcm_frames(pDecoder, pDst, framesToTryReading, &framesRead); + if (framesRead > 0) { + pDataBufferNode->data.backend.decoded.decodedFrameCount += framesRead; + } + } else { + framesRead = 0; + } + } break; + + case ma_resource_manager_data_supply_type_decoded_paged: + { + /* The destination buffer is a freshly allocated page. */ + ma_paged_audio_buffer_page* pPage; + + result = ma_paged_audio_buffer_data_allocate_page(&pDataBufferNode->data.backend.decodedPaged.data, framesToTryReading, NULL, &pResourceManager->config.allocationCallbacks, &pPage); + if (result != MA_SUCCESS) { + return result; + } + + result = ma_decoder_read_pcm_frames(pDecoder, pPage->pAudioData, framesToTryReading, &framesRead); + if (framesRead > 0) { + pPage->sizeInFrames = framesRead; + + result = ma_paged_audio_buffer_data_append_page(&pDataBufferNode->data.backend.decodedPaged.data, pPage); + if (result == MA_SUCCESS) { + pDataBufferNode->data.backend.decodedPaged.decodedFrameCount += framesRead; + } else { + /* Failed to append the page. Just abort and set the status to MA_AT_END. */ + ma_paged_audio_buffer_data_free_page(&pDataBufferNode->data.backend.decodedPaged.data, pPage, &pResourceManager->config.allocationCallbacks); + result = MA_AT_END; + } + } else { + /* No frames were read. Free the page and just set the status to MA_AT_END. */ + ma_paged_audio_buffer_data_free_page(&pDataBufferNode->data.backend.decodedPaged.data, pPage, &pResourceManager->config.allocationCallbacks); + result = MA_AT_END; + } + } break; + + case ma_resource_manager_data_supply_type_encoded: + case ma_resource_manager_data_supply_type_unknown: + default: + { + /* Unexpected data supply type. */ + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Unexpected data supply type (%d) when decoding page.", ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBufferNode)); + return MA_ERROR; + }; + } + + if (result == MA_SUCCESS && framesRead == 0) { + result = MA_AT_END; + } + + return result; +} + +static ma_result ma_resource_manager_data_buffer_node_acquire_critical_section(ma_resource_manager* pResourceManager, const char* pFilePath, const wchar_t* pFilePathW, ma_uint32 hashedName32, ma_uint32 flags, const ma_resource_manager_data_supply* pExistingData, ma_fence* pInitFence, ma_fence* pDoneFence, ma_resource_manager_inline_notification* pInitNotification, ma_resource_manager_data_buffer_node** ppDataBufferNode) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager_data_buffer_node* pDataBufferNode = NULL; + ma_resource_manager_data_buffer_node* pInsertPoint; + + if (ppDataBufferNode != NULL) { + *ppDataBufferNode = NULL; + } + + result = ma_resource_manager_data_buffer_node_insert_point(pResourceManager, hashedName32, &pInsertPoint); + if (result == MA_ALREADY_EXISTS) { + /* The node already exists. We just need to increment the reference count. */ + pDataBufferNode = pInsertPoint; + + result = ma_resource_manager_data_buffer_node_increment_ref(pResourceManager, pDataBufferNode, NULL); + if (result != MA_SUCCESS) { + return result; /* Should never happen. Failed to increment the reference count. */ + } + + result = MA_ALREADY_EXISTS; + goto done; + } else { + /* + The node does not already exist. We need to post a LOAD_DATA_BUFFER_NODE job here. This + needs to be done inside the critical section to ensure an uninitialization of the node + does not occur before initialization on another thread. + */ + pDataBufferNode = (ma_resource_manager_data_buffer_node*)ma_malloc(sizeof(*pDataBufferNode), &pResourceManager->config.allocationCallbacks); + if (pDataBufferNode == NULL) { + return MA_OUT_OF_MEMORY; + } + + MA_ZERO_OBJECT(pDataBufferNode); + pDataBufferNode->hashedName32 = hashedName32; + pDataBufferNode->refCount = 1; /* Always set to 1 by default (this is our first reference). */ + + if (pExistingData == NULL) { + pDataBufferNode->data.type = ma_resource_manager_data_supply_type_unknown; /* <-- We won't know this until we start decoding. */ + pDataBufferNode->result = MA_BUSY; /* Must be set to MA_BUSY before we leave the critical section, so might as well do it now. */ + pDataBufferNode->isDataOwnedByResourceManager = MA_TRUE; + } else { + pDataBufferNode->data = *pExistingData; + pDataBufferNode->result = MA_SUCCESS; /* Not loading asynchronously, so just set the status */ + pDataBufferNode->isDataOwnedByResourceManager = MA_FALSE; + } + + result = ma_resource_manager_data_buffer_node_insert_at(pResourceManager, pDataBufferNode, pInsertPoint); + if (result != MA_SUCCESS) { + ma_free(pDataBufferNode, &pResourceManager->config.allocationCallbacks); + return result; /* Should never happen. Failed to insert the data buffer into the BST. */ + } + + /* + Here is where we'll post the job, but only if we're loading asynchronously. If we're + loading synchronously we'll defer loading to a later stage, outside of the critical + section. + */ + if (pDataBufferNode->isDataOwnedByResourceManager && (flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC) != 0) { + /* Loading asynchronously. Post the job. */ + ma_resource_manager_job job; + char* pFilePathCopy = NULL; + wchar_t* pFilePathWCopy = NULL; + + /* We need a copy of the file path. We should probably make this more efficient, but for now we'll do a transient memory allocation. */ + if (pFilePath != NULL) { + pFilePathCopy = ma_copy_string(pFilePath, &pResourceManager->config.allocationCallbacks); + } else { + pFilePathWCopy = ma_copy_string_w(pFilePathW, &pResourceManager->config.allocationCallbacks); + } + + if (pFilePathCopy == NULL && pFilePathWCopy == NULL) { + ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode); + ma_free(pDataBufferNode, &pResourceManager->config.allocationCallbacks); + return MA_OUT_OF_MEMORY; + } + + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + ma_resource_manager_inline_notification_init(pResourceManager, pInitNotification); + } + + /* Acquire init and done fences before posting the job. These will be unacquired by the job thread. */ + if (pInitFence != NULL) { ma_fence_acquire(pInitFence); } + if (pDoneFence != NULL) { ma_fence_acquire(pDoneFence); } + + /* We now have everything we need to post the job to the job thread. */ + job = ma_resource_manager_job_init(MA_RESOURCE_MANAGER_JOB_LOAD_DATA_BUFFER_NODE); + job.order = ma_resource_manager_data_buffer_node_next_execution_order(pDataBufferNode); + job.data.loadDataBufferNode.pDataBufferNode = pDataBufferNode; + job.data.loadDataBufferNode.pFilePath = pFilePathCopy; + job.data.loadDataBufferNode.pFilePathW = pFilePathWCopy; + job.data.loadDataBufferNode.decode = (flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE ) != 0; + job.data.loadDataBufferNode.pInitNotification = ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) ? pInitNotification : NULL; + job.data.loadDataBufferNode.pDoneNotification = NULL; + job.data.loadDataBufferNode.pInitFence = pInitFence; + job.data.loadDataBufferNode.pDoneFence = pDoneFence; + + result = ma_resource_manager_post_job(pResourceManager, &job); + if (result != MA_SUCCESS) { + /* Failed to post job. Probably ran out of memory. */ + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_RESOURCE_MANAGER_JOB_LOAD_DATA_BUFFER_NODE job. %s.\n", ma_result_description(result)); + + /* + Fences were acquired before posting the job, but since the job was not able to + be posted, we need to make sure we release them so nothing gets stuck waiting. + */ + if (pInitFence != NULL) { ma_fence_release(pInitFence); } + if (pDoneFence != NULL) { ma_fence_release(pDoneFence); } + + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + ma_resource_manager_inline_notification_init(pResourceManager, pInitNotification); + } + + ma_free(pFilePathCopy, &pResourceManager->config.allocationCallbacks); + ma_free(pFilePathWCopy, &pResourceManager->config.allocationCallbacks); + + ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode); + ma_free(pDataBufferNode, &pResourceManager->config.allocationCallbacks); + + return result; + } + } + } + +done: + if (ppDataBufferNode != NULL) { + *ppDataBufferNode = pDataBufferNode; + } + + return result; +} + +static ma_result ma_resource_manager_data_buffer_node_acquire(ma_resource_manager* pResourceManager, const char* pFilePath, const wchar_t* pFilePathW, ma_uint32 hashedName32, ma_uint32 flags, const ma_resource_manager_data_supply* pExistingData, ma_fence* pInitFence, ma_fence* pDoneFence, ma_resource_manager_data_buffer_node** ppDataBufferNode) +{ + ma_result result = MA_SUCCESS; + ma_bool32 nodeAlreadyExists = MA_FALSE; + ma_resource_manager_data_buffer_node* pDataBufferNode = NULL; + ma_resource_manager_inline_notification initNotification; /* Used when the WAIT_INIT flag is set. */ + + if (ppDataBufferNode != NULL) { + *ppDataBufferNode = NULL; /* Safety. */ + } + + if (pResourceManager == NULL || (pFilePath == NULL && pFilePathW == NULL && hashedName32 == 0)) { + return MA_INVALID_ARGS; + } + + /* If we're specifying existing data, it must be valid. */ + if (pExistingData != NULL && pExistingData->type == ma_resource_manager_data_supply_type_unknown) { + return MA_INVALID_ARGS; + } + + /* If we don't support threading, remove the ASYNC flag to make the rest of this a bit simpler. */ + if (ma_resource_manager_is_threading_enabled(pResourceManager) == MA_FALSE) { + flags &= ~MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC; + } + + if (hashedName32 == 0) { + if (pFilePath != NULL) { + hashedName32 = ma_hash_string_32(pFilePath); + } else { + hashedName32 = ma_hash_string_w_32(pFilePathW); + } + } + + /* + Here is where we either increment the node's reference count or allocate a new one and add it + to the BST. When allocating a new node, we need to make sure the LOAD_DATA_BUFFER_NODE job is + posted inside the critical section just in case the caller immediately uninitializes the node + as this will ensure the FREE_DATA_BUFFER_NODE job is given an execution order such that the + node is not uninitialized before initialization. + */ + ma_resource_manager_data_buffer_bst_lock(pResourceManager); + { + result = ma_resource_manager_data_buffer_node_acquire_critical_section(pResourceManager, pFilePath, pFilePathW, hashedName32, flags, pExistingData, pInitFence, pDoneFence, &initNotification, &pDataBufferNode); + } + ma_resource_manager_data_buffer_bst_unlock(pResourceManager); + + if (result == MA_ALREADY_EXISTS) { + nodeAlreadyExists = MA_TRUE; + result = MA_SUCCESS; + } else { + if (result != MA_SUCCESS) { + return result; + } + } + + /* + If we're loading synchronously, we'll need to load everything now. When loading asynchronously, + a job will have been posted inside the BST critical section so that an uninitialization can be + allocated an appropriate execution order thereby preventing it from being uninitialized before + the node is initialized by the decoding thread(s). + */ + if (nodeAlreadyExists == MA_FALSE) { /* Don't need to try loading anything if the node already exists. */ + if (pFilePath == NULL && pFilePathW == NULL) { + /* + If this path is hit, it means a buffer is being copied (i.e. initialized from only the + hashed name), but that node has been freed in the meantime, probably from some other + thread. This is an invalid operation. + */ + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Cloning data buffer node failed because the source node was released. The source node must remain valid until the cloning has completed.\n"); + result = MA_INVALID_OPERATION; + goto done; + } + + if (pDataBufferNode->isDataOwnedByResourceManager) { + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC) == 0) { + /* Loading synchronously. Load the sound in it's entirety here. */ + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE) == 0) { + /* No decoding. This is the simple case - just store the file contents in memory. */ + result = ma_resource_manager_data_buffer_node_init_supply_encoded(pResourceManager, pDataBufferNode, pFilePath, pFilePathW); + if (result != MA_SUCCESS) { + goto done; + } + } else { + /* Decoding. We do this the same way as we do when loading asynchronously. */ + ma_decoder* pDecoder; + result = ma_resource_manager_data_buffer_node_init_supply_decoded(pResourceManager, pDataBufferNode, pFilePath, pFilePathW, &pDecoder); + if (result != MA_SUCCESS) { + goto done; + } + + /* We have the decoder, now decode page by page just like we do when loading asynchronously. */ + for (;;) { + /* Decode next page. */ + result = ma_resource_manager_data_buffer_node_decode_next_page(pResourceManager, pDataBufferNode, pDecoder); + if (result != MA_SUCCESS) { + break; /* Will return MA_AT_END when the last page has been decoded. */ + } + } + + /* Reaching the end needs to be considered successful. */ + if (result == MA_AT_END) { + result = MA_SUCCESS; + } + + /* + At this point the data buffer is either fully decoded or some error occurred. Either + way, the decoder is no longer necessary. + */ + ma_decoder_uninit(pDecoder); + ma_free(pDecoder, &pResourceManager->config.allocationCallbacks); + } + + /* Getting here means we were successful. Make sure the status of the node is updated accordingly. */ + c89atomic_exchange_i32(&pDataBufferNode->result, result); + } else { + /* Loading asynchronously. We may need to wait for initialization. */ + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + ma_resource_manager_inline_notification_wait(&initNotification); + } + } + } else { + /* The data is not managed by the resource manager so there's nothing else to do. */ + MA_ASSERT(pExistingData != NULL); + } + } + +done: + /* If we failed to initialize the data buffer we need to free it. */ + if (result != MA_SUCCESS) { + if (nodeAlreadyExists == MA_FALSE) { + ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode); + ma_free(pDataBufferNode, &pResourceManager->config.allocationCallbacks); + } + } + + /* + The init notification needs to be uninitialized. This will be used if the node does not already + exist, and we've specified ASYNC | WAIT_INIT. + */ + if (nodeAlreadyExists == MA_FALSE && pDataBufferNode->isDataOwnedByResourceManager && (flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC) != 0) { + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + ma_resource_manager_inline_notification_uninit(&initNotification); + } + } + + if (ppDataBufferNode != NULL) { + *ppDataBufferNode = pDataBufferNode; + } + + return result; +} + +static ma_result ma_resource_manager_data_buffer_node_unacquire(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, const char* pName, const wchar_t* pNameW) +{ + ma_result result = MA_SUCCESS; + ma_uint32 refCount = 0xFFFFFFFF; /* The new reference count of the node after decrementing. Initialize to non-0 to be safe we don't fall into the freeing path. */ + ma_uint32 hashedName32 = 0; + + if (pResourceManager == NULL) { + return MA_INVALID_ARGS; + } + + if (pDataBufferNode == NULL) { + if (pName == NULL && pNameW == NULL) { + return MA_INVALID_ARGS; + } + + if (pName != NULL) { + hashedName32 = ma_hash_string_32(pName); + } else { + hashedName32 = ma_hash_string_w_32(pNameW); + } + } + + /* + The first thing to do is decrement the reference counter of the node. Then, if the reference + count is zero, we need to free the node. If the node is still in the process of loading, we'll + need to post a job to the job queue to free the node. Otherwise we'll just do it here. + */ + ma_resource_manager_data_buffer_bst_lock(pResourceManager); + { + /* Might need to find the node. Must be done inside the critical section. */ + if (pDataBufferNode == NULL) { + result = ma_resource_manager_data_buffer_node_search(pResourceManager, hashedName32, &pDataBufferNode); + if (result != MA_SUCCESS) { + goto stage2; /* Couldn't find the node. */ + } + } + + result = ma_resource_manager_data_buffer_node_decrement_ref(pResourceManager, pDataBufferNode, &refCount); + if (result != MA_SUCCESS) { + goto stage2; /* Should never happen. */ + } + + if (refCount == 0) { + result = ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode); + if (result != MA_SUCCESS) { + goto stage2; /* An error occurred when trying to remove the data buffer. This should never happen. */ + } + } + } + ma_resource_manager_data_buffer_bst_unlock(pResourceManager); + +stage2: + if (result != MA_SUCCESS) { + return result; + } + + /* + Here is where we need to free the node. We don't want to do this inside the critical section + above because we want to keep that as small as possible for multi-threaded efficiency. + */ + if (refCount == 0) { + if (ma_resource_manager_data_buffer_node_result(pDataBufferNode) == MA_BUSY) { + /* The sound is still loading. We need to delay the freeing of the node to a safe time. */ + ma_resource_manager_job job; + + /* We need to mark the node as unavailable for the sake of the resource manager worker threads. */ + c89atomic_exchange_i32(&pDataBufferNode->result, MA_UNAVAILABLE); + + job = ma_resource_manager_job_init(MA_RESOURCE_MANAGER_JOB_FREE_DATA_BUFFER_NODE); + job.order = ma_resource_manager_data_buffer_node_next_execution_order(pDataBufferNode); + job.data.freeDataBufferNode.pDataBufferNode = pDataBufferNode; + + result = ma_resource_manager_post_job(pResourceManager, &job); + if (result != MA_SUCCESS) { + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_RESOURCE_MANAGER_JOB_FREE_DATA_BUFFER_NODE job. %s.\n", ma_result_description(result)); + return result; + } + + /* If we don't support threading, process the job queue here. */ + if (ma_resource_manager_is_threading_enabled(pResourceManager) == MA_FALSE) { + while (ma_resource_manager_data_buffer_node_result(pDataBufferNode) == MA_BUSY) { + result = ma_resource_manager_process_next_job(pResourceManager); + if (result == MA_NO_DATA_AVAILABLE || result == MA_RESOURCE_MANAGER_JOB_QUIT) { + result = MA_SUCCESS; + break; + } + } + } else { + /* Threading is enabled. The job queue will deal with the rest of the cleanup from here. */ + } + } else { + /* The sound isn't loading so we can just free the node here. */ + ma_resource_manager_data_buffer_node_free(pResourceManager, pDataBufferNode); + } + } + + return result; +} + + + +static ma_uint32 ma_resource_manager_data_buffer_next_execution_order(ma_resource_manager_data_buffer* pDataBuffer) +{ + MA_ASSERT(pDataBuffer != NULL); + return c89atomic_fetch_add_32(&pDataBuffer->executionCounter, 1); +} + +static ma_result ma_resource_manager_data_buffer_cb__read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + return ma_resource_manager_data_buffer_read_pcm_frames((ma_resource_manager_data_buffer*)pDataSource, pFramesOut, frameCount, pFramesRead); +} + +static ma_result ma_resource_manager_data_buffer_cb__seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex) +{ + return ma_resource_manager_data_buffer_seek_to_pcm_frame((ma_resource_manager_data_buffer*)pDataSource, frameIndex); +} + +static ma_result ma_resource_manager_data_buffer_cb__get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + return ma_resource_manager_data_buffer_get_data_format((ma_resource_manager_data_buffer*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); +} + +static ma_result ma_resource_manager_data_buffer_cb__get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor) +{ + return ma_resource_manager_data_buffer_get_cursor_in_pcm_frames((ma_resource_manager_data_buffer*)pDataSource, pCursor); +} + +static ma_result ma_resource_manager_data_buffer_cb__get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength) +{ + return ma_resource_manager_data_buffer_get_length_in_pcm_frames((ma_resource_manager_data_buffer*)pDataSource, pLength); +} + +static ma_result ma_resource_manager_data_buffer_cb__set_looping(ma_data_source* pDataSource, ma_bool32 isLooping) +{ + return ma_resource_manager_data_buffer_set_looping((ma_resource_manager_data_buffer*)pDataSource, isLooping); +} + +static ma_data_source_vtable g_ma_resource_manager_data_buffer_vtable = +{ + ma_resource_manager_data_buffer_cb__read_pcm_frames, + ma_resource_manager_data_buffer_cb__seek_to_pcm_frame, + ma_resource_manager_data_buffer_cb__get_data_format, + ma_resource_manager_data_buffer_cb__get_cursor_in_pcm_frames, + ma_resource_manager_data_buffer_cb__get_length_in_pcm_frames, + ma_resource_manager_data_buffer_cb__set_looping, + 0 +}; + +static ma_result ma_resource_manager_data_buffer_init_ex_internal(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_uint32 hashedName32, ma_resource_manager_data_buffer* pDataBuffer) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager_data_buffer_node* pDataBufferNode; + ma_data_source_config dataSourceConfig; + ma_bool32 async; + ma_uint32 flags; + ma_resource_manager_pipeline_notifications notifications; + + if (pDataBuffer == NULL) { + if (pConfig != NULL && pConfig->pNotifications != NULL) { + ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); + } + + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pDataBuffer); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->pNotifications != NULL) { + notifications = *pConfig->pNotifications; /* From here on out we should be referencing `notifications` instead of `pNotifications`. Set this to NULL to catch errors at testing time. */ + } else { + MA_ZERO_OBJECT(¬ifications); + } + + /* For safety, always remove the ASYNC flag if threading is disabled on the resource manager. */ + flags = pConfig->flags; + if (ma_resource_manager_is_threading_enabled(pResourceManager) == MA_FALSE) { + flags &= ~MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC; + } + + async = (flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC) != 0; + + /* + Fences need to be acquired before doing anything. These must be aquired and released outside of + the node to ensure there's no holes where ma_fence_wait() could prematurely return before the + data buffer has completed initialization. + + When loading asynchronously, the node acquisition routine below will acquire the fences on this + thread and then release them on the async thread when the operation is complete. + + These fences are always released at the "done" tag at the end of this function. They'll be + acquired a second if loading asynchronously. This double acquisition system is just done to + simplify code maintanence. + */ + ma_resource_manager_pipeline_notifications_acquire_all_fences(¬ifications); + { + /* We first need to acquire a node. If ASYNC is not set, this will not return until the entire sound has been loaded. */ + result = ma_resource_manager_data_buffer_node_acquire(pResourceManager, pConfig->pFilePath, pConfig->pFilePathW, hashedName32, flags, NULL, notifications.init.pFence, notifications.done.pFence, &pDataBufferNode); + if (result != MA_SUCCESS) { + ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); + goto done; + } + + dataSourceConfig = ma_data_source_config_init(); + dataSourceConfig.vtable = &g_ma_resource_manager_data_buffer_vtable; + + result = ma_data_source_init(&dataSourceConfig, &pDataBuffer->ds); + if (result != MA_SUCCESS) { + ma_resource_manager_data_buffer_node_unacquire(pResourceManager, pDataBufferNode, NULL, NULL); + ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); + goto done; + } + + pDataBuffer->pResourceManager = pResourceManager; + pDataBuffer->pNode = pDataBufferNode; + pDataBuffer->flags = flags; + pDataBuffer->result = MA_BUSY; /* Always default to MA_BUSY for safety. It'll be overwritten when loading completes or an error occurs. */ + + /* If we're loading asynchronously we need to post a job to the job queue to initialize the connector. */ + if (async == MA_FALSE || ma_resource_manager_data_buffer_node_result(pDataBufferNode) == MA_SUCCESS) { + /* Loading synchronously or the data has already been fully loaded. We can just initialize the connector from here without a job. */ + result = ma_resource_manager_data_buffer_init_connector(pDataBuffer, NULL, NULL); + c89atomic_exchange_i32(&pDataBuffer->result, result); + + ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); + goto done; + } else { + /* The node's data supply isn't initialized yet. The caller has requested that we load asynchronously so we need to post a job to do this. */ + ma_resource_manager_job job; + ma_resource_manager_inline_notification initNotification; /* Used when the WAIT_INIT flag is set. */ + + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + ma_resource_manager_inline_notification_init(pResourceManager, &initNotification); + } + + /* + The status of the data buffer needs to be set to MA_BUSY before posting the job so that the + worker thread is aware of it's busy state. If the LOAD_DATA_BUFFER job sees a status other + than MA_BUSY, it'll assume an error and fall through to an early exit. + */ + c89atomic_exchange_i32(&pDataBuffer->result, MA_BUSY); + + /* Acquire fences a second time. These will be released by the async thread. */ + ma_resource_manager_pipeline_notifications_acquire_all_fences(¬ifications); + + job = ma_resource_manager_job_init(MA_RESOURCE_MANAGER_JOB_LOAD_DATA_BUFFER); + job.order = ma_resource_manager_data_buffer_next_execution_order(pDataBuffer); + job.data.loadDataBuffer.pDataBuffer = pDataBuffer; + job.data.loadDataBuffer.pInitNotification = ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) ? &initNotification : notifications.init.pNotification; + job.data.loadDataBuffer.pDoneNotification = notifications.done.pNotification; + job.data.loadDataBuffer.pInitFence = notifications.init.pFence; + job.data.loadDataBuffer.pDoneFence = notifications.done.pFence; + + result = ma_resource_manager_post_job(pResourceManager, &job); + if (result != MA_SUCCESS) { + /* We failed to post the job. Most likely there isn't enough room in the queue's buffer. */ + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_RESOURCE_MANAGER_JOB_LOAD_DATA_BUFFER job. %s.\n", ma_result_description(result)); + c89atomic_exchange_i32(&pDataBuffer->result, result); + + /* Release the fences after the result has been set on the data buffer. */ + ma_resource_manager_pipeline_notifications_release_all_fences(¬ifications); + } else { + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + ma_resource_manager_inline_notification_wait(&initNotification); + + if (notifications.init.pNotification != NULL) { + ma_async_notification_signal(notifications.init.pNotification); + } + + /* NOTE: Do not release the init fence here. It will have been done by the job. */ + + /* Make sure we return an error if initialization failed on the async thread. */ + result = ma_resource_manager_data_buffer_result(pDataBuffer); + if (result == MA_BUSY) { + result = MA_SUCCESS; + } + } + } + + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + ma_resource_manager_inline_notification_uninit(&initNotification); + } + } + + if (result != MA_SUCCESS) { + ma_resource_manager_data_buffer_node_unacquire(pResourceManager, pDataBufferNode, NULL, NULL); + goto done; + } + } +done: + if (result == MA_SUCCESS) { + if (pConfig->initialSeekPointInPCMFrames > 0) { + ma_resource_manager_data_buffer_seek_to_pcm_frame(pDataBuffer, pConfig->initialSeekPointInPCMFrames); + } + } + + ma_resource_manager_pipeline_notifications_release_all_fences(¬ifications); + + return result; +} + +MA_API ma_result ma_resource_manager_data_buffer_init_ex(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_buffer* pDataBuffer) +{ + return ma_resource_manager_data_buffer_init_ex_internal(pResourceManager, pConfig, 0, pDataBuffer); +} + +MA_API ma_result ma_resource_manager_data_buffer_init(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_buffer* pDataBuffer) +{ + ma_resource_manager_data_source_config config; + + config = ma_resource_manager_data_source_config_init(); + config.pFilePath = pFilePath; + config.flags = flags; + config.pNotifications = pNotifications; + + return ma_resource_manager_data_buffer_init_ex(pResourceManager, &config, pDataBuffer); +} + +MA_API ma_result ma_resource_manager_data_buffer_init_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_buffer* pDataBuffer) +{ + ma_resource_manager_data_source_config config; + + config = ma_resource_manager_data_source_config_init(); + config.pFilePathW = pFilePath; + config.flags = flags; + config.pNotifications = pNotifications; + + return ma_resource_manager_data_buffer_init_ex(pResourceManager, &config, pDataBuffer); +} + +MA_API ma_result ma_resource_manager_data_buffer_init_copy(ma_resource_manager* pResourceManager, const ma_resource_manager_data_buffer* pExistingDataBuffer, ma_resource_manager_data_buffer* pDataBuffer) +{ + ma_resource_manager_data_source_config config; + + if (pExistingDataBuffer == NULL) { + return MA_INVALID_ARGS; + } + + MA_ASSERT(pExistingDataBuffer->pNode != NULL); /* <-- If you've triggered this, you've passed in an invalid existing data buffer. */ + + config = ma_resource_manager_data_source_config_init(); + config.flags = pExistingDataBuffer->flags; + + return ma_resource_manager_data_buffer_init_ex_internal(pResourceManager, &config, pExistingDataBuffer->pNode->hashedName32, pDataBuffer); +} + +static ma_result ma_resource_manager_data_buffer_uninit_internal(ma_resource_manager_data_buffer* pDataBuffer) +{ + MA_ASSERT(pDataBuffer != NULL); + + /* The connector should be uninitialized first. */ + ma_resource_manager_data_buffer_uninit_connector(pDataBuffer->pResourceManager, pDataBuffer); + + /* With the connector uninitialized we can unacquire the node. */ + ma_resource_manager_data_buffer_node_unacquire(pDataBuffer->pResourceManager, pDataBuffer->pNode, NULL, NULL); + + /* The base data source needs to be uninitialized as well. */ + ma_data_source_uninit(&pDataBuffer->ds); + + return MA_SUCCESS; +} + +MA_API ma_result ma_resource_manager_data_buffer_uninit(ma_resource_manager_data_buffer* pDataBuffer) +{ + ma_result result; + + if (pDataBuffer == NULL) { + return MA_INVALID_ARGS; + } + + if (ma_resource_manager_data_buffer_result(pDataBuffer) == MA_SUCCESS) { + /* The data buffer can be deleted synchronously. */ + return ma_resource_manager_data_buffer_uninit_internal(pDataBuffer); + } else { + /* + The data buffer needs to be deleted asynchronously because it's still loading. With the status set to MA_UNAVAILABLE, no more pages will + be loaded and the uninitialization should happen fairly quickly. Since the caller owns the data buffer, we need to wait for this event + to get processed before returning. + */ + ma_resource_manager_inline_notification notification; + ma_resource_manager_job job; + + /* + We need to mark the node as unavailable so we don't try reading from it anymore, but also to + let the loading thread know that it needs to abort it's loading procedure. + */ + c89atomic_exchange_i32(&pDataBuffer->result, MA_UNAVAILABLE); + + result = ma_resource_manager_inline_notification_init(pDataBuffer->pResourceManager, ¬ification); + if (result != MA_SUCCESS) { + return result; /* Failed to create the notification. This should rarely, if ever, happen. */ + } + + job = ma_resource_manager_job_init(MA_RESOURCE_MANAGER_JOB_FREE_DATA_BUFFER); + job.order = ma_resource_manager_data_buffer_next_execution_order(pDataBuffer); + job.data.freeDataBuffer.pDataBuffer = pDataBuffer; + job.data.freeDataBuffer.pDoneNotification = ¬ification; + job.data.freeDataBuffer.pDoneFence = NULL; + + result = ma_resource_manager_post_job(pDataBuffer->pResourceManager, &job); + if (result != MA_SUCCESS) { + ma_resource_manager_inline_notification_uninit(¬ification); + return result; + } + + ma_resource_manager_inline_notification_wait_and_uninit(¬ification); + } + + return result; +} + +MA_API ma_result ma_resource_manager_data_buffer_read_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + ma_result result = MA_SUCCESS; + ma_uint64 framesRead = 0; + ma_bool32 isDecodedBufferBusy = MA_FALSE; + + /* Safety. */ + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + + /* + We cannot be using the data buffer after it's been uninitialized. If you trigger this assert it means you're trying to read from the data buffer after + it's been uninitialized or is in the process of uninitializing. + */ + MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE); + + /* If the node is not initialized we need to abort with a busy code. */ + if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode) == ma_resource_manager_data_supply_type_unknown) { + return MA_BUSY; /* Still loading. */ + } + + if (pDataBuffer->seekToCursorOnNextRead) { + pDataBuffer->seekToCursorOnNextRead = MA_FALSE; + + result = ma_data_source_seek_to_pcm_frame(ma_resource_manager_data_buffer_get_connector(pDataBuffer), pDataBuffer->seekTargetInPCMFrames); + if (result != MA_SUCCESS) { + return result; + } + } + + /* + For decoded buffers (not paged) we need to check beforehand how many frames we have available. We cannot + exceed this amount. We'll read as much as we can, and then return MA_BUSY. + */ + if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode) == ma_resource_manager_data_supply_type_decoded) { + ma_uint64 availableFrames; + + isDecodedBufferBusy = (ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) == MA_BUSY); + + if (ma_resource_manager_data_buffer_get_available_frames(pDataBuffer, &availableFrames) == MA_SUCCESS) { + /* Don't try reading more than the available frame count. */ + if (frameCount > availableFrames) { + frameCount = availableFrames; + + /* + If there's no frames available we want to set the status to MA_AT_END. The logic below + will check if the node is busy, and if so, change it to MA_BUSY. The reason we do this + is because we don't want to call `ma_data_source_read_pcm_frames()` if the frame count + is 0 because that'll result in a situation where it's possible MA_AT_END won't get + returned. + */ + if (frameCount == 0) { + result = MA_AT_END; + } + } else { + isDecodedBufferBusy = MA_FALSE; /* We have enough frames available in the buffer to avoid a MA_BUSY status. */ + } + } + } + + /* Don't attempt to read anything if we've got no frames available. */ + if (frameCount > 0) { + result = ma_data_source_read_pcm_frames(ma_resource_manager_data_buffer_get_connector(pDataBuffer), pFramesOut, frameCount, &framesRead); + } + + /* + If we returned MA_AT_END, but the node is still loading, we don't want to return that code or else the caller will interpret the sound + as at the end and terminate decoding. + */ + if (result == MA_AT_END) { + if (ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) == MA_BUSY) { + result = MA_BUSY; + } + } + + if (isDecodedBufferBusy) { + result = MA_BUSY; + } + + if (pFramesRead != NULL) { + *pFramesRead = framesRead; + } + + if (result == MA_SUCCESS && framesRead == 0) { + result = MA_AT_END; + } + + return result; +} + +MA_API ma_result ma_resource_manager_data_buffer_seek_to_pcm_frame(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64 frameIndex) +{ + ma_result result; + + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE); + + /* If we haven't yet got a connector we need to abort. */ + if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode) == ma_resource_manager_data_supply_type_unknown) { + pDataBuffer->seekTargetInPCMFrames = frameIndex; + pDataBuffer->seekToCursorOnNextRead = MA_TRUE; + return MA_BUSY; /* Still loading. */ + } + + result = ma_data_source_seek_to_pcm_frame(ma_resource_manager_data_buffer_get_connector(pDataBuffer), frameIndex); + if (result != MA_SUCCESS) { + return result; + } + + pDataBuffer->seekTargetInPCMFrames = ~(ma_uint64)0; /* <-- For identification purposes. */ + pDataBuffer->seekToCursorOnNextRead = MA_FALSE; + + return MA_SUCCESS; +} + +MA_API ma_result ma_resource_manager_data_buffer_get_data_format(ma_resource_manager_data_buffer* pDataBuffer, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE); + + switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode)) + { + case ma_resource_manager_data_supply_type_encoded: + { + return ma_data_source_get_data_format(&pDataBuffer->connector.decoder, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); + }; + + case ma_resource_manager_data_supply_type_decoded: + { + *pFormat = pDataBuffer->pNode->data.backend.decoded.format; + *pChannels = pDataBuffer->pNode->data.backend.decoded.channels; + *pSampleRate = pDataBuffer->pNode->data.backend.decoded.sampleRate; + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pDataBuffer->pNode->data.backend.decoded.channels); + return MA_SUCCESS; + }; + + case ma_resource_manager_data_supply_type_decoded_paged: + { + *pFormat = pDataBuffer->pNode->data.backend.decodedPaged.data.format; + *pChannels = pDataBuffer->pNode->data.backend.decodedPaged.data.channels; + *pSampleRate = pDataBuffer->pNode->data.backend.decodedPaged.sampleRate; + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pDataBuffer->pNode->data.backend.decoded.channels); + return MA_SUCCESS; + }; + + case ma_resource_manager_data_supply_type_unknown: + { + return MA_BUSY; /* Still loading. */ + }; + + default: + { + /* Unknown supply type. Should never hit this. */ + return MA_INVALID_ARGS; + } + } +} + +MA_API ma_result ma_resource_manager_data_buffer_get_cursor_in_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pCursor) +{ + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE); + + if (pDataBuffer == NULL || pCursor == NULL) { + return MA_INVALID_ARGS; + } + + *pCursor = 0; + + switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode)) + { + case ma_resource_manager_data_supply_type_encoded: + { + return ma_decoder_get_cursor_in_pcm_frames(&pDataBuffer->connector.decoder, pCursor); + }; + + case ma_resource_manager_data_supply_type_decoded: + { + return ma_audio_buffer_get_cursor_in_pcm_frames(&pDataBuffer->connector.buffer, pCursor); + }; + + case ma_resource_manager_data_supply_type_decoded_paged: + { + return ma_paged_audio_buffer_get_cursor_in_pcm_frames(&pDataBuffer->connector.pagedBuffer, pCursor); + }; + + case ma_resource_manager_data_supply_type_unknown: + { + return MA_BUSY; + }; + + default: + { + return MA_INVALID_ARGS; + } + } +} + +MA_API ma_result ma_resource_manager_data_buffer_get_length_in_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pLength) +{ + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE); + + if (pDataBuffer == NULL || pLength == NULL) { + return MA_INVALID_ARGS; + } + + if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode) == ma_resource_manager_data_supply_type_unknown) { + return MA_BUSY; /* Still loading. */ + } + + return ma_data_source_get_length_in_pcm_frames(ma_resource_manager_data_buffer_get_connector(pDataBuffer), pLength); +} + +MA_API ma_result ma_resource_manager_data_buffer_result(const ma_resource_manager_data_buffer* pDataBuffer) +{ + if (pDataBuffer == NULL) { + return MA_INVALID_ARGS; + } + + return c89atomic_load_i32((ma_result*)&pDataBuffer->result); /* Need a naughty const-cast here. */ +} + +MA_API ma_result ma_resource_manager_data_buffer_set_looping(ma_resource_manager_data_buffer* pDataBuffer, ma_bool32 isLooping) +{ + if (pDataBuffer == NULL) { + return MA_INVALID_ARGS; + } + + c89atomic_exchange_32(&pDataBuffer->isLooping, isLooping); + + /* The looping state needs to be set on the connector as well or else looping won't work when we read audio data. */ + ma_data_source_set_looping(ma_resource_manager_data_buffer_get_connector(pDataBuffer), isLooping); + + return MA_SUCCESS; +} + +MA_API ma_bool32 ma_resource_manager_data_buffer_is_looping(const ma_resource_manager_data_buffer* pDataBuffer) +{ + if (pDataBuffer == NULL) { + return MA_FALSE; + } + + return c89atomic_load_32((ma_bool32*)&pDataBuffer->isLooping); +} + +MA_API ma_result ma_resource_manager_data_buffer_get_available_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pAvailableFrames) +{ + if (pAvailableFrames == NULL) { + return MA_INVALID_ARGS; + } + + *pAvailableFrames = 0; + + if (pDataBuffer == NULL) { + return MA_INVALID_ARGS; + } + + if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode) == ma_resource_manager_data_supply_type_unknown) { + if (ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) == MA_BUSY) { + return MA_BUSY; + } else { + return MA_INVALID_OPERATION; /* No connector. */ + } + } + + switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode)) + { + case ma_resource_manager_data_supply_type_encoded: + { + return ma_decoder_get_available_frames(&pDataBuffer->connector.decoder, pAvailableFrames); + }; + + case ma_resource_manager_data_supply_type_decoded: + { + return ma_audio_buffer_get_available_frames(&pDataBuffer->connector.buffer, pAvailableFrames); + }; + + case ma_resource_manager_data_supply_type_decoded_paged: + { + ma_uint64 cursor; + ma_paged_audio_buffer_get_cursor_in_pcm_frames(&pDataBuffer->connector.pagedBuffer, &cursor); + + if (pDataBuffer->pNode->data.backend.decodedPaged.decodedFrameCount > cursor) { + *pAvailableFrames = pDataBuffer->pNode->data.backend.decodedPaged.decodedFrameCount - cursor; + } else { + *pAvailableFrames = 0; + } + + return MA_SUCCESS; + }; + + case ma_resource_manager_data_supply_type_unknown: + default: + { + /* Unknown supply type. Should never hit this. */ + return MA_INVALID_ARGS; + } + } +} + +MA_API ma_result ma_resource_manager_register_file(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags) +{ + return ma_resource_manager_data_buffer_node_acquire(pResourceManager, pFilePath, NULL, 0, flags, NULL, NULL, NULL, NULL); +} + +MA_API ma_result ma_resource_manager_register_file_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags) +{ + return ma_resource_manager_data_buffer_node_acquire(pResourceManager, NULL, pFilePath, 0, flags, NULL, NULL, NULL, NULL); +} + + +static ma_result ma_resource_manager_register_data(ma_resource_manager* pResourceManager, const char* pName, const wchar_t* pNameW, ma_resource_manager_data_supply* pExistingData) +{ + return ma_resource_manager_data_buffer_node_acquire(pResourceManager, pName, pNameW, 0, 0, pExistingData, NULL, NULL, NULL); +} + +static ma_result ma_resource_manager_register_decoded_data_internal(ma_resource_manager* pResourceManager, const char* pName, const wchar_t* pNameW, const void* pData, ma_uint64 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate) +{ + ma_resource_manager_data_supply data; + data.type = ma_resource_manager_data_supply_type_decoded; + data.backend.decoded.pData = pData; + data.backend.decoded.totalFrameCount = frameCount; + data.backend.decoded.format = format; + data.backend.decoded.channels = channels; + data.backend.decoded.sampleRate = sampleRate; + + return ma_resource_manager_register_data(pResourceManager, pName, pNameW, &data); +} + +MA_API ma_result ma_resource_manager_register_decoded_data(ma_resource_manager* pResourceManager, const char* pName, const void* pData, ma_uint64 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate) +{ + return ma_resource_manager_register_decoded_data_internal(pResourceManager, pName, NULL, pData, frameCount, format, channels, sampleRate); +} + +MA_API ma_result ma_resource_manager_register_decoded_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName, const void* pData, ma_uint64 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate) +{ + return ma_resource_manager_register_decoded_data_internal(pResourceManager, NULL, pName, pData, frameCount, format, channels, sampleRate); +} + + +static ma_result ma_resource_manager_register_encoded_data_internal(ma_resource_manager* pResourceManager, const char* pName, const wchar_t* pNameW, const void* pData, size_t sizeInBytes) +{ + ma_resource_manager_data_supply data; + data.type = ma_resource_manager_data_supply_type_encoded; + data.backend.encoded.pData = pData; + data.backend.encoded.sizeInBytes = sizeInBytes; + + return ma_resource_manager_register_data(pResourceManager, pName, pNameW, &data); +} + +MA_API ma_result ma_resource_manager_register_encoded_data(ma_resource_manager* pResourceManager, const char* pName, const void* pData, size_t sizeInBytes) +{ + return ma_resource_manager_register_encoded_data_internal(pResourceManager, pName, NULL, pData, sizeInBytes); +} + +MA_API ma_result ma_resource_manager_register_encoded_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName, const void* pData, size_t sizeInBytes) +{ + return ma_resource_manager_register_encoded_data_internal(pResourceManager, NULL, pName, pData, sizeInBytes); +} + + +MA_API ma_result ma_resource_manager_unregister_file(ma_resource_manager* pResourceManager, const char* pFilePath) +{ + return ma_resource_manager_unregister_data(pResourceManager, pFilePath); +} + +MA_API ma_result ma_resource_manager_unregister_file_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath) +{ + return ma_resource_manager_unregister_data_w(pResourceManager, pFilePath); +} + +MA_API ma_result ma_resource_manager_unregister_data(ma_resource_manager* pResourceManager, const char* pName) +{ + return ma_resource_manager_data_buffer_node_unacquire(pResourceManager, NULL, pName, NULL); +} + +MA_API ma_result ma_resource_manager_unregister_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName) +{ + return ma_resource_manager_data_buffer_node_unacquire(pResourceManager, NULL, NULL, pName); +} + + +static ma_uint32 ma_resource_manager_data_stream_next_execution_order(ma_resource_manager_data_stream* pDataStream) +{ + MA_ASSERT(pDataStream != NULL); + return c89atomic_fetch_add_32(&pDataStream->executionCounter, 1); +} + +static ma_bool32 ma_resource_manager_data_stream_is_decoder_at_end(const ma_resource_manager_data_stream* pDataStream) +{ + MA_ASSERT(pDataStream != NULL); + return c89atomic_load_32((ma_bool32*)&pDataStream->isDecoderAtEnd); +} + +static ma_uint32 ma_resource_manager_data_stream_seek_counter(const ma_resource_manager_data_stream* pDataStream) +{ + MA_ASSERT(pDataStream != NULL); + return c89atomic_load_32((ma_uint32*)&pDataStream->seekCounter); +} + + +static ma_result ma_resource_manager_data_stream_cb__read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + return ma_resource_manager_data_stream_read_pcm_frames((ma_resource_manager_data_stream*)pDataSource, pFramesOut, frameCount, pFramesRead); +} + +static ma_result ma_resource_manager_data_stream_cb__seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex) +{ + return ma_resource_manager_data_stream_seek_to_pcm_frame((ma_resource_manager_data_stream*)pDataSource, frameIndex); +} + +static ma_result ma_resource_manager_data_stream_cb__get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + return ma_resource_manager_data_stream_get_data_format((ma_resource_manager_data_stream*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); +} + +static ma_result ma_resource_manager_data_stream_cb__get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor) +{ + return ma_resource_manager_data_stream_get_cursor_in_pcm_frames((ma_resource_manager_data_stream*)pDataSource, pCursor); +} + +static ma_result ma_resource_manager_data_stream_cb__get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength) +{ + return ma_resource_manager_data_stream_get_length_in_pcm_frames((ma_resource_manager_data_stream*)pDataSource, pLength); +} + +static ma_result ma_resource_manager_data_stream_cb__set_looping(ma_data_source* pDataSource, ma_bool32 isLooping) +{ + return ma_resource_manager_data_stream_set_looping((ma_resource_manager_data_stream*)pDataSource, isLooping); +} + +static ma_data_source_vtable g_ma_resource_manager_data_stream_vtable = +{ + ma_resource_manager_data_stream_cb__read_pcm_frames, + ma_resource_manager_data_stream_cb__seek_to_pcm_frame, + ma_resource_manager_data_stream_cb__get_data_format, + ma_resource_manager_data_stream_cb__get_cursor_in_pcm_frames, + ma_resource_manager_data_stream_cb__get_length_in_pcm_frames, + ma_resource_manager_data_stream_cb__set_looping, + MA_DATA_SOURCE_SELF_MANAGED_RANGE_AND_LOOP_POINT +}; + +static void ma_resource_manager_data_stream_set_absolute_cursor(ma_resource_manager_data_stream* pDataStream, ma_uint64 absoluteCursor) +{ + /* Loop if possible. */ + if (absoluteCursor > pDataStream->totalLengthInPCMFrames && pDataStream->totalLengthInPCMFrames > 0) { + absoluteCursor = absoluteCursor % pDataStream->totalLengthInPCMFrames; + } + + c89atomic_exchange_64(&pDataStream->absoluteCursor, absoluteCursor); +} + +MA_API ma_result ma_resource_manager_data_stream_init_ex(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_stream* pDataStream) +{ + ma_result result; + ma_data_source_config dataSourceConfig; + char* pFilePathCopy = NULL; + wchar_t* pFilePathWCopy = NULL; + ma_resource_manager_job job; + ma_bool32 waitBeforeReturning = MA_FALSE; + ma_resource_manager_inline_notification waitNotification; + ma_resource_manager_pipeline_notifications notifications; + + if (pDataStream == NULL) { + if (pConfig != NULL && pConfig->pNotifications != NULL) { + ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); + } + + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pDataStream); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->pNotifications != NULL) { + notifications = *pConfig->pNotifications; /* From here on out, `notifications` should be used instead of `pNotifications`. Setting this to NULL to catch any errors at testing time. */ + } else { + MA_ZERO_OBJECT(¬ifications); + } + + dataSourceConfig = ma_data_source_config_init(); + dataSourceConfig.vtable = &g_ma_resource_manager_data_stream_vtable; + + result = ma_data_source_init(&dataSourceConfig, &pDataStream->ds); + if (result != MA_SUCCESS) { + ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); + return result; + } + + pDataStream->pResourceManager = pResourceManager; + pDataStream->flags = pConfig->flags; + pDataStream->result = MA_BUSY; + + ma_data_source_set_range_in_pcm_frames(pDataStream, pConfig->rangeBegInPCMFrames, pConfig->rangeEndInPCMFrames); + ma_data_source_set_loop_point_in_pcm_frames(pDataStream, pConfig->loopPointBegInPCMFrames, pConfig->loopPointEndInPCMFrames); + ma_data_source_set_looping(pDataStream, pConfig->isLooping); + + if (pResourceManager == NULL || (pConfig->pFilePath == NULL && pConfig->pFilePathW == NULL)) { + ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); + return MA_INVALID_ARGS; + } + + /* We want all access to the VFS and the internal decoder to happen on the job thread just to keep things easier to manage for the VFS. */ + + /* We need a copy of the file path. We should probably make this more efficient, but for now we'll do a transient memory allocation. */ + if (pConfig->pFilePath != NULL) { + pFilePathCopy = ma_copy_string(pConfig->pFilePath, &pResourceManager->config.allocationCallbacks); + } else { + pFilePathWCopy = ma_copy_string_w(pConfig->pFilePathW, &pResourceManager->config.allocationCallbacks); + } + + if (pFilePathCopy == NULL && pFilePathWCopy == NULL) { + ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); + return MA_OUT_OF_MEMORY; + } + + /* + We need to check for the presence of MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC. If it's not set, we need to wait before returning. Otherwise we + can return immediately. Likewise, we'll also check for MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT and do the same. + */ + if ((pConfig->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC) == 0 || (pConfig->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + waitBeforeReturning = MA_TRUE; + ma_resource_manager_inline_notification_init(pResourceManager, &waitNotification); + } + + ma_resource_manager_pipeline_notifications_acquire_all_fences(¬ifications); + + /* Set the absolute cursor to our initial seek position so retrieval of the cursor returns a good value. */ + ma_resource_manager_data_stream_set_absolute_cursor(pDataStream, pConfig->initialSeekPointInPCMFrames); + + /* We now have everything we need to post the job. This is the last thing we need to do from here. The rest will be done by the job thread. */ + job = ma_resource_manager_job_init(MA_RESOURCE_MANAGER_JOB_LOAD_DATA_STREAM); + job.order = ma_resource_manager_data_stream_next_execution_order(pDataStream); + job.data.loadDataStream.pDataStream = pDataStream; + job.data.loadDataStream.pFilePath = pFilePathCopy; + job.data.loadDataStream.pFilePathW = pFilePathWCopy; + job.data.loadDataStream.initialSeekPoint = pConfig->initialSeekPointInPCMFrames; + job.data.loadDataStream.pInitNotification = (waitBeforeReturning == MA_TRUE) ? &waitNotification : notifications.init.pNotification; + job.data.loadDataStream.pInitFence = notifications.init.pFence; + result = ma_resource_manager_post_job(pResourceManager, &job); + if (result != MA_SUCCESS) { + ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); + ma_resource_manager_pipeline_notifications_release_all_fences(¬ifications); + + if (waitBeforeReturning) { + ma_resource_manager_inline_notification_uninit(&waitNotification); + } + + ma_free(pFilePathCopy, &pResourceManager->config.allocationCallbacks); + ma_free(pFilePathWCopy, &pResourceManager->config.allocationCallbacks); + return result; + } + + /* Wait if needed. */ + if (waitBeforeReturning) { + ma_resource_manager_inline_notification_wait_and_uninit(&waitNotification); + + if (notifications.init.pNotification != NULL) { + ma_async_notification_signal(notifications.init.pNotification); + } + + /* NOTE: Do not release pInitFence here. That will be done by the job. */ + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_resource_manager_data_stream_init(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_stream* pDataStream) +{ + ma_resource_manager_data_source_config config; + + config = ma_resource_manager_data_source_config_init(); + config.pFilePath = pFilePath; + config.flags = flags; + config.pNotifications = pNotifications; + + return ma_resource_manager_data_stream_init_ex(pResourceManager, &config, pDataStream); +} + +MA_API ma_result ma_resource_manager_data_stream_init_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_stream* pDataStream) +{ + ma_resource_manager_data_source_config config; + + config = ma_resource_manager_data_source_config_init(); + config.pFilePathW = pFilePath; + config.flags = flags; + config.pNotifications = pNotifications; + + return ma_resource_manager_data_stream_init_ex(pResourceManager, &config, pDataStream); +} + +MA_API ma_result ma_resource_manager_data_stream_uninit(ma_resource_manager_data_stream* pDataStream) +{ + ma_resource_manager_inline_notification freeEvent; + ma_resource_manager_job job; + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + /* The first thing to do is set the result to unavailable. This will prevent future page decoding. */ + c89atomic_exchange_i32(&pDataStream->result, MA_UNAVAILABLE); + + /* + We need to post a job to ensure we're not in the middle or decoding or anything. Because the object is owned by the caller, we'll need + to wait for it to complete before returning which means we need an event. + */ + ma_resource_manager_inline_notification_init(pDataStream->pResourceManager, &freeEvent); + + job = ma_resource_manager_job_init(MA_RESOURCE_MANAGER_JOB_FREE_DATA_STREAM); + job.order = ma_resource_manager_data_stream_next_execution_order(pDataStream); + job.data.freeDataStream.pDataStream = pDataStream; + job.data.freeDataStream.pDoneNotification = &freeEvent; + job.data.freeDataStream.pDoneFence = NULL; + ma_resource_manager_post_job(pDataStream->pResourceManager, &job); + + /* We need to wait for the job to finish processing before we return. */ + ma_resource_manager_inline_notification_wait_and_uninit(&freeEvent); + + return MA_SUCCESS; +} + + +static ma_uint32 ma_resource_manager_data_stream_get_page_size_in_frames(ma_resource_manager_data_stream* pDataStream) +{ + MA_ASSERT(pDataStream != NULL); + MA_ASSERT(pDataStream->isDecoderInitialized == MA_TRUE); + + return MA_RESOURCE_MANAGER_PAGE_SIZE_IN_MILLISECONDS * (pDataStream->decoder.outputSampleRate/1000); +} + +static void* ma_resource_manager_data_stream_get_page_data_pointer(ma_resource_manager_data_stream* pDataStream, ma_uint32 pageIndex, ma_uint32 relativeCursor) +{ + MA_ASSERT(pDataStream != NULL); + MA_ASSERT(pDataStream->isDecoderInitialized == MA_TRUE); + MA_ASSERT(pageIndex == 0 || pageIndex == 1); + + return ma_offset_ptr(pDataStream->pPageData, ((ma_resource_manager_data_stream_get_page_size_in_frames(pDataStream) * pageIndex) + relativeCursor) * ma_get_bytes_per_frame(pDataStream->decoder.outputFormat, pDataStream->decoder.outputChannels)); +} + +static void ma_resource_manager_data_stream_fill_page(ma_resource_manager_data_stream* pDataStream, ma_uint32 pageIndex) +{ + ma_result result = MA_SUCCESS; + ma_uint64 pageSizeInFrames; + ma_uint64 totalFramesReadForThisPage = 0; + void* pPageData = ma_resource_manager_data_stream_get_page_data_pointer(pDataStream, pageIndex, 0); + + pageSizeInFrames = ma_resource_manager_data_stream_get_page_size_in_frames(pDataStream); + + /* The decoder needs to inherit the stream's looping and range state. */ + { + ma_uint64 rangeBeg; + ma_uint64 rangeEnd; + ma_uint64 loopPointBeg; + ma_uint64 loopPointEnd; + + ma_data_source_set_looping(&pDataStream->decoder, ma_resource_manager_data_stream_is_looping(pDataStream)); + + ma_data_source_get_range_in_pcm_frames(pDataStream, &rangeBeg, &rangeEnd); + ma_data_source_set_range_in_pcm_frames(&pDataStream->decoder, rangeBeg, rangeEnd); + + ma_data_source_get_loop_point_in_pcm_frames(pDataStream, &loopPointBeg, &loopPointEnd); + ma_data_source_set_loop_point_in_pcm_frames(&pDataStream->decoder, loopPointBeg, loopPointEnd); + } + + /* Just read straight from the decoder. It will deal with ranges and looping for us. */ + result = ma_data_source_read_pcm_frames(&pDataStream->decoder, pPageData, pageSizeInFrames, &totalFramesReadForThisPage); + if (result == MA_AT_END || totalFramesReadForThisPage < pageSizeInFrames) { + c89atomic_exchange_32(&pDataStream->isDecoderAtEnd, MA_TRUE); + } + + c89atomic_exchange_32(&pDataStream->pageFrameCount[pageIndex], (ma_uint32)totalFramesReadForThisPage); + c89atomic_exchange_32(&pDataStream->isPageValid[pageIndex], MA_TRUE); +} + +static void ma_resource_manager_data_stream_fill_pages(ma_resource_manager_data_stream* pDataStream) +{ + ma_uint32 iPage; + + MA_ASSERT(pDataStream != NULL); + + for (iPage = 0; iPage < 2; iPage += 1) { + ma_resource_manager_data_stream_fill_page(pDataStream, iPage); + } +} + + +static ma_result ma_resource_manager_data_stream_map(ma_resource_manager_data_stream* pDataStream, void** ppFramesOut, ma_uint64* pFrameCount) +{ + ma_uint64 framesAvailable; + ma_uint64 frameCount = 0; + + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) != MA_UNAVAILABLE); + + if (pFrameCount != NULL) { + frameCount = *pFrameCount; + *pFrameCount = 0; + } + if (ppFramesOut != NULL) { + *ppFramesOut = NULL; + } + + if (pDataStream == NULL || ppFramesOut == NULL || pFrameCount == NULL) { + return MA_INVALID_ARGS; + } + + if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS) { + return MA_INVALID_OPERATION; + } + + /* Don't attempt to read while we're in the middle of seeking. Tell the caller that we're busy. */ + if (ma_resource_manager_data_stream_seek_counter(pDataStream) > 0) { + return MA_BUSY; + } + + /* If the page we're on is invalid it means we've caught up to the job thread. */ + if (c89atomic_load_32(&pDataStream->isPageValid[pDataStream->currentPageIndex]) == MA_FALSE) { + framesAvailable = 0; + } else { + /* + The page we're on is valid so we must have some frames available. We need to make sure that we don't overflow into the next page, even if it's valid. The reason is + that the unmap process will only post an update for one page at a time. Keeping mapping tied to page boundaries makes this simpler. + */ + ma_uint32 currentPageFrameCount = c89atomic_load_32(&pDataStream->pageFrameCount[pDataStream->currentPageIndex]); + MA_ASSERT(currentPageFrameCount >= pDataStream->relativeCursor); + + framesAvailable = currentPageFrameCount - pDataStream->relativeCursor; + } + + /* If there's no frames available and the result is set to MA_AT_END we need to return MA_AT_END. */ + if (framesAvailable == 0) { + if (ma_resource_manager_data_stream_is_decoder_at_end(pDataStream)) { + return MA_AT_END; + } else { + return MA_BUSY; /* There are no frames available, but we're not marked as EOF so we might have caught up to the job thread. Need to return MA_BUSY and wait for more data. */ + } + } + + MA_ASSERT(framesAvailable > 0); + + if (frameCount > framesAvailable) { + frameCount = framesAvailable; + } + + *ppFramesOut = ma_resource_manager_data_stream_get_page_data_pointer(pDataStream, pDataStream->currentPageIndex, pDataStream->relativeCursor); + *pFrameCount = frameCount; + + return MA_SUCCESS; +} + +static ma_result ma_resource_manager_data_stream_unmap(ma_resource_manager_data_stream* pDataStream, ma_uint64 frameCount) +{ + ma_uint32 newRelativeCursor; + ma_uint32 pageSizeInFrames; + ma_resource_manager_job job; + + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) != MA_UNAVAILABLE); + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS) { + return MA_INVALID_OPERATION; + } + + /* The frame count should always fit inside a 32-bit integer. */ + if (frameCount > 0xFFFFFFFF) { + return MA_INVALID_ARGS; + } + + pageSizeInFrames = ma_resource_manager_data_stream_get_page_size_in_frames(pDataStream); + + /* The absolute cursor needs to be updated for ma_resource_manager_data_stream_get_cursor_in_pcm_frames(). */ + ma_resource_manager_data_stream_set_absolute_cursor(pDataStream, c89atomic_load_64(&pDataStream->absoluteCursor) + frameCount); + + /* Here is where we need to check if we need to load a new page, and if so, post a job to load it. */ + newRelativeCursor = pDataStream->relativeCursor + (ma_uint32)frameCount; + + /* If the new cursor has flowed over to the next page we need to mark the old one as invalid and post an event for it. */ + if (newRelativeCursor >= pageSizeInFrames) { + newRelativeCursor -= pageSizeInFrames; + + /* Here is where we post the job start decoding. */ + job = ma_resource_manager_job_init(MA_RESOURCE_MANAGER_JOB_PAGE_DATA_STREAM); + job.order = ma_resource_manager_data_stream_next_execution_order(pDataStream); + job.data.pageDataStream.pDataStream = pDataStream; + job.data.pageDataStream.pageIndex = pDataStream->currentPageIndex; + + /* The page needs to be marked as invalid so that the public API doesn't try reading from it. */ + c89atomic_exchange_32(&pDataStream->isPageValid[pDataStream->currentPageIndex], MA_FALSE); + + /* Before posting the job we need to make sure we set some state. */ + pDataStream->relativeCursor = newRelativeCursor; + pDataStream->currentPageIndex = (pDataStream->currentPageIndex + 1) & 0x01; + return ma_resource_manager_post_job(pDataStream->pResourceManager, &job); + } else { + /* We haven't moved into a new page so we can just move the cursor forward. */ + pDataStream->relativeCursor = newRelativeCursor; + return MA_SUCCESS; + } +} + + +MA_API ma_result ma_resource_manager_data_stream_read_pcm_frames(ma_resource_manager_data_stream* pDataStream, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + ma_result result = MA_SUCCESS; + ma_uint64 totalFramesProcessed; + ma_format format; + ma_uint32 channels; + + /* Safety. */ + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) != MA_UNAVAILABLE); + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS) { + return MA_INVALID_OPERATION; + } + + /* Don't attempt to read while we're in the middle of seeking. Tell the caller that we're busy. */ + if (ma_resource_manager_data_stream_seek_counter(pDataStream) > 0) { + return MA_BUSY; + } + + ma_resource_manager_data_stream_get_data_format(pDataStream, &format, &channels, NULL, NULL, 0); + + /* Reading is implemented in terms of map/unmap. We need to run this in a loop because mapping is clamped against page boundaries. */ + totalFramesProcessed = 0; + while (totalFramesProcessed < frameCount) { + void* pMappedFrames; + ma_uint64 mappedFrameCount; + + mappedFrameCount = frameCount - totalFramesProcessed; + result = ma_resource_manager_data_stream_map(pDataStream, &pMappedFrames, &mappedFrameCount); + if (result != MA_SUCCESS) { + break; + } + + /* Copy the mapped data to the output buffer if we have one. It's allowed for pFramesOut to be NULL in which case a relative forward seek is performed. */ + if (pFramesOut != NULL) { + ma_copy_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, totalFramesProcessed, format, channels), pMappedFrames, mappedFrameCount, format, channels); + } + + totalFramesProcessed += mappedFrameCount; + + result = ma_resource_manager_data_stream_unmap(pDataStream, mappedFrameCount); + if (result != MA_SUCCESS) { + break; /* This is really bad - will only get an error here if we failed to post a job to the queue for loading the next page. */ + } + } + + if (pFramesRead != NULL) { + *pFramesRead = totalFramesProcessed; + } + + if (result == MA_SUCCESS && totalFramesProcessed == 0) { + result = MA_AT_END; + } + + return result; +} + +MA_API ma_result ma_resource_manager_data_stream_seek_to_pcm_frame(ma_resource_manager_data_stream* pDataStream, ma_uint64 frameIndex) +{ + ma_resource_manager_job job; + ma_result streamResult; + + streamResult = ma_resource_manager_data_stream_result(pDataStream); + + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(streamResult != MA_UNAVAILABLE); + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + if (streamResult != MA_SUCCESS && streamResult != MA_BUSY) { + return MA_INVALID_OPERATION; + } + + /* Increment the seek counter first to indicate to read_paged_pcm_frames() and map_paged_pcm_frames() that we are in the middle of a seek and MA_BUSY should be returned. */ + c89atomic_fetch_add_32(&pDataStream->seekCounter, 1); + + /* Update the absolute cursor so that ma_resource_manager_data_stream_get_cursor_in_pcm_frames() returns the new position. */ + ma_resource_manager_data_stream_set_absolute_cursor(pDataStream, frameIndex); + + /* + We need to clear our currently loaded pages so that the stream starts playback from the new seek point as soon as possible. These are for the purpose of the public + API and will be ignored by the seek job. The seek job will operate on the assumption that both pages have been marked as invalid and the cursor is at the start of + the first page. + */ + pDataStream->relativeCursor = 0; + pDataStream->currentPageIndex = 0; + c89atomic_exchange_32(&pDataStream->isPageValid[0], MA_FALSE); + c89atomic_exchange_32(&pDataStream->isPageValid[1], MA_FALSE); + + /* Make sure the data stream is not marked as at the end or else if we seek in response to hitting the end, we won't be able to read any more data. */ + c89atomic_exchange_32(&pDataStream->isDecoderAtEnd, MA_FALSE); + + /* + The public API is not allowed to touch the internal decoder so we need to use a job to perform the seek. When seeking, the job thread will assume both pages + are invalid and any content contained within them will be discarded and replaced with newly decoded data. + */ + job = ma_resource_manager_job_init(MA_RESOURCE_MANAGER_JOB_SEEK_DATA_STREAM); + job.order = ma_resource_manager_data_stream_next_execution_order(pDataStream); + job.data.seekDataStream.pDataStream = pDataStream; + job.data.seekDataStream.frameIndex = frameIndex; + return ma_resource_manager_post_job(pDataStream->pResourceManager, &job); +} + +MA_API ma_result ma_resource_manager_data_stream_get_data_format(ma_resource_manager_data_stream* pDataStream, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) != MA_UNAVAILABLE); + + if (pFormat != NULL) { + *pFormat = ma_format_unknown; + } + + if (pChannels != NULL) { + *pChannels = 0; + } + + if (pSampleRate != NULL) { + *pSampleRate = 0; + } + + if (pChannelMap != NULL) { + MA_ZERO_MEMORY(pChannelMap, sizeof(*pChannelMap) * channelMapCap); + } + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS) { + return MA_INVALID_OPERATION; + } + + /* + We're being a little bit naughty here and accessing the internal decoder from the public API. The output data format is constant, and we've defined this function + such that the application is responsible for ensuring it's not called while uninitializing so it should be safe. + */ + return ma_data_source_get_data_format(&pDataStream->decoder, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); +} + +MA_API ma_result ma_resource_manager_data_stream_get_cursor_in_pcm_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pCursor) +{ + ma_result result; + + if (pCursor == NULL) { + return MA_INVALID_ARGS; + } + + *pCursor = 0; + + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) != MA_UNAVAILABLE); + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + /* + If the stream is in an erroneous state we need to return an invalid operation. We can allow + this to be called when the data stream is in a busy state because the caller may have asked + for an initial seek position and it's convenient to return that as the cursor position. + */ + result = ma_resource_manager_data_stream_result(pDataStream); + if (result != MA_SUCCESS && result != MA_BUSY) { + return MA_INVALID_OPERATION; + } + + *pCursor = c89atomic_load_64(&pDataStream->absoluteCursor); + + return MA_SUCCESS; +} + +MA_API ma_result ma_resource_manager_data_stream_get_length_in_pcm_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pLength) +{ + ma_result streamResult; + + if (pLength == NULL) { + return MA_INVALID_ARGS; + } + + *pLength = 0; + + streamResult = ma_resource_manager_data_stream_result(pDataStream); + + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(streamResult != MA_UNAVAILABLE); + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + if (streamResult != MA_SUCCESS) { + return streamResult; + } + + /* + We most definitely do not want to be calling ma_decoder_get_length_in_pcm_frames() directly. Instead we want to use a cached value that we + calculated when we initialized it on the job thread. + */ + *pLength = pDataStream->totalLengthInPCMFrames; + if (*pLength == 0) { + return MA_NOT_IMPLEMENTED; /* Some decoders may not have a known length. */ + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_resource_manager_data_stream_result(const ma_resource_manager_data_stream* pDataStream) +{ + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + return c89atomic_load_i32(&pDataStream->result); +} + +MA_API ma_result ma_resource_manager_data_stream_set_looping(ma_resource_manager_data_stream* pDataStream, ma_bool32 isLooping) +{ + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + c89atomic_exchange_32(&pDataStream->isLooping, isLooping); + + return MA_SUCCESS; +} + +MA_API ma_bool32 ma_resource_manager_data_stream_is_looping(const ma_resource_manager_data_stream* pDataStream) +{ + if (pDataStream == NULL) { + return MA_FALSE; + } + + return c89atomic_load_32((ma_bool32*)&pDataStream->isLooping); /* Naughty const-cast. Value won't change from here in practice (maybe from another thread). */ +} + +MA_API ma_result ma_resource_manager_data_stream_get_available_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pAvailableFrames) +{ + ma_uint32 pageIndex0; + ma_uint32 pageIndex1; + ma_uint32 relativeCursor; + ma_uint64 availableFrames; + + if (pAvailableFrames == NULL) { + return MA_INVALID_ARGS; + } + + *pAvailableFrames = 0; + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + pageIndex0 = pDataStream->currentPageIndex; + pageIndex1 = (pDataStream->currentPageIndex + 1) & 0x01; + relativeCursor = pDataStream->relativeCursor; + + availableFrames = 0; + if (c89atomic_load_32(&pDataStream->isPageValid[pageIndex0])) { + availableFrames += c89atomic_load_32(&pDataStream->pageFrameCount[pageIndex0]) - relativeCursor; + if (c89atomic_load_32(&pDataStream->isPageValid[pageIndex1])) { + availableFrames += c89atomic_load_32(&pDataStream->pageFrameCount[pageIndex1]); + } + } + + *pAvailableFrames = availableFrames; + return MA_SUCCESS; +} + + +static ma_result ma_resource_manager_data_source_preinit(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_source* pDataSource) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pDataSource); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pResourceManager == NULL) { + return MA_INVALID_ARGS; + } + + pDataSource->flags = pConfig->flags; + + return MA_SUCCESS; +} + +MA_API ma_result ma_resource_manager_data_source_init_ex(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_source* pDataSource) +{ + ma_result result; + + result = ma_resource_manager_data_source_preinit(pResourceManager, pConfig, pDataSource); + if (result != MA_SUCCESS) { + return result; + } + + /* The data source itself is just a data stream or a data buffer. */ + if ((pConfig->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_init_ex(pResourceManager, pConfig, &pDataSource->backend.stream); + } else { + return ma_resource_manager_data_buffer_init_ex(pResourceManager, pConfig, &pDataSource->backend.buffer); + } +} + +MA_API ma_result ma_resource_manager_data_source_init(ma_resource_manager* pResourceManager, const char* pName, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_source* pDataSource) +{ + ma_resource_manager_data_source_config config; + + config = ma_resource_manager_data_source_config_init(); + config.pFilePath = pName; + config.flags = flags; + config.pNotifications = pNotifications; + + return ma_resource_manager_data_source_init_ex(pResourceManager, &config, pDataSource); +} + +MA_API ma_result ma_resource_manager_data_source_init_w(ma_resource_manager* pResourceManager, const wchar_t* pName, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_source* pDataSource) +{ + ma_resource_manager_data_source_config config; + + config = ma_resource_manager_data_source_config_init(); + config.pFilePathW = pName; + config.flags = flags; + config.pNotifications = pNotifications; + + return ma_resource_manager_data_source_init_ex(pResourceManager, &config, pDataSource); +} + +MA_API ma_result ma_resource_manager_data_source_init_copy(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source* pExistingDataSource, ma_resource_manager_data_source* pDataSource) +{ + ma_result result; + ma_resource_manager_data_source_config config; + + if (pExistingDataSource == NULL) { + return MA_INVALID_ARGS; + } + + config = ma_resource_manager_data_source_config_init(); + config.flags = pExistingDataSource->flags; + + result = ma_resource_manager_data_source_preinit(pResourceManager, &config, pDataSource); + if (result != MA_SUCCESS) { + return result; + } + + /* Copying can only be done from data buffers. Streams cannot be copied. */ + if ((pExistingDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return MA_INVALID_OPERATION; + } + + return ma_resource_manager_data_buffer_init_copy(pResourceManager, &pExistingDataSource->backend.buffer, &pDataSource->backend.buffer); +} + +MA_API ma_result ma_resource_manager_data_source_uninit(ma_resource_manager_data_source* pDataSource) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + /* All we need to is uninitialize the underlying data buffer or data stream. */ + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_uninit(&pDataSource->backend.stream); + } else { + return ma_resource_manager_data_buffer_uninit(&pDataSource->backend.buffer); + } +} + +MA_API ma_result ma_resource_manager_data_source_read_pcm_frames(ma_resource_manager_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + /* Safety. */ + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_read_pcm_frames(&pDataSource->backend.stream, pFramesOut, frameCount, pFramesRead); + } else { + return ma_resource_manager_data_buffer_read_pcm_frames(&pDataSource->backend.buffer, pFramesOut, frameCount, pFramesRead); + } +} + +MA_API ma_result ma_resource_manager_data_source_seek_to_pcm_frame(ma_resource_manager_data_source* pDataSource, ma_uint64 frameIndex) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_seek_to_pcm_frame(&pDataSource->backend.stream, frameIndex); + } else { + return ma_resource_manager_data_buffer_seek_to_pcm_frame(&pDataSource->backend.buffer, frameIndex); + } +} + +MA_API ma_result ma_resource_manager_data_source_map(ma_resource_manager_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_map(&pDataSource->backend.stream, ppFramesOut, pFrameCount); + } else { + return MA_NOT_IMPLEMENTED; /* Mapping not supported with data buffers. */ + } +} + +MA_API ma_result ma_resource_manager_data_source_unmap(ma_resource_manager_data_source* pDataSource, ma_uint64 frameCount) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_unmap(&pDataSource->backend.stream, frameCount); + } else { + return MA_NOT_IMPLEMENTED; /* Mapping not supported with data buffers. */ + } +} + +MA_API ma_result ma_resource_manager_data_source_get_data_format(ma_resource_manager_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_get_data_format(&pDataSource->backend.stream, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); + } else { + return ma_resource_manager_data_buffer_get_data_format(&pDataSource->backend.buffer, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); + } +} + +MA_API ma_result ma_resource_manager_data_source_get_cursor_in_pcm_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pCursor) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_get_cursor_in_pcm_frames(&pDataSource->backend.stream, pCursor); + } else { + return ma_resource_manager_data_buffer_get_cursor_in_pcm_frames(&pDataSource->backend.buffer, pCursor); + } +} + +MA_API ma_result ma_resource_manager_data_source_get_length_in_pcm_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pLength) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_get_length_in_pcm_frames(&pDataSource->backend.stream, pLength); + } else { + return ma_resource_manager_data_buffer_get_length_in_pcm_frames(&pDataSource->backend.buffer, pLength); + } +} + +MA_API ma_result ma_resource_manager_data_source_result(const ma_resource_manager_data_source* pDataSource) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_result(&pDataSource->backend.stream); + } else { + return ma_resource_manager_data_buffer_result(&pDataSource->backend.buffer); + } +} + +MA_API ma_result ma_resource_manager_data_source_set_looping(ma_resource_manager_data_source* pDataSource, ma_bool32 isLooping) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_set_looping(&pDataSource->backend.stream, isLooping); + } else { + return ma_resource_manager_data_buffer_set_looping(&pDataSource->backend.buffer, isLooping); + } +} + +MA_API ma_bool32 ma_resource_manager_data_source_is_looping(const ma_resource_manager_data_source* pDataSource) +{ + if (pDataSource == NULL) { + return MA_FALSE; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_is_looping(&pDataSource->backend.stream); + } else { + return ma_resource_manager_data_buffer_is_looping(&pDataSource->backend.buffer); + } +} + +MA_API ma_result ma_resource_manager_data_source_get_available_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pAvailableFrames) +{ + if (pAvailableFrames == NULL) { + return MA_INVALID_ARGS; + } + + *pAvailableFrames = 0; + + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_get_available_frames(&pDataSource->backend.stream, pAvailableFrames); + } else { + return ma_resource_manager_data_buffer_get_available_frames(&pDataSource->backend.buffer, pAvailableFrames); + } +} + + +MA_API ma_result ma_resource_manager_post_job(ma_resource_manager* pResourceManager, const ma_resource_manager_job* pJob) +{ + if (pResourceManager == NULL) { + return MA_INVALID_ARGS; + } + + return ma_resource_manager_job_queue_post(&pResourceManager->jobQueue, pJob); +} + +MA_API ma_result ma_resource_manager_post_job_quit(ma_resource_manager* pResourceManager) +{ + ma_resource_manager_job job = ma_resource_manager_job_init(MA_RESOURCE_MANAGER_JOB_QUIT); + return ma_resource_manager_post_job(pResourceManager, &job); +} + +MA_API ma_result ma_resource_manager_next_job(ma_resource_manager* pResourceManager, ma_resource_manager_job* pJob) +{ + if (pResourceManager == NULL) { + return MA_INVALID_ARGS; + } + + return ma_resource_manager_job_queue_next(&pResourceManager->jobQueue, pJob); +} + + +static ma_result ma_resource_manager_process_job__load_data_buffer_node(ma_resource_manager* pResourceManager, ma_resource_manager_job* pJob) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager_data_buffer_node* pDataBufferNode; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pJob != NULL); + + pDataBufferNode = pJob->data.loadDataBufferNode.pDataBufferNode; + + MA_ASSERT(pDataBufferNode != NULL); + MA_ASSERT(pDataBufferNode->isDataOwnedByResourceManager == MA_TRUE); /* The data should always be owned by the resource manager. */ + + /* The data buffer is not getting deleted, but we may be getting executed out of order. If so, we need to push the job back onto the queue and return. */ + if (pJob->order != c89atomic_load_32(&pDataBufferNode->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Attempting to execute out of order. Probably interleaved with a MA_RESOURCE_MANAGER_JOB_FREE_DATA_BUFFER job. */ + } + + /* First thing we need to do is check whether or not the data buffer is getting deleted. If so we just abort. */ + if (ma_resource_manager_data_buffer_node_result(pDataBufferNode) != MA_BUSY) { + result = ma_resource_manager_data_buffer_node_result(pDataBufferNode); /* The data buffer may be getting deleted before it's even been loaded. */ + goto done; + } + + /* + We're ready to start loading. Essentially what we're doing here is initializing the data supply + of the node. Once this is complete, data buffers can have their connectors initialized which + will allow then to have audio data read from them. + + Note that when the data supply type has been moved away from "unknown", that is when other threads + will determine that the node is available for data delivery and the data buffer connectors can be + initialized. Therefore, it's important that it is set after the data supply has been initialized. + */ + if (pJob->data.loadDataBufferNode.decode) { + /* + Decoding. This is the complex case because we're not going to be doing the entire decoding + process here. Instead it's going to be split of multiple jobs and loaded in pages. The + reason for this is to evenly distribute decoding time across multiple sounds, rather than + having one huge sound hog all the available processing resources. + + The first thing we do is initialize a decoder. This is allocated on the heap and is passed + around to the paging jobs. When the last paging job has completed it's processing, it'll + free the decoder for us. + + This job does not do any actual decoding. It instead just posts a PAGE_DATA_BUFFER_NODE job + which is where the actual decoding work will be done. However, once this job is complete, + the node will be in a state where data buffer connectors can be initialized. + */ + ma_decoder* pDecoder; /* <-- Free'd on the last page decode. */ + ma_resource_manager_job pageDataBufferNodeJob; + + /* Allocate the decoder by initializing a decoded data supply. */ + result = ma_resource_manager_data_buffer_node_init_supply_decoded(pResourceManager, pDataBufferNode, pJob->data.loadDataBufferNode.pFilePath, pJob->data.loadDataBufferNode.pFilePathW, &pDecoder); + + /* + Don't ever propagate an MA_BUSY result code or else the resource manager will think the + node is just busy decoding rather than in an error state. This should never happen, but + including this logic for safety just in case. + */ + if (result == MA_BUSY) { + result = MA_ERROR; + } + + if (result != MA_SUCCESS) { + if (pJob->data.loadDataBufferNode.pFilePath != NULL) { + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to initialize data supply for \"%s\". %s.\n", pJob->data.loadDataBufferNode.pFilePath, ma_result_description(result)); + } else { + #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(_MSC_VER) + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to initialize data supply for \"%ls\", %s.\n", pJob->data.loadDataBufferNode.pFilePathW, ma_result_description(result)); + #endif + } + + goto done; + } + + /* + At this point the node's data supply is initialized and other threads can start initializing + their data buffer connectors. However, no data will actually be available until we start to + actually decode it. To do this, we need to post a paging job which is where the decoding + work is done. + + Note that if an error occurred at an earlier point, this section will have been skipped. + */ + pageDataBufferNodeJob = ma_resource_manager_job_init(MA_RESOURCE_MANAGER_JOB_PAGE_DATA_BUFFER_NODE); + pageDataBufferNodeJob.order = ma_resource_manager_data_buffer_node_next_execution_order(pDataBufferNode); + pageDataBufferNodeJob.data.pageDataBufferNode.pDataBufferNode = pDataBufferNode; + pageDataBufferNodeJob.data.pageDataBufferNode.pDecoder = pDecoder; + pageDataBufferNodeJob.data.pageDataBufferNode.pDoneNotification = pJob->data.loadDataBufferNode.pDoneNotification; + pageDataBufferNodeJob.data.pageDataBufferNode.pDoneFence = pJob->data.loadDataBufferNode.pDoneFence; + + /* The job has been set up so it can now be posted. */ + result = ma_resource_manager_post_job(pResourceManager, &pageDataBufferNodeJob); + + /* + When we get here, we want to make sure the result code is set to MA_BUSY. The reason for + this is that the result will be copied over to the node's internal result variable. In + this case, since the decoding is still in-progress, we need to make sure the result code + is set to MA_BUSY. + */ + if (result != MA_SUCCESS) { + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_RESOURCE_MANAGER_JOB_PAGE_DATA_BUFFER_NODE job. %s\n", ma_result_description(result)); + ma_decoder_uninit(pDecoder); + ma_free(pDecoder, &pResourceManager->config.allocationCallbacks); + } else { + result = MA_BUSY; + } + } else { + /* No decoding. This is the simple case. We need only read the file content into memory and we're done. */ + result = ma_resource_manager_data_buffer_node_init_supply_encoded(pResourceManager, pDataBufferNode, pJob->data.loadDataBufferNode.pFilePath, pJob->data.loadDataBufferNode.pFilePathW); + } + + +done: + /* File paths are no longer needed. */ + ma_free(pJob->data.loadDataBufferNode.pFilePath, &pResourceManager->config.allocationCallbacks); + ma_free(pJob->data.loadDataBufferNode.pFilePathW, &pResourceManager->config.allocationCallbacks); + + /* + We need to set the result to at the very end to ensure no other threads try reading the data before we've fully initialized the object. Other threads + are going to be inspecting this variable to determine whether or not they're ready to read data. We can only change the result if it's set to MA_BUSY + because otherwise we may be changing away from an error code which would be bad. An example is if the application creates a data buffer, but then + immediately deletes it before we've got to this point. In this case, pDataBuffer->result will be MA_UNAVAILABLE, and setting it to MA_SUCCESS or any + other error code would cause the buffer to look like it's in a state that it's not. + */ + c89atomic_compare_and_swap_i32(&pDataBufferNode->result, MA_BUSY, result); + + /* At this point initialization is complete and we can signal the notification if any. */ + if (pJob->data.loadDataBufferNode.pInitNotification != NULL) { + ma_async_notification_signal(pJob->data.loadDataBufferNode.pInitNotification); + } + if (pJob->data.loadDataBufferNode.pInitFence != NULL) { + ma_fence_release(pJob->data.loadDataBufferNode.pInitFence); + } + + /* If we have a success result it means we've fully loaded the buffer. This will happen in the non-decoding case. */ + if (result != MA_BUSY) { + if (pJob->data.loadDataBufferNode.pDoneNotification != NULL) { + ma_async_notification_signal(pJob->data.loadDataBufferNode.pDoneNotification); + } + if (pJob->data.loadDataBufferNode.pDoneFence != NULL) { + ma_fence_release(pJob->data.loadDataBufferNode.pDoneFence); + } + } + + /* Increment the node's execution pointer so that the next jobs can be processed. This is how we keep decoding of pages in-order. */ + c89atomic_fetch_add_32(&pDataBufferNode->executionPointer, 1); + return result; +} + +static ma_result ma_resource_manager_process_job__free_data_buffer_node(ma_resource_manager* pResourceManager, ma_resource_manager_job* pJob) +{ + ma_resource_manager_data_buffer_node* pDataBufferNode; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pJob != NULL); + + pDataBufferNode = pJob->data.freeDataBufferNode.pDataBufferNode; + + MA_ASSERT(pDataBufferNode != NULL); + + if (pJob->order != c89atomic_load_32(&pDataBufferNode->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ + } + + ma_resource_manager_data_buffer_node_free(pResourceManager, pDataBufferNode); + + /* The event needs to be signalled last. */ + if (pJob->data.freeDataBufferNode.pDoneNotification != NULL) { + ma_async_notification_signal(pJob->data.freeDataBufferNode.pDoneNotification); + } + + if (pJob->data.freeDataBufferNode.pDoneFence != NULL) { + ma_fence_release(pJob->data.freeDataBufferNode.pDoneFence); + } + + c89atomic_fetch_add_32(&pDataBufferNode->executionPointer, 1); + return MA_SUCCESS; +} + +static ma_result ma_resource_manager_process_job__page_data_buffer_node(ma_resource_manager* pResourceManager, ma_resource_manager_job* pJob) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager_data_buffer_node* pDataBufferNode; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pJob != NULL); + + pDataBufferNode = pJob->data.pageDataBufferNode.pDataBufferNode; + MA_ASSERT(pDataBufferNode != NULL); + + if (pJob->order != c89atomic_load_32(&pDataBufferNode->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ + } + + /* Don't do any more decoding if the data buffer has started the uninitialization process. */ + result = ma_resource_manager_data_buffer_node_result(pDataBufferNode); + if (result != MA_BUSY) { + goto done; + } + + /* We're ready to decode the next page. */ + result = ma_resource_manager_data_buffer_node_decode_next_page(pResourceManager, pDataBufferNode, pJob->data.pageDataBufferNode.pDecoder); + + /* + If we have a success code by this point, we want to post another job. We're going to set the + result back to MA_BUSY to make it clear that there's still more to load. + */ + if (result == MA_SUCCESS) { + ma_resource_manager_job newJob; + newJob = *pJob; /* Everything is the same as the input job, except the execution order. */ + newJob.order = ma_resource_manager_data_buffer_node_next_execution_order(pDataBufferNode); /* We need a fresh execution order. */ + + result = ma_resource_manager_post_job(pResourceManager, &newJob); + + /* Since the sound isn't yet fully decoded we want the status to be set to busy. */ + if (result == MA_SUCCESS) { + result = MA_BUSY; + } + } + +done: + /* If there's still more to decode the result will be set to MA_BUSY. Otherwise we can free the decoder. */ + if (result != MA_BUSY) { + ma_decoder_uninit(pJob->data.pageDataBufferNode.pDecoder); + ma_free(pJob->data.pageDataBufferNode.pDecoder, &pResourceManager->config.allocationCallbacks); + } + + /* If we reached the end we need to treat it as successful. */ + if (result == MA_AT_END) { + result = MA_SUCCESS; + } + + /* Make sure we set the result of node in case some error occurred. */ + c89atomic_compare_and_swap_i32(&pDataBufferNode->result, MA_BUSY, result); + + /* Signal the notification after setting the result in case the notification callback wants to inspect the result code. */ + if (result != MA_BUSY) { + if (pJob->data.pageDataBufferNode.pDoneNotification != NULL) { + ma_async_notification_signal(pJob->data.pageDataBufferNode.pDoneNotification); + } + + if (pJob->data.pageDataBufferNode.pDoneFence != NULL) { + ma_fence_release(pJob->data.pageDataBufferNode.pDoneFence); + } + } + + c89atomic_fetch_add_32(&pDataBufferNode->executionPointer, 1); + return result; +} + + +static ma_result ma_resource_manager_process_job__load_data_buffer(ma_resource_manager* pResourceManager, ma_resource_manager_job* pJob) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager_data_buffer* pDataBuffer; + ma_resource_manager_data_supply_type dataSupplyType = ma_resource_manager_data_supply_type_unknown; + ma_bool32 isConnectorInitialized = MA_FALSE; + + /* + All we're doing here is checking if the node has finished loading. If not, we just re-post the job + and keep waiting. Otherwise we increment the execution counter and set the buffer's result code. + */ + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pJob != NULL); + + pDataBuffer = pJob->data.loadDataBuffer.pDataBuffer; + MA_ASSERT(pDataBuffer != NULL); + + if (pJob->order != c89atomic_load_32(&pDataBuffer->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Attempting to execute out of order. Probably interleaved with a MA_RESOURCE_MANAGER_JOB_FREE_DATA_BUFFER job. */ + } + + /* + First thing we need to do is check whether or not the data buffer is getting deleted. If so we + just abort, but making sure we increment the execution pointer. + */ + result = ma_resource_manager_data_buffer_result(pDataBuffer); + if (result != MA_BUSY) { + goto done; /* <-- This will ensure the exucution pointer is incremented. */ + } else { + result = MA_SUCCESS; /* <-- Make sure this is reset. */ + } + + /* Try initializing the connector if we haven't already. */ + isConnectorInitialized = pDataBuffer->isConnectorInitialized; + if (isConnectorInitialized == MA_FALSE) { + dataSupplyType = ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode); + + if (dataSupplyType != ma_resource_manager_data_supply_type_unknown) { + /* We can now initialize the connector. If this fails, we need to abort. It's very rare for this to fail. */ + result = ma_resource_manager_data_buffer_init_connector(pDataBuffer, pJob->data.loadDataBuffer.pInitNotification, pJob->data.loadDataBuffer.pInitFence); + if (result != MA_SUCCESS) { + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to initialize connector for data buffer. %s.\n", ma_result_description(result)); + goto done; + } + } else { + /* Don't have a known data supply type. Most likely the data buffer node is still loading, but it could be that an error occurred. */ + } + } else { + /* The connector is already initialized. Nothing to do here. */ + } + + /* + If the data node is still loading, we need to repost the job and *not* increment the execution + pointer (i.e. we need to not fall through to the "done" label). + + There is a hole between here and the where the data connector is initialized where the data + buffer node may have finished initializing. We need to check for this by checking the result of + the data buffer node and whether or not we had an unknown data supply type at the time of + trying to initialize the data connector. + */ + result = ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode); + if (result == MA_BUSY || (result == MA_SUCCESS && isConnectorInitialized == MA_FALSE && dataSupplyType == ma_resource_manager_data_supply_type_unknown)) { + return ma_resource_manager_post_job(pResourceManager, pJob); + } + +done: + /* Only move away from a busy code so that we don't trash any existing error codes. */ + c89atomic_compare_and_swap_i32(&pJob->data.loadDataBuffer.pDataBuffer->result, MA_BUSY, result); + + /* Only signal the other threads after the result has been set just for cleanliness sake. */ + if (pJob->data.loadDataBuffer.pDoneNotification != NULL) { + ma_async_notification_signal(pJob->data.loadDataBuffer.pDoneNotification); + } + if (pJob->data.loadDataBuffer.pDoneFence != NULL) { + ma_fence_release(pJob->data.loadDataBuffer.pDoneFence); + } + + /* + If at this point the data buffer has not had it's connector initialized, it means the + notification event was never signalled which means we need to signal it here. + */ + if (pDataBuffer->isConnectorInitialized == MA_FALSE && result != MA_SUCCESS) { + if (pJob->data.loadDataBuffer.pInitNotification != NULL) { + ma_async_notification_signal(pJob->data.loadDataBuffer.pInitNotification); + } + if (pJob->data.loadDataBuffer.pInitFence != NULL) { + ma_fence_release(pJob->data.loadDataBuffer.pInitFence); + } + } + + c89atomic_fetch_add_32(&pDataBuffer->executionPointer, 1); + return result; +} + +static ma_result ma_resource_manager_process_job__free_data_buffer(ma_resource_manager* pResourceManager, ma_resource_manager_job* pJob) +{ + ma_resource_manager_data_buffer* pDataBuffer; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pJob != NULL); + + pDataBuffer = pJob->data.freeDataBuffer.pDataBuffer; + MA_ASSERT(pDataBuffer != NULL); + + if (pJob->order != c89atomic_load_32(&pDataBuffer->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ + } + + ma_resource_manager_data_buffer_uninit_internal(pDataBuffer); + + /* The event needs to be signalled last. */ + if (pJob->data.freeDataBuffer.pDoneNotification != NULL) { + ma_async_notification_signal(pJob->data.freeDataBuffer.pDoneNotification); + } + + if (pJob->data.freeDataBuffer.pDoneFence != NULL) { + ma_fence_release(pJob->data.freeDataBuffer.pDoneFence); + } + + c89atomic_fetch_add_32(&pDataBuffer->executionPointer, 1); + return MA_SUCCESS; +} + +static ma_result ma_resource_manager_process_job__load_data_stream(ma_resource_manager* pResourceManager, ma_resource_manager_job* pJob) +{ + ma_result result = MA_SUCCESS; + ma_decoder_config decoderConfig; + ma_uint32 pageBufferSizeInBytes; + ma_resource_manager_data_stream* pDataStream; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pJob != NULL); + + pDataStream = pJob->data.loadDataStream.pDataStream; + MA_ASSERT(pDataStream != NULL); + + if (pJob->order != c89atomic_load_32(&pDataStream->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ + } + + if (ma_resource_manager_data_stream_result(pDataStream) != MA_BUSY) { + result = MA_INVALID_OPERATION; /* Most likely the data stream is being uninitialized. */ + goto done; + } + + /* We need to initialize the decoder first so we can determine the size of the pages. */ + decoderConfig = ma_resource_manager__init_decoder_config(pResourceManager); + + if (pJob->data.loadDataStream.pFilePath != NULL) { + result = ma_decoder_init_vfs(pResourceManager->config.pVFS, pJob->data.loadDataStream.pFilePath, &decoderConfig, &pDataStream->decoder); + } else { + result = ma_decoder_init_vfs_w(pResourceManager->config.pVFS, pJob->data.loadDataStream.pFilePathW, &decoderConfig, &pDataStream->decoder); + } + if (result != MA_SUCCESS) { + goto done; + } + + /* Retrieve the total length of the file before marking the decoder are loaded. */ + result = ma_decoder_get_length_in_pcm_frames(&pDataStream->decoder, &pDataStream->totalLengthInPCMFrames); + if (result != MA_SUCCESS) { + goto done; /* Failed to retrieve the length. */ + } + + /* + Only mark the decoder as initialized when the length of the decoder has been retrieved because that can possibly require a scan over the whole file + and we don't want to have another thread trying to access the decoder while it's scanning. + */ + pDataStream->isDecoderInitialized = MA_TRUE; + + /* We have the decoder so we can now initialize our page buffer. */ + pageBufferSizeInBytes = ma_resource_manager_data_stream_get_page_size_in_frames(pDataStream) * 2 * ma_get_bytes_per_frame(pDataStream->decoder.outputFormat, pDataStream->decoder.outputChannels); + + pDataStream->pPageData = ma_malloc(pageBufferSizeInBytes, &pResourceManager->config.allocationCallbacks); + if (pDataStream->pPageData == NULL) { + ma_decoder_uninit(&pDataStream->decoder); + result = MA_OUT_OF_MEMORY; + goto done; + } + + /* Seek to our initial seek point before filling the initial pages. */ + ma_decoder_seek_to_pcm_frame(&pDataStream->decoder, pJob->data.loadDataStream.initialSeekPoint); + + /* We have our decoder and our page buffer, so now we need to fill our pages. */ + ma_resource_manager_data_stream_fill_pages(pDataStream); + + /* And now we're done. We want to make sure the result is MA_SUCCESS. */ + result = MA_SUCCESS; + +done: + ma_free(pJob->data.loadDataStream.pFilePath, &pResourceManager->config.allocationCallbacks); + ma_free(pJob->data.loadDataStream.pFilePathW, &pResourceManager->config.allocationCallbacks); + + /* We can only change the status away from MA_BUSY. If it's set to anything else it means an error has occurred somewhere or the uninitialization process has started (most likely). */ + c89atomic_compare_and_swap_i32(&pDataStream->result, MA_BUSY, result); + + /* Only signal the other threads after the result has been set just for cleanliness sake. */ + if (pJob->data.loadDataStream.pInitNotification != NULL) { + ma_async_notification_signal(pJob->data.loadDataStream.pInitNotification); + } + if (pJob->data.loadDataStream.pInitFence != NULL) { + ma_fence_release(pJob->data.loadDataStream.pInitFence); + } + + c89atomic_fetch_add_32(&pDataStream->executionPointer, 1); + return result; +} + +static ma_result ma_resource_manager_process_job__free_data_stream(ma_resource_manager* pResourceManager, ma_resource_manager_job* pJob) +{ + ma_resource_manager_data_stream* pDataStream; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pJob != NULL); + + pDataStream = pJob->data.freeDataStream.pDataStream; + MA_ASSERT(pDataStream != NULL); + + if (pJob->order != c89atomic_load_32(&pDataStream->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ + } + + /* If our status is not MA_UNAVAILABLE we have a bug somewhere. */ + MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) == MA_UNAVAILABLE); + + if (pDataStream->isDecoderInitialized) { + ma_decoder_uninit(&pDataStream->decoder); + } + + if (pDataStream->pPageData != NULL) { + ma_free(pDataStream->pPageData, &pResourceManager->config.allocationCallbacks); + pDataStream->pPageData = NULL; /* Just in case... */ + } + + ma_data_source_uninit(&pDataStream->ds); + + /* The event needs to be signalled last. */ + if (pJob->data.freeDataStream.pDoneNotification != NULL) { + ma_async_notification_signal(pJob->data.freeDataStream.pDoneNotification); + } + if (pJob->data.freeDataStream.pDoneFence != NULL) { + ma_fence_release(pJob->data.freeDataStream.pDoneFence); + } + + /*c89atomic_fetch_add_32(&pDataStream->executionPointer, 1);*/ + return MA_SUCCESS; +} + +static ma_result ma_resource_manager_process_job__page_data_stream(ma_resource_manager* pResourceManager, ma_resource_manager_job* pJob) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager_data_stream* pDataStream; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pJob != NULL); + + pDataStream = pJob->data.pageDataStream.pDataStream; + MA_ASSERT(pDataStream != NULL); + + if (pJob->order != c89atomic_load_32(&pDataStream->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ + } + + /* For streams, the status should be MA_SUCCESS. */ + if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS) { + result = MA_INVALID_OPERATION; + goto done; + } + + ma_resource_manager_data_stream_fill_page(pDataStream, pJob->data.pageDataStream.pageIndex); + +done: + c89atomic_fetch_add_32(&pDataStream->executionPointer, 1); + return result; +} + +static ma_result ma_resource_manager_process_job__seek_data_stream(ma_resource_manager* pResourceManager, ma_resource_manager_job* pJob) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager_data_stream* pDataStream; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pJob != NULL); + + pDataStream = pJob->data.seekDataStream.pDataStream; + MA_ASSERT(pDataStream != NULL); + + if (pJob->order != c89atomic_load_32(&pDataStream->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ + } + + /* For streams the status should be MA_SUCCESS for this to do anything. */ + if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS || pDataStream->isDecoderInitialized == MA_FALSE) { + result = MA_INVALID_OPERATION; + goto done; + } + + /* + With seeking we just assume both pages are invalid and the relative frame cursor at position 0. This is basically exactly the same as loading, except + instead of initializing the decoder, we seek to a frame. + */ + ma_decoder_seek_to_pcm_frame(&pDataStream->decoder, pJob->data.seekDataStream.frameIndex); + + /* After seeking we'll need to reload the pages. */ + ma_resource_manager_data_stream_fill_pages(pDataStream); + + /* We need to let the public API know that we're done seeking. */ + c89atomic_fetch_sub_32(&pDataStream->seekCounter, 1); + +done: + c89atomic_fetch_add_32(&pDataStream->executionPointer, 1); + return result; +} + +MA_API ma_result ma_resource_manager_process_job(ma_resource_manager* pResourceManager, ma_resource_manager_job* pJob) +{ + if (pResourceManager == NULL || pJob == NULL) { + return MA_INVALID_ARGS; + } + + switch (pJob->toc.breakup.code) + { + /* Data Buffer Node */ + case MA_RESOURCE_MANAGER_JOB_LOAD_DATA_BUFFER_NODE: return ma_resource_manager_process_job__load_data_buffer_node(pResourceManager, pJob); + case MA_RESOURCE_MANAGER_JOB_FREE_DATA_BUFFER_NODE: return ma_resource_manager_process_job__free_data_buffer_node(pResourceManager, pJob); + case MA_RESOURCE_MANAGER_JOB_PAGE_DATA_BUFFER_NODE: return ma_resource_manager_process_job__page_data_buffer_node(pResourceManager, pJob); + + /* Data Buffer */ + case MA_RESOURCE_MANAGER_JOB_LOAD_DATA_BUFFER: return ma_resource_manager_process_job__load_data_buffer(pResourceManager, pJob); + case MA_RESOURCE_MANAGER_JOB_FREE_DATA_BUFFER: return ma_resource_manager_process_job__free_data_buffer(pResourceManager, pJob); + + /* Data Stream */ + case MA_RESOURCE_MANAGER_JOB_LOAD_DATA_STREAM: return ma_resource_manager_process_job__load_data_stream(pResourceManager, pJob); + case MA_RESOURCE_MANAGER_JOB_FREE_DATA_STREAM: return ma_resource_manager_process_job__free_data_stream(pResourceManager, pJob); + case MA_RESOURCE_MANAGER_JOB_PAGE_DATA_STREAM: return ma_resource_manager_process_job__page_data_stream(pResourceManager, pJob); + case MA_RESOURCE_MANAGER_JOB_SEEK_DATA_STREAM: return ma_resource_manager_process_job__seek_data_stream(pResourceManager, pJob); + + default: break; + } + + /* Getting here means we don't know what the job code is and cannot do anything with it. */ + return MA_INVALID_OPERATION; +} + +MA_API ma_result ma_resource_manager_process_next_job(ma_resource_manager* pResourceManager) +{ + ma_result result; + ma_resource_manager_job job; + + if (pResourceManager == NULL) { + return MA_INVALID_ARGS; + } + + /* This will return MA_CANCELLED if the next job is a quit job. */ + result = ma_resource_manager_next_job(pResourceManager, &job); + if (result != MA_SUCCESS) { + return result; + } + + return ma_resource_manager_process_job(pResourceManager, &job); +} +#endif /* MA_NO_RESOURCE_MANAGER */ + + +#ifndef MA_NO_NODE_GRAPH +/* 10ms @ 48K = 480. Must never exceed 65535. */ +#ifndef MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS +#define MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS 480 +#endif + + +static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusIndex, float* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead, ma_uint64 globalTime); + +MA_API void ma_debug_fill_pcm_frames_with_sine_wave(float* pFramesOut, ma_uint32 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate) +{ + #ifndef MA_NO_GENERATION + { + ma_waveform_config waveformConfig; + ma_waveform waveform; + + waveformConfig = ma_waveform_config_init(format, channels, sampleRate, ma_waveform_type_sine, 1.0, 400); + ma_waveform_init(&waveformConfig, &waveform); + ma_waveform_read_pcm_frames(&waveform, pFramesOut, frameCount, NULL); + } + #else + { + (void)pFramesOut; + (void)frameCount; + (void)format; + (void)channels; + (void)sampleRate; + #if defined(MA_DEBUG_OUTPUT) + { + #if _MSC_VER + #pragma message ("ma_debug_fill_pcm_frames_with_sine_wave() will do nothing because MA_NO_GENERATION is enabled.") + #endif + } + #endif + } + #endif +} + + + +static ma_result ma_mix_pcm_frames_f32(float* pDst, const float* pSrc, ma_uint64 frameCount, ma_uint32 channels, float volume) +{ + ma_uint64 iSample; + ma_uint64 sampleCount; + + if (pDst == NULL || pSrc == NULL || channels == 0) { + return MA_INVALID_ARGS; + } + + if (volume == 0) { + return MA_SUCCESS; /* No changes if the volume is 0. */ + } + + sampleCount = frameCount * channels; + + if (volume == 1) { + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pDst[iSample] += pSrc[iSample]; + } + } else { + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pDst[iSample] += ma_apply_volume_unclipped_f32(pSrc[iSample], volume); + } + } + + return MA_SUCCESS; +} + + +MA_API ma_node_graph_config ma_node_graph_config_init(ma_uint32 channels) +{ + ma_node_graph_config config; + + MA_ZERO_OBJECT(&config); + config.channels = channels; + + return config; +} + + +static void ma_node_graph_set_is_reading(ma_node_graph* pNodeGraph, ma_bool32 isReading) +{ + MA_ASSERT(pNodeGraph != NULL); + c89atomic_exchange_32(&pNodeGraph->isReading, isReading); +} + +#if 0 +static ma_bool32 ma_node_graph_is_reading(ma_node_graph* pNodeGraph) +{ + MA_ASSERT(pNodeGraph != NULL); + return c89atomic_load_32(&pNodeGraph->isReading); +} +#endif + + +static void ma_node_graph_endpoint_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + MA_ASSERT(pNode != NULL); + MA_ASSERT(ma_node_get_input_bus_count(pNode) == 1); + MA_ASSERT(ma_node_get_output_bus_count(pNode) == 1); + + /* Input channel count needs to be the same as the output channel count. */ + MA_ASSERT(ma_node_get_input_channels(pNode, 0) == ma_node_get_output_channels(pNode, 0)); + + /* We don't need to do anything here because it's a passthrough. */ + (void)pNode; + (void)ppFramesIn; + (void)pFrameCountIn; + (void)ppFramesOut; + (void)pFrameCountOut; + +#if 0 + /* The data has already been mixed. We just need to move it to the output buffer. */ + if (ppFramesIn != NULL) { + ma_copy_pcm_frames(ppFramesOut[0], ppFramesIn[0], *pFrameCountOut, ma_format_f32, ma_node_get_output_channels(pNode, 0)); + } +#endif +} + +static ma_node_vtable g_node_graph_endpoint_vtable = +{ + ma_node_graph_endpoint_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* 1 input bus. */ + 1, /* 1 output bus. */ + MA_NODE_FLAG_PASSTHROUGH /* Flags. The endpoint is a passthrough. */ +}; + +MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node_graph* pNodeGraph) +{ + ma_result result; + ma_node_config endpointConfig; + + if (pNodeGraph == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNodeGraph); + + endpointConfig = ma_node_config_init(); + endpointConfig.vtable = &g_node_graph_endpoint_vtable; + endpointConfig.pInputChannels = &pConfig->channels; + endpointConfig.pOutputChannels = &pConfig->channels; + + result = ma_node_init(pNodeGraph, &endpointConfig, pAllocationCallbacks, &pNodeGraph->endpoint); + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + +MA_API void ma_node_graph_uninit(ma_node_graph* pNodeGraph, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pNodeGraph == NULL) { + return; + } + + ma_node_uninit(&pNodeGraph->endpoint, pAllocationCallbacks); +} + +MA_API ma_node* ma_node_graph_get_endpoint(ma_node_graph* pNodeGraph) +{ + if (pNodeGraph == NULL) { + return NULL; + } + + return &pNodeGraph->endpoint; +} + +MA_API ma_result ma_node_graph_read_pcm_frames(ma_node_graph* pNodeGraph, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + ma_result result = MA_SUCCESS; + ma_uint64 totalFramesRead; + ma_uint32 channels; + + if (pFramesRead != NULL) { + *pFramesRead = 0; /* Safety. */ + } + + if (pNodeGraph == NULL) { + return MA_INVALID_ARGS; + } + + channels = ma_node_get_output_channels(&pNodeGraph->endpoint, 0); + + + /* We'll be nice and try to do a full read of all frameCount frames. */ + totalFramesRead = 0; + while (totalFramesRead < frameCount) { + ma_uint32 framesJustRead; + ma_uint64 framesToRead = frameCount - totalFramesRead; + + if (framesToRead > 0xFFFFFFFF) { + framesToRead = 0xFFFFFFFF; + } + + ma_node_graph_set_is_reading(pNodeGraph, MA_TRUE); + { + result = ma_node_read_pcm_frames(&pNodeGraph->endpoint, 0, (float*)ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, ma_format_f32, channels), (ma_uint32)framesToRead, &framesJustRead, ma_node_get_time(&pNodeGraph->endpoint)); + } + ma_node_graph_set_is_reading(pNodeGraph, MA_FALSE); + + totalFramesRead += framesJustRead; + + if (result != MA_SUCCESS) { + break; + } + + /* Abort if we weren't able to read any frames or else we risk getting stuck in a loop. */ + if (framesJustRead == 0) { + break; + } + } + + /* Let's go ahead and silence any leftover frames just for some added safety to ensure the caller doesn't try emitting garbage out of the speakers. */ + if (totalFramesRead < frameCount) { + ma_silence_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, ma_format_f32, channels), (frameCount - totalFramesRead), ma_format_f32, channels); + } + + if (pFramesRead != NULL) { + *pFramesRead = totalFramesRead; + } + + return result; +} + +MA_API ma_uint32 ma_node_graph_get_channels(const ma_node_graph* pNodeGraph) +{ + if (pNodeGraph == NULL) { + return 0; + } + + return ma_node_get_output_channels(&pNodeGraph->endpoint, 0); +} + +MA_API ma_uint64 ma_node_graph_get_time(const ma_node_graph* pNodeGraph) +{ + if (pNodeGraph == NULL) { + return 0; + } + + return ma_node_get_time(&pNodeGraph->endpoint); /* Global time is just the local time of the endpoint. */ +} + +MA_API ma_result ma_node_graph_set_time(ma_node_graph* pNodeGraph, ma_uint64 globalTime) +{ + if (pNodeGraph == NULL) { + return MA_INVALID_ARGS; + } + + return ma_node_set_time(&pNodeGraph->endpoint, globalTime); /* Global time is just the local time of the endpoint. */ +} + + + +static ma_result ma_node_output_bus_init(ma_node* pNode, ma_uint32 outputBusIndex, ma_uint32 channels, ma_node_output_bus* pOutputBus) +{ + MA_ASSERT(pOutputBus != NULL); + MA_ASSERT(outputBusIndex < MA_MAX_NODE_BUS_COUNT); + MA_ASSERT(outputBusIndex < ma_node_get_output_bus_count(pNode)); + MA_ASSERT(channels < 256); + + MA_ZERO_OBJECT(pOutputBus); + + if (channels == 0) { + return MA_INVALID_ARGS; + } + + pOutputBus->pNode = pNode; + pOutputBus->outputBusIndex = (ma_uint8)outputBusIndex; + pOutputBus->channels = (ma_uint8)channels; + pOutputBus->flags = MA_NODE_OUTPUT_BUS_FLAG_HAS_READ; /* <-- Important that this flag is set by default. */ + pOutputBus->volume = 1; + + return MA_SUCCESS; +} + +static void ma_node_output_bus_lock(ma_node_output_bus* pOutputBus) +{ + ma_spinlock_lock(&pOutputBus->lock); +} + +static void ma_node_output_bus_unlock(ma_node_output_bus* pOutputBus) +{ + ma_spinlock_unlock(&pOutputBus->lock); +} + + +static ma_uint32 ma_node_output_bus_get_channels(const ma_node_output_bus* pOutputBus) +{ + return pOutputBus->channels; +} + + +static void ma_node_output_bus_set_has_read(ma_node_output_bus* pOutputBus, ma_bool32 hasRead) +{ + if (hasRead) { + c89atomic_fetch_or_32(&pOutputBus->flags, MA_NODE_OUTPUT_BUS_FLAG_HAS_READ); + } else { + c89atomic_fetch_and_32(&pOutputBus->flags, (ma_uint32)~MA_NODE_OUTPUT_BUS_FLAG_HAS_READ); + } +} + +static ma_bool32 ma_node_output_bus_has_read(ma_node_output_bus* pOutputBus) +{ + return (c89atomic_load_32(&pOutputBus->flags) & MA_NODE_OUTPUT_BUS_FLAG_HAS_READ) != 0; +} + + +static void ma_node_output_bus_set_is_attached(ma_node_output_bus* pOutputBus, ma_bool32 isAttached) +{ + c89atomic_exchange_32(&pOutputBus->isAttached, isAttached); +} + +static ma_bool32 ma_node_output_bus_is_attached(ma_node_output_bus* pOutputBus) +{ + return c89atomic_load_32(&pOutputBus->isAttached); +} + + +static ma_result ma_node_output_bus_set_volume(ma_node_output_bus* pOutputBus, float volume) +{ + MA_ASSERT(pOutputBus != NULL); + + if (volume < 0.0f) { + volume = 0.0f; + } + + c89atomic_exchange_f32(&pOutputBus->volume, volume); + + return MA_SUCCESS; +} + +static float ma_node_output_bus_get_volume(const ma_node_output_bus* pOutputBus) +{ + return c89atomic_load_f32((float*)&pOutputBus->volume); +} + + +static ma_result ma_node_input_bus_init(ma_uint32 channels, ma_node_input_bus* pInputBus) +{ + MA_ASSERT(pInputBus != NULL); + MA_ASSERT(channels < 256); + + MA_ZERO_OBJECT(pInputBus); + + if (channels == 0) { + return MA_INVALID_ARGS; + } + + pInputBus->channels = (ma_uint8)channels; + + return MA_SUCCESS; +} + +static void ma_node_input_bus_lock(ma_node_input_bus* pInputBus) +{ + ma_spinlock_lock(&pInputBus->lock); +} + +static void ma_node_input_bus_unlock(ma_node_input_bus* pInputBus) +{ + ma_spinlock_unlock(&pInputBus->lock); +} + + +static void ma_node_input_bus_next_begin(ma_node_input_bus* pInputBus) +{ + c89atomic_fetch_add_32(&pInputBus->nextCounter, 1); +} + +static void ma_node_input_bus_next_end(ma_node_input_bus* pInputBus) +{ + c89atomic_fetch_sub_32(&pInputBus->nextCounter, 1); +} + +static ma_uint32 ma_node_input_bus_get_next_counter(ma_node_input_bus* pInputBus) +{ + return c89atomic_load_32(&pInputBus->nextCounter); +} + + +static ma_uint32 ma_node_input_bus_get_channels(const ma_node_input_bus* pInputBus) +{ + return pInputBus->channels; +} + + +static void ma_node_input_bus_detach__no_output_bus_lock(ma_node_input_bus* pInputBus, ma_node_output_bus* pOutputBus) +{ + MA_ASSERT(pInputBus != NULL); + MA_ASSERT(pOutputBus != NULL); + + /* + Mark the output bus as detached first. This will prevent future iterations on the audio thread + from iterating this output bus. + */ + ma_node_output_bus_set_is_attached(pOutputBus, MA_FALSE); + + /* + We cannot use the output bus lock here since it'll be getting used at a higher level, but we do + still need to use the input bus lock since we'll be updating pointers on two different output + buses. The same rules apply here as the attaching case. Although we're using a lock here, we're + *not* using a lock when iterating over the list in the audio thread. We therefore need to craft + this in a way such that the iteration on the audio thread doesn't break. + + The the first thing to do is swap out the "next" pointer of the previous output bus with the + new "next" output bus. This is the operation that matters for iteration on the audio thread. + After that, the previous pointer on the new "next" pointer needs to be updated, after which + point the linked list will be in a good state. + */ + ma_node_input_bus_lock(pInputBus); + { + ma_node_output_bus* pOldPrev = (ma_node_output_bus*)c89atomic_load_ptr(&pOutputBus->pPrev); + ma_node_output_bus* pOldNext = (ma_node_output_bus*)c89atomic_load_ptr(&pOutputBus->pNext); + + if (pOldPrev != NULL) { + c89atomic_exchange_ptr(&pOldPrev->pNext, pOldNext); /* <-- This is where the output bus is detached from the list. */ + } + if (pOldNext != NULL) { + c89atomic_exchange_ptr(&pOldNext->pPrev, pOldPrev); /* <-- This is required for detachment. */ + } + } + ma_node_input_bus_unlock(pInputBus); + + /* At this point the output bus is detached and the linked list is completely unaware of it. Reset some data for safety. */ + c89atomic_exchange_ptr(&pOutputBus->pNext, NULL); /* Using atomic exchanges here, mainly for the benefit of analysis tools which don't always recognize spinlocks. */ + c89atomic_exchange_ptr(&pOutputBus->pPrev, NULL); /* As above. */ + pOutputBus->pInputNode = NULL; + pOutputBus->inputNodeInputBusIndex = 0; + + + /* + For thread-safety reasons, we don't want to be returning from this straight away. We need to + wait for the audio thread to finish with the output bus. There's two things we need to wait + for. The first is the part that selects the next output bus in the list, and the other is the + part that reads from the output bus. Basically all we're doing is waiting for the input bus + to stop referencing the output bus. + + We're doing this part last because we want the section above to run while the audio thread + is finishing up with the output bus, just for efficiency reasons. We marked the output bus as + detached right at the top of this function which is going to prevent the audio thread from + iterating the output bus again. + */ + + /* Part 1: Wait for the current iteration to complete. */ + while (ma_node_input_bus_get_next_counter(pInputBus) > 0) { + ma_yield(); + } + + /* Part 2: Wait for any reads to complete. */ + while (c89atomic_load_32(&pOutputBus->refCount) > 0) { + ma_yield(); + } + + /* + At this point we're done detaching and we can be guaranteed that the audio thread is not going + to attempt to reference this output bus again (until attached again). + */ +} + +#if 0 /* Not used at the moment, but leaving here in case I need it later. */ +static void ma_node_input_bus_detach(ma_node_input_bus* pInputBus, ma_node_output_bus* pOutputBus) +{ + MA_ASSERT(pInputBus != NULL); + MA_ASSERT(pOutputBus != NULL); + + ma_node_output_bus_lock(pOutputBus); + { + ma_node_input_bus_detach__no_output_bus_lock(pInputBus, pOutputBus); + } + ma_node_output_bus_unlock(pOutputBus); +} +#endif + +static void ma_node_input_bus_attach(ma_node_input_bus* pInputBus, ma_node_output_bus* pOutputBus, ma_node* pNewInputNode, ma_uint32 inputNodeInputBusIndex) +{ + MA_ASSERT(pInputBus != NULL); + MA_ASSERT(pOutputBus != NULL); + + ma_node_output_bus_lock(pOutputBus); + { + ma_node_output_bus* pOldInputNode = (ma_node_output_bus*)c89atomic_load_ptr(&pOutputBus->pInputNode); + + /* Detach from any existing attachment first if necessary. */ + if (pOldInputNode != NULL) { + ma_node_input_bus_detach__no_output_bus_lock(pInputBus, pOutputBus); + } + + /* + At this point we can be sure the output bus is not attached to anything. The linked list in the + old input bus has been updated so that pOutputBus will not get iterated again. + */ + pOutputBus->pInputNode = pNewInputNode; /* No need for an atomic assignment here because modification of this variable always happens within a lock. */ + pOutputBus->inputNodeInputBusIndex = (ma_uint8)inputNodeInputBusIndex; /* As above. */ + + /* + Now we need to attach the output bus to the linked list. This involves updating two pointers on + two different output buses so I'm going to go ahead and keep this simple and just use a lock. + There are ways to do this without a lock, but it's just too hard to maintain for it's value. + + Although we're locking here, it's important to remember that we're *not* locking when iterating + and reading audio data since that'll be running on the audio thread. As a result we need to be + careful how we craft this so that we don't break iteration. What we're going to do is always + attach the new item so that it becomes the first item in the list. That way, as we're iterating + we won't break any links in the list and iteration will continue safely. The detaching case will + also be crafted in a way as to not break list iteration. It's important to remember to use + atomic exchanges here since no locking is happening on the audio thread during iteration. + */ + ma_node_input_bus_lock(pInputBus); + { + ma_node_output_bus* pNewPrev = &pInputBus->head; + ma_node_output_bus* pNewNext = (ma_node_output_bus*)c89atomic_load_ptr(&pInputBus->head.pNext); + + /* Update the local output bus. */ + c89atomic_exchange_ptr(&pOutputBus->pPrev, pNewPrev); + c89atomic_exchange_ptr(&pOutputBus->pNext, pNewNext); + + /* Update the other output buses to point back to the local output bus. */ + c89atomic_exchange_ptr(&pInputBus->head.pNext, pOutputBus); /* <-- This is where the output bus is actually attached to the input bus. */ + + /* Do the previous pointer last. This is only used for detachment. */ + if (pNewNext != NULL) { + c89atomic_exchange_ptr(&pNewNext->pPrev, pOutputBus); + } + } + ma_node_input_bus_unlock(pInputBus); + + /* + Mark the node as attached last. This is used to controlling whether or the output bus will be + iterated on the audio thread. Mainly required for detachment purposes. + */ + ma_node_output_bus_set_is_attached(pOutputBus, MA_TRUE); + } + ma_node_output_bus_unlock(pOutputBus); +} + +static ma_node_output_bus* ma_node_input_bus_next(ma_node_input_bus* pInputBus, ma_node_output_bus* pOutputBus) +{ + ma_node_output_bus* pNext; + + MA_ASSERT(pInputBus != NULL); + + if (pOutputBus == NULL) { + return NULL; + } + + ma_node_input_bus_next_begin(pInputBus); + { + pNext = pOutputBus; + for (;;) { + pNext = (ma_node_output_bus*)c89atomic_load_ptr(&pNext->pNext); + if (pNext == NULL) { + break; /* Reached the end. */ + } + + if (ma_node_output_bus_is_attached(pNext) == MA_FALSE) { + continue; /* The node is not attached. Keep checking. */ + } + + /* The next node has been selected. */ + break; + } + + /* We need to increment the reference count of the selected node. */ + if (pNext != NULL) { + c89atomic_fetch_add_32(&pNext->refCount, 1); + } + + /* The previous node is no longer being referenced. */ + c89atomic_fetch_sub_32(&pOutputBus->refCount, 1); + } + ma_node_input_bus_next_end(pInputBus); + + return pNext; +} + +static ma_node_output_bus* ma_node_input_bus_first(ma_node_input_bus* pInputBus) +{ + return ma_node_input_bus_next(pInputBus, &pInputBus->head); +} + + + +static ma_result ma_node_input_bus_read_pcm_frames(ma_node* pInputNode, ma_node_input_bus* pInputBus, float* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead, ma_uint64 globalTime) +{ + ma_result result = MA_SUCCESS; + ma_node_output_bus* pOutputBus; + ma_node_output_bus* pFirst; + ma_uint32 inputChannels; + + /* + This will be called from the audio thread which means we can't be doing any locking. Basically, + this function will not perfom any locking, whereas attaching and detaching will, but crafted in + such a way that we don't need to perform any locking here. The important thing to remember is + to always iterate in a forward direction. + + In order to process any data we need to first read from all input buses. That's where this + function comes in. This iterates over each of the attachments and accumulates/mixes them. We + also convert the channels to the nodes output channel count before mixing. We want to do this + channel conversion so that the caller of this function can invoke the processing callback + without having to do it themselves. + + When we iterate over each of the attachments on the input bus, we need to read as much data as + we can from each of them so that we don't end up with holes between each of the attachments. To + do this, we need to read from each attachment in a loop and read as many frames as we can, up + to `frameCount`. + */ + MA_ASSERT(pInputNode != NULL); + MA_ASSERT(pFramesRead != NULL); /* pFramesRead is critical and must always be specified. On input it's undefined and on output it'll be set to the number of frames actually read. */ + + *pFramesRead = 0; /* Safety. */ + + inputChannels = ma_node_input_bus_get_channels(pInputBus); + + /* + We need to be careful with how we call ma_node_input_bus_first() and ma_node_input_bus_next(). They + are both critical to our lock-free thread-safety system. We can only call ma_node_input_bus_first() + once per iteration, however we have an optimization to checks whether or not it's the first item in + the list. We therefore need to store a pointer to the first item rather than repeatedly calling + ma_node_input_bus_first(). It's safe to keep hold of this pointer, so long as we don't dereference it + after calling ma_node_input_bus_next(), which we won't be. + */ + pFirst = ma_node_input_bus_first(pInputBus); + if (pFirst == NULL) { + return MA_SUCCESS; /* No attachments. Read nothing. */ + } + + for (pOutputBus = pFirst; pOutputBus != NULL; pOutputBus = ma_node_input_bus_next(pInputBus, pOutputBus)) { + ma_uint32 framesProcessed = 0; + + MA_ASSERT(pOutputBus->pNode != NULL); + + if (pFramesOut != NULL) { + /* Read. */ + float temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE / sizeof(float)]; + ma_uint32 tempCapInFrames = ma_countof(temp) / inputChannels; + + while (framesProcessed < frameCount) { + float* pRunningFramesOut; + ma_uint32 framesToRead; + ma_uint32 framesJustRead; + + framesToRead = frameCount - framesProcessed; + if (framesToRead > tempCapInFrames) { + framesToRead = tempCapInFrames; + } + + pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(pFramesOut, framesProcessed, inputChannels); + + if (pOutputBus == pFirst) { + /* Fast path. First attachment. We just read straight into the output buffer (no mixing required). */ + result = ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, pRunningFramesOut, framesToRead, &framesJustRead, globalTime + framesProcessed); + } else { + /* Slow path. Not the first attachment. Mixing required. */ + result = ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, temp, framesToRead, &framesJustRead, globalTime + framesProcessed); + if (result == MA_SUCCESS || result == MA_AT_END) { + ma_mix_pcm_frames_f32(pRunningFramesOut, temp, framesJustRead, inputChannels, /*volume*/1); + } + } + + framesProcessed += framesJustRead; + + /* If we reached the end or otherwise failed to read any data we need to finish up with this output node. */ + if (result != MA_SUCCESS) { + break; + } + + /* If we didn't read anything, abort so we don't get stuck in a loop. */ + if (framesJustRead == 0) { + break; + } + } + + /* If it's the first attachment we didn't do any mixing. Any leftover samples need to be silenced. */ + if (pOutputBus == pFirst && framesProcessed < frameCount) { + ma_silence_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, framesProcessed, ma_format_f32, inputChannels), (frameCount - framesProcessed), ma_format_f32, inputChannels); + } + } else { + /* Seek. */ + ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, NULL, frameCount, &framesProcessed, globalTime); + } + } + + /* In this path we always "process" the entire amount. */ + *pFramesRead = frameCount; + + return result; +} + + +MA_API ma_node_config ma_node_config_init(void) +{ + ma_node_config config; + + MA_ZERO_OBJECT(&config); + config.initialState = ma_node_state_started; /* Nodes are started by default. */ + config.inputBusCount = MA_NODE_BUS_COUNT_UNKNOWN; + config.outputBusCount = MA_NODE_BUS_COUNT_UNKNOWN; + + return config; +} + + + +static ma_result ma_node_detach_full(ma_node* pNode); + +static float* ma_node_get_cached_input_ptr(ma_node* pNode, ma_uint32 inputBusIndex) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_uint32 iInputBus; + float* pBasePtr; + + MA_ASSERT(pNodeBase != NULL); + + /* Input data is stored at the front of the buffer. */ + pBasePtr = pNodeBase->pCachedData; + for (iInputBus = 0; iInputBus < inputBusIndex; iInputBus += 1) { + pBasePtr += pNodeBase->cachedDataCapInFramesPerBus * ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[iInputBus]); + } + + return pBasePtr; +} + +static float* ma_node_get_cached_output_ptr(ma_node* pNode, ma_uint32 outputBusIndex) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_uint32 iInputBus; + ma_uint32 iOutputBus; + float* pBasePtr; + + MA_ASSERT(pNodeBase != NULL); + + /* Cached output data starts after the input data. */ + pBasePtr = pNodeBase->pCachedData; + for (iInputBus = 0; iInputBus < ma_node_get_input_bus_count(pNodeBase); iInputBus += 1) { + pBasePtr += pNodeBase->cachedDataCapInFramesPerBus * ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[iInputBus]); + } + + for (iOutputBus = 0; iOutputBus < outputBusIndex; iOutputBus += 1) { + pBasePtr += pNodeBase->cachedDataCapInFramesPerBus * ma_node_output_bus_get_channels(&pNodeBase->pOutputBuses[iOutputBus]); + } + + return pBasePtr; +} + + +typedef struct +{ + size_t sizeInBytes; + size_t inputBusOffset; + size_t outputBusOffset; + size_t cachedDataOffset; + ma_uint32 inputBusCount; /* So it doesn't have to be calculated twice. */ + ma_uint32 outputBusCount; /* So it doesn't have to be calculated twice. */ +} ma_node_heap_layout; + +static ma_result ma_node_translate_bus_counts(const ma_node_config* pConfig, ma_uint32* pInputBusCount, ma_uint32* pOutputBusCount) +{ + ma_uint32 inputBusCount; + ma_uint32 outputBusCount; + + MA_ASSERT(pConfig != NULL); + MA_ASSERT(pInputBusCount != NULL); + MA_ASSERT(pOutputBusCount != NULL); + + /* Bus counts are determined by the vtable, unless they're set to `MA_NODE_BUS_COUNT_UNKNWON`, in which case they're taken from the config. */ + if (pConfig->vtable->inputBusCount == MA_NODE_BUS_COUNT_UNKNOWN) { + inputBusCount = pConfig->inputBusCount; + } else { + inputBusCount = pConfig->vtable->inputBusCount; + + if (pConfig->inputBusCount != MA_NODE_BUS_COUNT_UNKNOWN && pConfig->inputBusCount != pConfig->vtable->inputBusCount) { + return MA_INVALID_ARGS; /* Invalid configuration. You must not specify a conflicting bus count between the node's config and the vtable. */ + } + } + + if (pConfig->vtable->outputBusCount == MA_NODE_BUS_COUNT_UNKNOWN) { + outputBusCount = pConfig->outputBusCount; + } else { + outputBusCount = pConfig->vtable->outputBusCount; + + if (pConfig->outputBusCount != MA_NODE_BUS_COUNT_UNKNOWN && pConfig->outputBusCount != pConfig->vtable->outputBusCount) { + return MA_INVALID_ARGS; /* Invalid configuration. You must not specify a conflicting bus count between the node's config and the vtable. */ + } + } + + /* Bus counts must be within limits. */ + if (inputBusCount > MA_MAX_NODE_BUS_COUNT || outputBusCount > MA_MAX_NODE_BUS_COUNT) { + return MA_INVALID_ARGS; + } + + + /* We must have channel counts for each bus. */ + if ((inputBusCount > 0 && pConfig->pInputChannels == NULL) || (outputBusCount > 0 && pConfig->pOutputChannels == NULL)) { + return MA_INVALID_ARGS; /* You must specify channel counts for each input and output bus. */ + } + + + /* Some special rules for passthrough nodes. */ + if ((pConfig->vtable->flags & MA_NODE_FLAG_PASSTHROUGH) != 0) { + if (pConfig->vtable->inputBusCount != 1 || pConfig->vtable->outputBusCount != 1) { + return MA_INVALID_ARGS; /* Passthrough nodes must have exactly 1 input bus and 1 output bus. */ + } + + if (pConfig->pInputChannels[0] != pConfig->pOutputChannels[0]) { + return MA_INVALID_ARGS; /* Passthrough nodes must have the same number of channels between input and output nodes. */ + } + } + + + *pInputBusCount = inputBusCount; + *pOutputBusCount = outputBusCount; + + return MA_SUCCESS; +} + +static ma_result ma_node_get_heap_layout(const ma_node_config* pConfig, ma_node_heap_layout* pHeapLayout) +{ + ma_result result; + ma_uint32 inputBusCount; + ma_uint32 outputBusCount; + + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL || pConfig->vtable == NULL || pConfig->vtable->onProcess == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_node_translate_bus_counts(pConfig, &inputBusCount, &outputBusCount); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes = 0; + + /* Input buses. */ + if (inputBusCount > MA_MAX_NODE_LOCAL_BUS_COUNT) { + pHeapLayout->inputBusOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(sizeof(ma_node_input_bus) * inputBusCount); + } else { + pHeapLayout->inputBusOffset = MA_SIZE_MAX; /* MA_SIZE_MAX indicates that no heap allocation is required for the input bus. */ + } + + /* Output buses. */ + if (outputBusCount > MA_MAX_NODE_LOCAL_BUS_COUNT) { + pHeapLayout->outputBusOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(sizeof(ma_node_output_bus) * outputBusCount); + } else { + pHeapLayout->outputBusOffset = MA_SIZE_MAX; + } + + /* + Cached audio data. + + We need to allocate memory for a caching both input and output data. We have an optimization + where no caching is necessary for specific conditions: + + - The node has 0 inputs and 1 output. + + When a node meets the above conditions, no cache is allocated. + + The size choice for this buffer is a little bit finicky. We don't want to be too wasteful by + allocating too much, but at the same time we want it be large enough so that enough frames can + be processed for each call to ma_node_read_pcm_frames() so that it keeps things efficient. For + now I'm going with 10ms @ 48K which is 480 frames per bus. This is configurable at compile + time. It might also be worth investigating whether or not this can be configured at run time. + */ + if (inputBusCount == 0 && outputBusCount == 1) { + /* Fast path. No cache needed. */ + pHeapLayout->cachedDataOffset = MA_SIZE_MAX; + } else { + /* Slow path. Cache needed. */ + size_t cachedDataSizeInBytes = 0; + ma_uint32 iBus; + + MA_ASSERT(MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS <= 0xFFFF); /* Clamped to 16 bits. */ + + for (iBus = 0; iBus < inputBusCount; iBus += 1) { + cachedDataSizeInBytes += MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS * ma_get_bytes_per_frame(ma_format_f32, pConfig->pInputChannels[iBus]); + } + + for (iBus = 0; iBus < outputBusCount; iBus += 1) { + cachedDataSizeInBytes += MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS * ma_get_bytes_per_frame(ma_format_f32, pConfig->pOutputChannels[iBus]); + } + + pHeapLayout->cachedDataOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(cachedDataSizeInBytes); + } + + + /* + Not technically part of the heap, but we can output the input and output bus counts so we can + avoid a redundant call to ma_node_translate_bus_counts(). + */ + pHeapLayout->inputBusCount = inputBusCount; + pHeapLayout->outputBusCount = outputBusCount; + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_node_get_heap_size(const ma_node_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_node_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_node_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_node_init_preallocated(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, void* pHeap, ma_node* pNode) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_result result; + ma_node_heap_layout heapLayout; + ma_uint32 iInputBus; + ma_uint32 iOutputBus; + + if (pNodeBase == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNodeBase); + + result = ma_node_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pNodeBase->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pNodeBase->pNodeGraph = pNodeGraph; + pNodeBase->vtable = pConfig->vtable; + pNodeBase->state = pConfig->initialState; + pNodeBase->stateTimes[ma_node_state_started] = 0; + pNodeBase->stateTimes[ma_node_state_stopped] = (ma_uint64)(ma_int64)-1; /* Weird casting for VC6 compatibility. */ + pNodeBase->inputBusCount = heapLayout.inputBusCount; + pNodeBase->outputBusCount = heapLayout.outputBusCount; + + if (heapLayout.inputBusOffset != MA_SIZE_MAX) { + pNodeBase->pInputBuses = (ma_node_input_bus*)ma_offset_ptr(pHeap, heapLayout.inputBusOffset); + } else { + pNodeBase->pInputBuses = pNodeBase->_inputBuses; + } + + if (heapLayout.outputBusOffset != MA_SIZE_MAX) { + pNodeBase->pOutputBuses = (ma_node_output_bus*)ma_offset_ptr(pHeap, heapLayout.inputBusOffset); + } else { + pNodeBase->pOutputBuses = pNodeBase->_outputBuses; + } + + if (heapLayout.cachedDataOffset != MA_SIZE_MAX) { + pNodeBase->pCachedData = (float*)ma_offset_ptr(pHeap, heapLayout.cachedDataOffset); + pNodeBase->cachedDataCapInFramesPerBus = MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS; + } else { + pNodeBase->pCachedData = NULL; + } + + + + /* We need to run an initialization step for each input and output bus. */ + for (iInputBus = 0; iInputBus < ma_node_get_input_bus_count(pNodeBase); iInputBus += 1) { + result = ma_node_input_bus_init(pConfig->pInputChannels[iInputBus], &pNodeBase->pInputBuses[iInputBus]); + if (result != MA_SUCCESS) { + return result; + } + } + + for (iOutputBus = 0; iOutputBus < ma_node_get_output_bus_count(pNodeBase); iOutputBus += 1) { + result = ma_node_output_bus_init(pNodeBase, iOutputBus, pConfig->pOutputChannels[iOutputBus], &pNodeBase->pOutputBuses[iOutputBus]); + if (result != MA_SUCCESS) { + return result; + } + } + + + /* The cached data needs to be initialized to silence (or a sine wave tone if we're debugging). */ + if (pNodeBase->pCachedData != NULL) { + ma_uint32 iBus; + + #if 1 /* Toggle this between 0 and 1 to turn debugging on or off. 1 = fill with a sine wave for debugging; 0 = fill with silence. */ + /* For safety we'll go ahead and default the buffer to silence. */ + for (iBus = 0; iBus < ma_node_get_input_bus_count(pNodeBase); iBus += 1) { + ma_silence_pcm_frames(ma_node_get_cached_input_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[iBus])); + } + for (iBus = 0; iBus < ma_node_get_output_bus_count(pNodeBase); iBus += 1) { + ma_silence_pcm_frames(ma_node_get_cached_output_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_output_bus_get_channels(&pNodeBase->pOutputBuses[iBus])); + } + #else + /* For debugging. Default to a sine wave. */ + for (iBus = 0; iBus < ma_node_get_input_bus_count(pNodeBase); iBus += 1) { + ma_debug_fill_pcm_frames_with_sine_wave(ma_node_get_cached_input_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[iBus]), 48000); + } + for (iBus = 0; iBus < ma_node_get_output_bus_count(pNodeBase); iBus += 1) { + ma_debug_fill_pcm_frames_with_sine_wave(ma_node_get_cached_output_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_output_bus_get_channels(&pNodeBase->pOutputBuses[iBus]), 48000); + } + #endif + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_node_init(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node* pNode) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_node_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_node_init_preallocated(pNodeGraph, pConfig, pHeap, pNode); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + ((ma_node_base*)pNode)->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_node_uninit(ma_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + + if (pNodeBase == NULL) { + return; + } + + /* + The first thing we need to do is fully detach the node. This will detach all inputs and + outputs. We need to do this first because it will sever the connection with the node graph and + allow us to complete uninitialization without needing to worry about thread-safety with the + audio thread. The detachment process will wait for any local processing of the node to finish. + */ + ma_node_detach_full(pNode); + + /* + At this point the node should be completely unreferenced by the node graph and we can finish up + the uninitialization process without needing to worry about thread-safety. + */ + if (pNodeBase->_ownsHeap) { + ma_free(pNodeBase->_pHeap, pAllocationCallbacks); + } +} + +MA_API ma_node_graph* ma_node_get_node_graph(const ma_node* pNode) +{ + if (pNode == NULL) { + return NULL; + } + + return ((const ma_node_base*)pNode)->pNodeGraph; +} + +MA_API ma_uint32 ma_node_get_input_bus_count(const ma_node* pNode) +{ + if (pNode == NULL) { + return 0; + } + + return ((ma_node_base*)pNode)->inputBusCount; +} + +MA_API ma_uint32 ma_node_get_output_bus_count(const ma_node* pNode) +{ + if (pNode == NULL) { + return 0; + } + + return ((ma_node_base*)pNode)->outputBusCount; +} + + +MA_API ma_uint32 ma_node_get_input_channels(const ma_node* pNode, ma_uint32 inputBusIndex) +{ + const ma_node_base* pNodeBase = (const ma_node_base*)pNode; + + if (pNode == NULL) { + return 0; + } + + if (inputBusIndex >= ma_node_get_input_bus_count(pNode)) { + return 0; /* Invalid bus index. */ + } + + return ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[inputBusIndex]); +} + +MA_API ma_uint32 ma_node_get_output_channels(const ma_node* pNode, ma_uint32 outputBusIndex) +{ + const ma_node_base* pNodeBase = (const ma_node_base*)pNode; + + if (pNode == NULL) { + return 0; + } + + if (outputBusIndex >= ma_node_get_output_bus_count(pNode)) { + return 0; /* Invalid bus index. */ + } + + return ma_node_output_bus_get_channels(&pNodeBase->pOutputBuses[outputBusIndex]); +} + + +static ma_result ma_node_detach_full(ma_node* pNode) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_uint32 iInputBus; + + if (pNodeBase == NULL) { + return MA_INVALID_ARGS; + } + + /* + Make sure the node is completely detached first. This will not return until the output bus is + guaranteed to no longer be referenced by the audio thread. + */ + ma_node_detach_all_output_buses(pNode); + + /* + At this point all output buses will have been detached from the graph and we can be guaranteed + that none of it's input nodes will be getting processed by the graph. We can detach these + without needing to worry about the audio thread touching them. + */ + for (iInputBus = 0; iInputBus < ma_node_get_input_bus_count(pNode); iInputBus += 1) { + ma_node_input_bus* pInputBus; + ma_node_output_bus* pOutputBus; + + pInputBus = &pNodeBase->pInputBuses[iInputBus]; + + /* + This is important. We cannot be using ma_node_input_bus_first() or ma_node_input_bus_next(). Those + functions are specifically for the audio thread. We'll instead just manually iterate using standard + linked list logic. We don't need to worry about the audio thread referencing these because the step + above severed the connection to the graph. + */ + for (pOutputBus = (ma_node_output_bus*)c89atomic_load_ptr(&pInputBus->head.pNext); pOutputBus != NULL; pOutputBus = (ma_node_output_bus*)c89atomic_load_ptr(&pOutputBus->pNext)) { + ma_node_detach_output_bus(pOutputBus->pNode, pOutputBus->outputBusIndex); /* This won't do any waiting in practice and should be efficient. */ + } + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_node_detach_output_bus(ma_node* pNode, ma_uint32 outputBusIndex) +{ + ma_result result = MA_SUCCESS; + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_node_base* pInputNodeBase; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + if (outputBusIndex >= ma_node_get_output_bus_count(pNode)) { + return MA_INVALID_ARGS; /* Invalid output bus index. */ + } + + /* We need to lock the output bus because we need to inspect the input node and grab it's input bus. */ + ma_node_output_bus_lock(&pNodeBase->pOutputBuses[outputBusIndex]); + { + pInputNodeBase = (ma_node_base*)pNodeBase->pOutputBuses[outputBusIndex].pInputNode; + if (pInputNodeBase != NULL) { + ma_node_input_bus_detach__no_output_bus_lock(&pInputNodeBase->pInputBuses[pNodeBase->pOutputBuses[outputBusIndex].inputNodeInputBusIndex], &pNodeBase->pOutputBuses[outputBusIndex]); + } + } + ma_node_output_bus_unlock(&pNodeBase->pOutputBuses[outputBusIndex]); + + return result; +} + +MA_API ma_result ma_node_detach_all_output_buses(ma_node* pNode) +{ + ma_uint32 iOutputBus; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + for (iOutputBus = 0; iOutputBus < ma_node_get_output_bus_count(pNode); iOutputBus += 1) { + ma_node_detach_output_bus(pNode, iOutputBus); + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_node_attach_output_bus(ma_node* pNode, ma_uint32 outputBusIndex, ma_node* pOtherNode, ma_uint32 otherNodeInputBusIndex) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_node_base* pOtherNodeBase = (ma_node_base*)pOtherNode; + + if (pNodeBase == NULL || pOtherNodeBase == NULL) { + return MA_INVALID_ARGS; + } + + if (pNodeBase == pOtherNodeBase) { + return MA_INVALID_OPERATION; /* Cannot attach a node to itself. */ + } + + if (outputBusIndex >= ma_node_get_output_bus_count(pNode) || otherNodeInputBusIndex >= ma_node_get_input_bus_count(pOtherNode)) { + return MA_INVALID_OPERATION; /* Invalid bus index. */ + } + + /* The output channel count of the output node must be the same as the input channel count of the input node. */ + if (ma_node_get_output_channels(pNode, outputBusIndex) != ma_node_get_input_channels(pOtherNode, otherNodeInputBusIndex)) { + return MA_INVALID_OPERATION; /* Channel count is incompatible. */ + } + + /* This will deal with detaching if the output bus is already attached to something. */ + ma_node_input_bus_attach(&pOtherNodeBase->pInputBuses[otherNodeInputBusIndex], &pNodeBase->pOutputBuses[outputBusIndex], pOtherNode, otherNodeInputBusIndex); + + return MA_SUCCESS; +} + +MA_API ma_result ma_node_set_output_bus_volume(ma_node* pNode, ma_uint32 outputBusIndex, float volume) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + + if (pNodeBase == NULL) { + return MA_INVALID_ARGS; + } + + if (outputBusIndex >= ma_node_get_output_bus_count(pNode)) { + return MA_INVALID_ARGS; /* Invalid bus index. */ + } + + return ma_node_output_bus_set_volume(&pNodeBase->pOutputBuses[outputBusIndex], volume); +} + +MA_API float ma_node_get_output_bus_volume(const ma_node* pNode, ma_uint32 outputBusIndex) +{ + const ma_node_base* pNodeBase = (const ma_node_base*)pNode; + + if (pNodeBase == NULL) { + return 0; + } + + if (outputBusIndex >= ma_node_get_output_bus_count(pNode)) { + return 0; /* Invalid bus index. */ + } + + return ma_node_output_bus_get_volume(&pNodeBase->pOutputBuses[outputBusIndex]); +} + +MA_API ma_result ma_node_set_state(ma_node* pNode, ma_node_state state) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + + if (pNodeBase == NULL) { + return MA_INVALID_ARGS; + } + + c89atomic_exchange_i32(&pNodeBase->state, state); + + return MA_SUCCESS; +} + +MA_API ma_node_state ma_node_get_state(const ma_node* pNode) +{ + const ma_node_base* pNodeBase = (const ma_node_base*)pNode; + + if (pNodeBase == NULL) { + return ma_node_state_stopped; + } + + return (ma_node_state)c89atomic_load_i32(&pNodeBase->state); +} + +MA_API ma_result ma_node_set_state_time(ma_node* pNode, ma_node_state state, ma_uint64 globalTime) +{ + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + /* Validation check for safety since we'll be using this as an index into stateTimes[]. */ + if (state != ma_node_state_started && state != ma_node_state_stopped) { + return MA_INVALID_ARGS; + } + + c89atomic_exchange_64(&((ma_node_base*)pNode)->stateTimes[state], globalTime); + + return MA_SUCCESS; +} + +MA_API ma_uint64 ma_node_get_state_time(const ma_node* pNode, ma_node_state state) +{ + if (pNode == NULL) { + return 0; + } + + /* Validation check for safety since we'll be using this as an index into stateTimes[]. */ + if (state != ma_node_state_started && state != ma_node_state_stopped) { + return 0; + } + + return c89atomic_load_64(&((ma_node_base*)pNode)->stateTimes[state]); +} + +MA_API ma_node_state ma_node_get_state_by_time(const ma_node* pNode, ma_uint64 globalTime) +{ + if (pNode == NULL) { + return ma_node_state_stopped; + } + + return ma_node_get_state_by_time_range(pNode, globalTime, globalTime); +} + +MA_API ma_node_state ma_node_get_state_by_time_range(const ma_node* pNode, ma_uint64 globalTimeBeg, ma_uint64 globalTimeEnd) +{ + ma_node_state state; + + if (pNode == NULL) { + return ma_node_state_stopped; + } + + state = ma_node_get_state(pNode); + + /* An explicitly stopped node is always stopped. */ + if (state == ma_node_state_stopped) { + return ma_node_state_stopped; + } + + /* + Getting here means the node is marked as started, but it may still not be truly started due to + it's start time not having been reached yet. Also, the stop time may have also been reached in + which case it'll be considered stopped. + */ + if (ma_node_get_state_time(pNode, ma_node_state_started) > globalTimeBeg) { + return ma_node_state_stopped; /* Start time has not yet been reached. */ + } + + if (ma_node_get_state_time(pNode, ma_node_state_stopped) <= globalTimeEnd) { + return ma_node_state_stopped; /* Stop time has been reached. */ + } + + /* Getting here means the node is marked as started and is within it's start/stop times. */ + return ma_node_state_started; +} + +MA_API ma_uint64 ma_node_get_time(const ma_node* pNode) +{ + if (pNode == NULL) { + return 0; + } + + return c89atomic_load_64(&((ma_node_base*)pNode)->localTime); +} + +MA_API ma_result ma_node_set_time(ma_node* pNode, ma_uint64 localTime) +{ + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + c89atomic_exchange_64(&((ma_node_base*)pNode)->localTime, localTime); + + return MA_SUCCESS; +} + + + +static void ma_node_process_pcm_frames_internal(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + + MA_ASSERT(pNode != NULL); + + if (pNodeBase->vtable->onProcess) { + pNodeBase->vtable->onProcess(pNode, ppFramesIn, pFrameCountIn, ppFramesOut, pFrameCountOut); + } +} + +static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusIndex, float* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead, ma_uint64 globalTime) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_result result = MA_SUCCESS; + ma_uint32 iInputBus; + ma_uint32 iOutputBus; + ma_uint32 inputBusCount; + ma_uint32 outputBusCount; + ma_uint32 totalFramesRead = 0; + float* ppFramesIn[MA_MAX_NODE_BUS_COUNT]; + float* ppFramesOut[MA_MAX_NODE_BUS_COUNT]; + ma_uint64 globalTimeBeg; + ma_uint64 globalTimeEnd; + ma_uint64 startTime; + ma_uint64 stopTime; + ma_uint32 timeOffsetBeg; + ma_uint32 timeOffsetEnd; + ma_uint32 frameCountIn; + ma_uint32 frameCountOut; + + /* + pFramesRead is mandatory. It must be used to determine how many frames were read. It's normal and + expected that the number of frames read may be different to that requested. Therefore, the caller + must look at this value to correctly determine how many frames were read. + */ + MA_ASSERT(pFramesRead != NULL); /* <-- If you've triggered this assert, you're using this function wrong. You *must* use this variable and inspect it after the call returns. */ + if (pFramesRead == NULL) { + return MA_INVALID_ARGS; + } + + *pFramesRead = 0; /* Safety. */ + + if (pNodeBase == NULL) { + return MA_INVALID_ARGS; + } + + if (outputBusIndex >= ma_node_get_output_bus_count(pNodeBase)) { + return MA_INVALID_ARGS; /* Invalid output bus index. */ + } + + /* Don't do anything if we're in a stopped state. */ + if (ma_node_get_state_by_time_range(pNode, globalTime, globalTime + frameCount) != ma_node_state_started) { + return MA_SUCCESS; /* We're in a stopped state. This is not an error - we just need to not read anything. */ + } + + + globalTimeBeg = globalTime; + globalTimeEnd = globalTime + frameCount; + startTime = ma_node_get_state_time(pNode, ma_node_state_started); + stopTime = ma_node_get_state_time(pNode, ma_node_state_stopped); + + /* + At this point we know that we are inside our start/stop times. However, we may need to adjust + our frame count and output pointer to accomodate since we could be straddling the time period + that this function is getting called for. + + It's possible (and likely) that the start time does not line up with the output buffer. We + therefore need to offset it by a number of frames to accomodate. The same thing applies for + the stop time. + */ + timeOffsetBeg = (globalTimeBeg < startTime) ? (ma_uint32)(globalTimeEnd - startTime) : 0; + timeOffsetEnd = (globalTimeEnd > stopTime) ? (ma_uint32)(globalTimeEnd - stopTime) : 0; + + /* Trim based on the start offset. We need to silence the start of the buffer. */ + if (timeOffsetBeg > 0) { + ma_silence_pcm_frames(pFramesOut, timeOffsetBeg, ma_format_f32, ma_node_get_output_channels(pNode, outputBusIndex)); + pFramesOut += timeOffsetBeg * ma_node_get_output_channels(pNode, outputBusIndex); + frameCount -= timeOffsetBeg; + } + + /* Trim based on the end offset. We don't need to silence the tail section because we'll just have a reduced value written to pFramesRead. */ + if (timeOffsetEnd > 0) { + frameCount -= timeOffsetEnd; + } + + + /* We run on different paths depending on the bus counts. */ + inputBusCount = ma_node_get_input_bus_count(pNode); + outputBusCount = ma_node_get_output_bus_count(pNode); + + /* + Run a simplified path when there are no inputs and one output. In this case there's nothing to + actually read and we can go straight to output. This is a very common scenario because the vast + majority of data source nodes will use this setup so this optimization I think is worthwhile. + */ + if (inputBusCount == 0 && outputBusCount == 1) { + /* Fast path. No need to read from input and no need for any caching. */ + frameCountIn = 0; + frameCountOut = frameCount; /* Just read as much as we can. The callback will return what was actually read. */ + + ppFramesOut[0] = pFramesOut; + ma_node_process_pcm_frames_internal(pNode, NULL, &frameCountIn, ppFramesOut, &frameCountOut); + totalFramesRead = frameCountOut; + } else { + /* Slow path. Need to read input data. */ + if ((pNodeBase->vtable->flags & MA_NODE_FLAG_PASSTHROUGH) != 0) { + /* + Fast path. We're running a passthrough. We need to read directly into the output buffer, but + still fire the callback so that event handling and trigger nodes can do their thing. Since + it's a passthrough there's no need for any kind of caching logic. + */ + MA_ASSERT(outputBusCount == inputBusCount); + MA_ASSERT(outputBusCount == 1); + MA_ASSERT(outputBusIndex == 0); + + /* We just read directly from input bus to output buffer, and then afterwards fire the callback. */ + ppFramesOut[0] = pFramesOut; + ppFramesIn[0] = ppFramesOut[0]; + + result = ma_node_input_bus_read_pcm_frames(pNodeBase, &pNodeBase->pInputBuses[0], ppFramesIn[0], frameCount, &totalFramesRead, globalTime); + if (result == MA_SUCCESS) { + /* Even though it's a passthrough, we still need to fire the callback. */ + frameCountIn = totalFramesRead; + frameCountOut = totalFramesRead; + + if (totalFramesRead > 0) { + ma_node_process_pcm_frames_internal(pNode, (const float**)ppFramesIn, &frameCountIn, ppFramesOut, &frameCountOut); /* From GCC: expected 'const float **' but argument is of type 'float **'. Shouldn't this be implicit? Excplicit cast to silence the warning. */ + } + + /* + A passthrough should never have modified the input and output frame counts. If you're + triggering these assers you need to fix your processing callback. + */ + MA_ASSERT(frameCountIn == totalFramesRead); + MA_ASSERT(frameCountOut == totalFramesRead); + } + } else { + /* Slow path. Need to do caching. */ + ma_uint32 framesToProcessIn; + ma_uint32 framesToProcessOut; + ma_bool32 consumeNullInput = MA_FALSE; + + /* + We use frameCount as a basis for the number of frames to read since that's what's being + requested, however we still need to clamp it to whatever can fit in the cache. + + This will also be used as the basis for determining how many input frames to read. This is + not ideal because it can result in too many input frames being read which introduces latency. + To solve this, nodes can implement an optional callback called onGetRequiredInputFrameCount + which is used as hint to miniaudio as to how many input frames it needs to read at a time. This + callback is completely optional, and if it's not set, miniaudio will assume `frameCount`. + + This function will be called multiple times for each period of time, once for each output node. + We cannot read from each input node each time this function is called. Instead we need to check + whether or not this is first output bus to be read from for this time period, and if so, read + from our input data. + + To determine whether or not we're ready to read data, we check a flag. There will be one flag + for each output. When the flag is set, it means data has been read previously and that we're + ready to advance time forward for our input nodes by reading fresh data. + */ + framesToProcessOut = frameCount; + if (framesToProcessOut > pNodeBase->cachedDataCapInFramesPerBus) { + framesToProcessOut = pNodeBase->cachedDataCapInFramesPerBus; + } + + framesToProcessIn = frameCount; + if (pNodeBase->vtable->onGetRequiredInputFrameCount) { + pNodeBase->vtable->onGetRequiredInputFrameCount(pNode, framesToProcessOut, &framesToProcessIn); /* <-- It does not matter if this fails. */ + } + if (framesToProcessIn > pNodeBase->cachedDataCapInFramesPerBus) { + framesToProcessIn = pNodeBase->cachedDataCapInFramesPerBus; + } + + + MA_ASSERT(framesToProcessIn <= 0xFFFF); + MA_ASSERT(framesToProcessOut <= 0xFFFF); + + if (ma_node_output_bus_has_read(&pNodeBase->pOutputBuses[outputBusIndex])) { + /* Getting here means we need to do another round of processing. */ + pNodeBase->cachedFrameCountOut = 0; + + for (;;) { + frameCountOut = 0; + + /* + We need to prepare our output frame pointers for processing. In the same iteration we need + to mark every output bus as unread so that future calls to this function for different buses + for the current time period don't pull in data when they should instead be reading from cache. + */ + for (iOutputBus = 0; iOutputBus < outputBusCount; iOutputBus += 1) { + ma_node_output_bus_set_has_read(&pNodeBase->pOutputBuses[iOutputBus], MA_FALSE); /* <-- This is what tells the next calls to this function for other output buses for this time period to read from cache instead of pulling in more data. */ + ppFramesOut[iOutputBus] = ma_node_get_cached_output_ptr(pNode, iOutputBus); + } + + /* We only need to read from input buses if there isn't already some data in the cache. */ + if (pNodeBase->cachedFrameCountIn == 0) { + ma_uint32 maxFramesReadIn = 0; + + /* Here is where we pull in data from the input buses. This is what will trigger an advance in time. */ + for (iInputBus = 0; iInputBus < inputBusCount; iInputBus += 1) { + ma_uint32 framesRead; + + /* The first thing to do is get the offset within our bulk allocation to store this input data. */ + ppFramesIn[iInputBus] = ma_node_get_cached_input_ptr(pNode, iInputBus); + + /* Once we've determined our destination pointer we can read. Note that we must inspect the number of frames read and fill any leftovers with silence for safety. */ + result = ma_node_input_bus_read_pcm_frames(pNodeBase, &pNodeBase->pInputBuses[iInputBus], ppFramesIn[iInputBus], framesToProcessIn, &framesRead, globalTime); + if (result != MA_SUCCESS) { + /* It doesn't really matter if we fail because we'll just fill with silence. */ + framesRead = 0; /* Just for safety, but I don't think it's really needed. */ + } + + /* TODO: Minor optimization opportunity here. If no frames were read and the buffer is already filled with silence, no need to re-silence it. */ + /* Any leftover frames need to silenced for safety. */ + if (framesRead < framesToProcessIn) { + ma_silence_pcm_frames(ppFramesIn[iInputBus] + (framesRead * ma_node_get_input_channels(pNodeBase, iInputBus)), (framesToProcessIn - framesRead), ma_format_f32, ma_node_get_input_channels(pNodeBase, iInputBus)); + } + + maxFramesReadIn = ma_max(maxFramesReadIn, framesRead); + } + + /* This was a fresh load of input data so reset our consumption counter. */ + pNodeBase->consumedFrameCountIn = 0; + + /* + We don't want to keep processing if there's nothing to process, so set the number of cached + input frames to the maximum number we read from each attachment (the lesser will be padded + with silence). If we didn't read anything, this will be set to 0 and the entire buffer will + have been assigned to silence. This being equal to 0 is an important property for us because + it allows us to detect when NULL can be passed into the processing callback for the input + buffer for the purpose of continuous processing. + */ + pNodeBase->cachedFrameCountIn = (ma_uint16)maxFramesReadIn; + } else { + /* We don't need to read anything, but we do need to prepare our input frame pointers. */ + for (iInputBus = 0; iInputBus < inputBusCount; iInputBus += 1) { + ppFramesIn[iInputBus] = ma_node_get_cached_input_ptr(pNode, iInputBus) + (pNodeBase->consumedFrameCountIn * ma_node_get_input_channels(pNodeBase, iInputBus)); + } + } + + /* + At this point we have our input data so now we need to do some processing. Sneaky little + optimization here - we can set the pointer to the output buffer for this output bus so + that the final copy into the output buffer is done directly by onProcess(). + */ + if (pFramesOut != NULL) { + ppFramesOut[outputBusIndex] = ma_offset_pcm_frames_ptr_f32(pFramesOut, pNodeBase->cachedFrameCountOut, ma_node_get_output_channels(pNode, outputBusIndex)); + } + + + /* Give the processing function the entire capacity of the output buffer. */ + frameCountOut = (framesToProcessOut - pNodeBase->cachedFrameCountOut); + + /* + We need to treat nodes with continuous processing a little differently. For these ones, + we always want to fire the callback with the requested number of frames, regardless of + pNodeBase->cachedFrameCountIn, which could be 0. Also, we want to check if we can pass + in NULL for the input buffer to the callback. + */ + if ((pNodeBase->vtable->flags & MA_NODE_FLAG_CONTINUOUS_PROCESSING) != 0) { + /* We're using continuous processing. Make sure we specify the whole frame count at all times. */ + frameCountIn = framesToProcessIn; /* Give the processing function as much input data as we've got in the buffer, including any silenced padding from short reads. */ + + if ((pNodeBase->vtable->flags & MA_NODE_FLAG_ALLOW_NULL_INPUT) != 0 && pNodeBase->consumedFrameCountIn == 0 && pNodeBase->cachedFrameCountIn == 0) { + consumeNullInput = MA_TRUE; + } else { + consumeNullInput = MA_FALSE; + } + + /* + Since we're using continuous processing we're always passing in a full frame count + regardless of how much input data was read. If this is greater than what we read as + input, we'll end up with an underflow. We instead need to make sure our cached frame + count is set to the number of frames we'll be passing to the data callback. Not + doing this will result in an underflow when we "consume" the cached data later on. + + Note that this check needs to be done after the "consumeNullInput" check above because + we use the property of cachedFrameCountIn being 0 to determine whether or not we + should be passing in a null pointer to the processing callback for when the node is + configured with MA_NODE_FLAG_ALLOW_NULL_INPUT. + */ + if (pNodeBase->cachedFrameCountIn < (ma_uint16)frameCountIn) { + pNodeBase->cachedFrameCountIn = (ma_uint16)frameCountIn; + } + } else { + frameCountIn = pNodeBase->cachedFrameCountIn; /* Give the processing function as much valid input data as we've got. */ + consumeNullInput = MA_FALSE; + } + + /* + Process data slightly differently depending on whether or not we're consuming NULL + input (checked just above). + */ + if (consumeNullInput) { + ma_node_process_pcm_frames_internal(pNode, NULL, &frameCountIn, ppFramesOut, &frameCountOut); + } else { + /* + We want to skip processing if there's no input data, but we can only do that safely if + we know that there is no chance of any output frames being produced. If continuous + processing is being used, this won't be a problem because the input frame count will + always be non-0. However, if continuous processing is *not* enabled and input and output + data is processed at different rates, we still need to process that last input frame + because there could be a few excess output frames needing to be produced from cached + data. The `MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES` flag is used as the indicator for + determining whether or not we need to process the node even when there are no input + frames available right now. + */ + if (frameCountIn > 0 || (pNodeBase->vtable->flags & MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES) != 0) { + ma_node_process_pcm_frames_internal(pNode, (const float**)ppFramesIn, &frameCountIn, ppFramesOut, &frameCountOut); /* From GCC: expected 'const float **' but argument is of type 'float **'. Shouldn't this be implicit? Excplicit cast to silence the warning. */ + } + } + + /* + Thanks to our sneaky optimization above we don't need to do any data copying directly into + the output buffer - the onProcess() callback just did that for us. We do, however, need to + apply the number of input and output frames that were processed. Note that due to continuous + processing above, we need to do explicit checks here. If we just consumed a NULL input + buffer it means that no actual input data was processed from the internal buffers and we + don't want to be modifying any counters. + */ + if (consumeNullInput == MA_FALSE) { + pNodeBase->consumedFrameCountIn += (ma_uint16)frameCountIn; + pNodeBase->cachedFrameCountIn -= (ma_uint16)frameCountIn; + } + + /* The cached output frame count is always equal to what we just read. */ + pNodeBase->cachedFrameCountOut += (ma_uint16)frameCountOut; + + /* If we couldn't process any data, we're done. The loop needs to be terminated here or else we'll get stuck in a loop. */ + if (pNodeBase->cachedFrameCountOut == framesToProcessOut || (frameCountOut == 0 && frameCountIn == 0)) { + break; + } + } + } else { + /* + We're not needing to read anything from the input buffer so just read directly from our + already-processed data. + */ + if (pFramesOut != NULL) { + ma_copy_pcm_frames(pFramesOut, ma_node_get_cached_output_ptr(pNodeBase, outputBusIndex), pNodeBase->cachedFrameCountOut, ma_format_f32, ma_node_get_output_channels(pNodeBase, outputBusIndex)); + } + } + + /* The number of frames read is always equal to the number of cached output frames. */ + totalFramesRead = pNodeBase->cachedFrameCountOut; + + /* Now that we've read the data, make sure our read flag is set. */ + ma_node_output_bus_set_has_read(&pNodeBase->pOutputBuses[outputBusIndex], MA_TRUE); + } + } + + /* Apply volume, if necessary. */ + ma_apply_volume_factor_f32(pFramesOut, totalFramesRead * ma_node_get_output_channels(pNodeBase, outputBusIndex), ma_node_output_bus_get_volume(&pNodeBase->pOutputBuses[outputBusIndex])); + + /* Advance our local time forward. */ + c89atomic_fetch_add_64(&pNodeBase->localTime, (ma_uint64)totalFramesRead); + + *pFramesRead = totalFramesRead + timeOffsetBeg; /* Must include the silenced section at the start of the buffer. */ + return result; +} + + + + +/* Data source node. */ +MA_API ma_data_source_node_config ma_data_source_node_config_init(ma_data_source* pDataSource) +{ + ma_data_source_node_config config; + + MA_ZERO_OBJECT(&config); + config.nodeConfig = ma_node_config_init(); + config.pDataSource = pDataSource; + + return config; +} + + +static void ma_data_source_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_data_source_node* pDataSourceNode = (ma_data_source_node*)pNode; + ma_format format; + ma_uint32 channels; + ma_uint32 frameCount; + ma_uint64 framesRead = 0; + + MA_ASSERT(pDataSourceNode != NULL); + MA_ASSERT(pDataSourceNode->pDataSource != NULL); + MA_ASSERT(ma_node_get_input_bus_count(pDataSourceNode) == 0); + MA_ASSERT(ma_node_get_output_bus_count(pDataSourceNode) == 1); + + /* We don't want to read from ppFramesIn at all. Instead we read from the data source. */ + (void)ppFramesIn; + (void)pFrameCountIn; + + frameCount = *pFrameCountOut; + + /* miniaudio should never be calling this with a frame count of zero. */ + MA_ASSERT(frameCount > 0); + + if (ma_data_source_get_data_format(pDataSourceNode->pDataSource, &format, &channels, NULL, NULL, 0) == MA_SUCCESS) { /* <-- Don't care about sample rate here. */ + /* The node graph system requires samples be in floating point format. This is checked in ma_data_source_node_init(). */ + MA_ASSERT(format == ma_format_f32); + (void)format; /* Just to silence some static analysis tools. */ + + ma_data_source_read_pcm_frames(pDataSourceNode->pDataSource, ppFramesOut[0], frameCount, &framesRead); + } + + *pFrameCountOut = (ma_uint32)framesRead; +} + +static ma_node_vtable g_ma_data_source_node_vtable = +{ + ma_data_source_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 0, /* 0 input buses. */ + 1, /* 1 output bus. */ + 0 +}; + +MA_API ma_result ma_data_source_node_init(ma_node_graph* pNodeGraph, const ma_data_source_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source_node* pDataSourceNode) +{ + ma_result result; + ma_format format; /* For validating the format, which must be ma_format_f32. */ + ma_uint32 channels; /* For specifying the channel count of the output bus. */ + ma_node_config baseConfig; + + if (pDataSourceNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pDataSourceNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_data_source_get_data_format(pConfig->pDataSource, &format, &channels, NULL, NULL, 0); /* Don't care about sample rate. This will check pDataSource for NULL. */ + if (result != MA_SUCCESS) { + return result; + } + + MA_ASSERT(format == ma_format_f32); /* <-- If you've triggered this it means your data source is not outputting floating-point samples. You must configure your data source to use ma_format_f32. */ + if (format != ma_format_f32) { + return MA_INVALID_ARGS; /* Invalid format. */ + } + + /* The channel count is defined by the data source. If the caller has manually changed the channels we just ignore it. */ + baseConfig = pConfig->nodeConfig; + baseConfig.vtable = &g_ma_data_source_node_vtable; /* Explicitly set the vtable here to prevent callers from setting it incorrectly. */ + + /* + The channel count is defined by the data source. It is invalid for the caller to manually set + the channel counts in the config. `ma_data_source_node_config_init()` will have defaulted the + channel count pointer to NULL which is how it must remain. If you trigger any of these asserts + it means you're explicitly setting the channel count. Instead, configure the output channel + count of your data source to be the necessary channel count. + */ + if (baseConfig.pOutputChannels != NULL) { + return MA_INVALID_ARGS; + } + + baseConfig.pOutputChannels = &channels; + + result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pDataSourceNode->base); + if (result != MA_SUCCESS) { + return result; + } + + pDataSourceNode->pDataSource = pConfig->pDataSource; + + return MA_SUCCESS; +} + +MA_API void ma_data_source_node_uninit(ma_data_source_node* pDataSourceNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_node_uninit(&pDataSourceNode->base, pAllocationCallbacks); +} + +MA_API ma_result ma_data_source_node_set_looping(ma_data_source_node* pDataSourceNode, ma_bool32 isLooping) +{ + if (pDataSourceNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_data_source_set_looping(pDataSourceNode->pDataSource, isLooping); +} + +MA_API ma_bool32 ma_data_source_node_is_looping(ma_data_source_node* pDataSourceNode) +{ + if (pDataSourceNode == NULL) { + return MA_FALSE; + } + + return ma_data_source_is_looping(pDataSourceNode->pDataSource); +} + + + +/* Splitter Node. */ +MA_API ma_splitter_node_config ma_splitter_node_config_init(ma_uint32 channels) +{ + ma_splitter_node_config config; + + MA_ZERO_OBJECT(&config); + config.nodeConfig = ma_node_config_init(); + config.channels = channels; + + return config; +} + + +static void ma_splitter_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_uint32 iOutputBus; + ma_uint32 channels; + + MA_ASSERT(pNodeBase != NULL); + MA_ASSERT(ma_node_get_input_bus_count(pNodeBase) == 1); + MA_ASSERT(ma_node_get_output_bus_count(pNodeBase) >= 2); + + /* We don't need to consider the input frame count - it'll be the same as the output frame count and we process everything. */ + (void)pFrameCountIn; + + /* NOTE: This assumes the same number of channels for all inputs and outputs. This was checked in ma_splitter_node_init(). */ + channels = ma_node_get_input_channels(pNodeBase, 0); + + /* Splitting is just copying the first input bus and copying it over to each output bus. */ + for (iOutputBus = 0; iOutputBus < ma_node_get_output_bus_count(pNodeBase); iOutputBus += 1) { + ma_copy_pcm_frames(ppFramesOut[iOutputBus], ppFramesIn[0], *pFrameCountOut, ma_format_f32, channels); + } +} + +static ma_node_vtable g_ma_splitter_node_vtable = +{ + ma_splitter_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* 1 input bus. */ + 2, /* 2 output buses. */ + 0 +}; + +MA_API ma_result ma_splitter_node_init(ma_node_graph* pNodeGraph, const ma_splitter_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_splitter_node* pSplitterNode) +{ + ma_result result; + ma_node_config baseConfig; + ma_uint32 pInputChannels[1]; + ma_uint32 pOutputChannels[2]; + + if (pSplitterNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pSplitterNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + /* Splitters require the same number of channels between inputs and outputs. */ + pInputChannels[0] = pConfig->channels; + pOutputChannels[0] = pConfig->channels; + pOutputChannels[1] = pConfig->channels; + + baseConfig = pConfig->nodeConfig; + baseConfig.vtable = &g_ma_splitter_node_vtable; + baseConfig.pInputChannels = pInputChannels; + baseConfig.pOutputChannels = pOutputChannels; + + result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pSplitterNode->base); + if (result != MA_SUCCESS) { + return result; /* Failed to initialize the base node. */ + } + + return MA_SUCCESS; +} + +MA_API void ma_splitter_node_uninit(ma_splitter_node* pSplitterNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_node_uninit(pSplitterNode, pAllocationCallbacks); +} + + +/* +Biquad Node +*/ +MA_API ma_biquad_node_config ma_biquad_node_config_init(ma_uint32 channels, float b0, float b1, float b2, float a0, float a1, float a2) +{ + ma_biquad_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.biquad = ma_biquad_config_init(ma_format_f32, channels, b0, b1, b2, a0, a1, a2); + + return config; +} + +static void ma_biquad_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_biquad_node* pLPFNode = (ma_biquad_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_biquad_process_pcm_frames(&pLPFNode->biquad, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_biquad_node_vtable = +{ + ma_biquad_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_biquad_node_init(ma_node_graph* pNodeGraph, const ma_biquad_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_biquad_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->biquad.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_biquad_init(&pConfig->biquad, pAllocationCallbacks, &pNode->biquad); + if (result != MA_SUCCESS) { + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_biquad_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->biquad.channels; + baseNodeConfig.pOutputChannels = &pConfig->biquad.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_biquad_node_reinit(const ma_biquad_config* pConfig, ma_biquad_node* pNode) +{ + ma_biquad_node* pLPFNode = (ma_biquad_node*)pNode; + + MA_ASSERT(pNode != NULL); + + return ma_biquad_reinit(pConfig, &pLPFNode->biquad); +} + +MA_API void ma_biquad_node_uninit(ma_biquad_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_biquad_node* pLPFNode = (ma_biquad_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_biquad_uninit(&pLPFNode->biquad, pAllocationCallbacks); +} + + + +/* +Low Pass Filter Node +*/ +MA_API ma_lpf_node_config ma_lpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order) +{ + ma_lpf_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.lpf = ma_lpf_config_init(ma_format_f32, channels, sampleRate, cutoffFrequency, order); + + return config; +} + +static void ma_lpf_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_lpf_node* pLPFNode = (ma_lpf_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_lpf_process_pcm_frames(&pLPFNode->lpf, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_lpf_node_vtable = +{ + ma_lpf_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_lpf_node_init(ma_node_graph* pNodeGraph, const ma_lpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->lpf.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_lpf_init(&pConfig->lpf, pAllocationCallbacks, &pNode->lpf); + if (result != MA_SUCCESS) { + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_lpf_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->lpf.channels; + baseNodeConfig.pOutputChannels = &pConfig->lpf.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_lpf_node_reinit(const ma_lpf_config* pConfig, ma_lpf_node* pNode) +{ + ma_lpf_node* pLPFNode = (ma_lpf_node*)pNode; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_lpf_reinit(pConfig, &pLPFNode->lpf); +} + +MA_API void ma_lpf_node_uninit(ma_lpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_lpf_node* pLPFNode = (ma_lpf_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_lpf_uninit(&pLPFNode->lpf, pAllocationCallbacks); +} + + + +/* +High Pass Filter Node +*/ +MA_API ma_hpf_node_config ma_hpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order) +{ + ma_hpf_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.hpf = ma_hpf_config_init(ma_format_f32, channels, sampleRate, cutoffFrequency, order); + + return config; +} + +static void ma_hpf_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_hpf_node* pHPFNode = (ma_hpf_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_hpf_process_pcm_frames(&pHPFNode->hpf, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_hpf_node_vtable = +{ + ma_hpf_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_hpf_node_init(ma_node_graph* pNodeGraph, const ma_hpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->hpf.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_hpf_init(&pConfig->hpf, pAllocationCallbacks, &pNode->hpf); + if (result != MA_SUCCESS) { + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_hpf_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->hpf.channels; + baseNodeConfig.pOutputChannels = &pConfig->hpf.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_hpf_node_reinit(const ma_hpf_config* pConfig, ma_hpf_node* pNode) +{ + ma_hpf_node* pHPFNode = (ma_hpf_node*)pNode; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_hpf_reinit(pConfig, &pHPFNode->hpf); +} + +MA_API void ma_hpf_node_uninit(ma_hpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_hpf_node* pHPFNode = (ma_hpf_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_hpf_uninit(&pHPFNode->hpf, pAllocationCallbacks); +} + + + + +/* +Band Pass Filter Node +*/ +MA_API ma_bpf_node_config ma_bpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order) +{ + ma_bpf_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.bpf = ma_bpf_config_init(ma_format_f32, channels, sampleRate, cutoffFrequency, order); + + return config; +} + +static void ma_bpf_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_bpf_node* pBPFNode = (ma_bpf_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_bpf_process_pcm_frames(&pBPFNode->bpf, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_bpf_node_vtable = +{ + ma_bpf_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_bpf_node_init(ma_node_graph* pNodeGraph, const ma_bpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_bpf_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->bpf.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_bpf_init(&pConfig->bpf, pAllocationCallbacks, &pNode->bpf); + if (result != MA_SUCCESS) { + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_bpf_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->bpf.channels; + baseNodeConfig.pOutputChannels = &pConfig->bpf.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_bpf_node_reinit(const ma_bpf_config* pConfig, ma_bpf_node* pNode) +{ + ma_bpf_node* pBPFNode = (ma_bpf_node*)pNode; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_bpf_reinit(pConfig, &pBPFNode->bpf); +} + +MA_API void ma_bpf_node_uninit(ma_bpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_bpf_node* pBPFNode = (ma_bpf_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_bpf_uninit(&pBPFNode->bpf, pAllocationCallbacks); +} + + + +/* +Notching Filter Node +*/ +MA_API ma_notch_node_config ma_notch_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double q, double frequency) +{ + ma_notch_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.notch = ma_notch2_config_init(ma_format_f32, channels, sampleRate, q, frequency); + + return config; +} + +static void ma_notch_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_notch_node* pBPFNode = (ma_notch_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_notch2_process_pcm_frames(&pBPFNode->notch, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_notch_node_vtable = +{ + ma_notch_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_notch_node_init(ma_node_graph* pNodeGraph, const ma_notch_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_notch_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->notch.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_notch2_init(&pConfig->notch, pAllocationCallbacks, &pNode->notch); + if (result != MA_SUCCESS) { + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_notch_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->notch.channels; + baseNodeConfig.pOutputChannels = &pConfig->notch.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_notch_node_reinit(const ma_notch_config* pConfig, ma_notch_node* pNode) +{ + ma_notch_node* pNotchNode = (ma_notch_node*)pNode; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_notch2_reinit(pConfig, &pNotchNode->notch); +} + +MA_API void ma_notch_node_uninit(ma_notch_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_notch_node* pNotchNode = (ma_notch_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_notch2_uninit(&pNotchNode->notch, pAllocationCallbacks); +} + + + +/* +Peaking Filter Node +*/ +MA_API ma_peak_node_config ma_peak_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency) +{ + ma_peak_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.peak = ma_peak2_config_init(ma_format_f32, channels, sampleRate, gainDB, q, frequency); + + return config; +} + +static void ma_peak_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_peak_node* pBPFNode = (ma_peak_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_peak2_process_pcm_frames(&pBPFNode->peak, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_peak_node_vtable = +{ + ma_peak_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_peak_node_init(ma_node_graph* pNodeGraph, const ma_peak_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_peak_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->peak.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_peak2_init(&pConfig->peak, pAllocationCallbacks, &pNode->peak); + if (result != MA_SUCCESS) { + ma_node_uninit(pNode, pAllocationCallbacks); + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_peak_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->peak.channels; + baseNodeConfig.pOutputChannels = &pConfig->peak.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_peak_node_reinit(const ma_peak_config* pConfig, ma_peak_node* pNode) +{ + ma_peak_node* pPeakNode = (ma_peak_node*)pNode; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_peak2_reinit(pConfig, &pPeakNode->peak); +} + +MA_API void ma_peak_node_uninit(ma_peak_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_peak_node* pPeakNode = (ma_peak_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_peak2_uninit(&pPeakNode->peak, pAllocationCallbacks); +} + + + +/* +Low Shelf Filter Node +*/ +MA_API ma_loshelf_node_config ma_loshelf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency) +{ + ma_loshelf_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.loshelf = ma_loshelf2_config_init(ma_format_f32, channels, sampleRate, gainDB, q, frequency); + + return config; +} + +static void ma_loshelf_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_loshelf_node* pBPFNode = (ma_loshelf_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_loshelf2_process_pcm_frames(&pBPFNode->loshelf, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_loshelf_node_vtable = +{ + ma_loshelf_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_loshelf_node_init(ma_node_graph* pNodeGraph, const ma_loshelf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_loshelf_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->loshelf.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_loshelf2_init(&pConfig->loshelf, pAllocationCallbacks, &pNode->loshelf); + if (result != MA_SUCCESS) { + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_loshelf_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->loshelf.channels; + baseNodeConfig.pOutputChannels = &pConfig->loshelf.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_loshelf_node_reinit(const ma_loshelf_config* pConfig, ma_loshelf_node* pNode) +{ + ma_loshelf_node* pLoshelfNode = (ma_loshelf_node*)pNode; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_loshelf2_reinit(pConfig, &pLoshelfNode->loshelf); +} + +MA_API void ma_loshelf_node_uninit(ma_loshelf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_loshelf_node* pLoshelfNode = (ma_loshelf_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_loshelf2_uninit(&pLoshelfNode->loshelf, pAllocationCallbacks); +} + + + +/* +High Shelf Filter Node +*/ +MA_API ma_hishelf_node_config ma_hishelf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency) +{ + ma_hishelf_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.hishelf = ma_hishelf2_config_init(ma_format_f32, channels, sampleRate, gainDB, q, frequency); + + return config; +} + +static void ma_hishelf_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_hishelf_node* pBPFNode = (ma_hishelf_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_hishelf2_process_pcm_frames(&pBPFNode->hishelf, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_hishelf_node_vtable = +{ + ma_hishelf_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_hishelf_node_init(ma_node_graph* pNodeGraph, const ma_hishelf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hishelf_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->hishelf.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_hishelf2_init(&pConfig->hishelf, pAllocationCallbacks, &pNode->hishelf); + if (result != MA_SUCCESS) { + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_hishelf_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->hishelf.channels; + baseNodeConfig.pOutputChannels = &pConfig->hishelf.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_hishelf_node_reinit(const ma_hishelf_config* pConfig, ma_hishelf_node* pNode) +{ + ma_hishelf_node* pHishelfNode = (ma_hishelf_node*)pNode; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_hishelf2_reinit(pConfig, &pHishelfNode->hishelf); +} + +MA_API void ma_hishelf_node_uninit(ma_hishelf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_hishelf_node* pHishelfNode = (ma_hishelf_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_hishelf2_uninit(&pHishelfNode->hishelf, pAllocationCallbacks); +} + + + + +MA_API ma_delay_node_config ma_delay_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 delayInFrames, float decay) +{ + ma_delay_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.delay = ma_delay_config_init(channels, sampleRate, delayInFrames, decay); + + return config; +} + + +static void ma_delay_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_delay_node* pDelayNode = (ma_delay_node*)pNode; + + (void)pFrameCountIn; + + ma_delay_process_pcm_frames(&pDelayNode->delay, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_delay_node_vtable = +{ + ma_delay_node_process_pcm_frames, + NULL, + 1, /* 1 input channels. */ + 1, /* 1 output channel. */ + MA_NODE_FLAG_CONTINUOUS_PROCESSING /* Delay requires continuous processing to ensure the tail get's processed. */ +}; + +MA_API ma_result ma_delay_node_init(ma_node_graph* pNodeGraph, const ma_delay_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_delay_node* pDelayNode) +{ + ma_result result; + ma_node_config baseConfig; + + if (pDelayNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pDelayNode); + + result = ma_delay_init(&pConfig->delay, pAllocationCallbacks, &pDelayNode->delay); + if (result != MA_SUCCESS) { + return result; + } + + baseConfig = pConfig->nodeConfig; + baseConfig.vtable = &g_ma_delay_node_vtable; + baseConfig.pInputChannels = &pConfig->delay.channels; + baseConfig.pOutputChannels = &pConfig->delay.channels; + + result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pDelayNode->baseNode); + if (result != MA_SUCCESS) { + ma_delay_uninit(&pDelayNode->delay, pAllocationCallbacks); + return result; + } + + return result; +} + +MA_API void ma_delay_node_uninit(ma_delay_node* pDelayNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pDelayNode == NULL) { + return; + } + + /* The base node is always uninitialized first. */ + ma_node_uninit(pDelayNode, pAllocationCallbacks); + ma_delay_uninit(&pDelayNode->delay, pAllocationCallbacks); +} + +MA_API void ma_delay_node_set_wet(ma_delay_node* pDelayNode, float value) +{ + if (pDelayNode == NULL) { + return; + } + + ma_delay_set_wet(&pDelayNode->delay, value); +} + +MA_API float ma_delay_node_get_wet(const ma_delay_node* pDelayNode) +{ + if (pDelayNode == NULL) { + return 0; + } + + return ma_delay_get_wet(&pDelayNode->delay); +} + +MA_API void ma_delay_node_set_dry(ma_delay_node* pDelayNode, float value) +{ + if (pDelayNode == NULL) { + return; + } + + ma_delay_set_dry(&pDelayNode->delay, value); +} + +MA_API float ma_delay_node_get_dry(const ma_delay_node* pDelayNode) +{ + if (pDelayNode == NULL) { + return 0; + } + + return ma_delay_get_dry(&pDelayNode->delay); +} + +MA_API void ma_delay_node_set_decay(ma_delay_node* pDelayNode, float value) +{ + if (pDelayNode == NULL) { + return; + } + + ma_delay_set_decay(&pDelayNode->delay, value); +} + +MA_API float ma_delay_node_get_decay(const ma_delay_node* pDelayNode) +{ + if (pDelayNode == NULL) { + return 0; + } + + return ma_delay_get_decay(&pDelayNode->delay); +} +#endif /* MA_NO_NODE_GRAPH */ + + +#if !defined(MA_NO_ENGINE) && !defined(MA_NO_NODE_GRAPH) +/************************************************************************************************************************************************************** + +Engine + +**************************************************************************************************************************************************************/ +#define MA_SEEK_TARGET_NONE (~(ma_uint64)0) + +MA_API ma_engine_node_config ma_engine_node_config_init(ma_engine* pEngine, ma_engine_node_type type, ma_uint32 flags) +{ + ma_engine_node_config config; + + MA_ZERO_OBJECT(&config); + config.pEngine = pEngine; + config.type = type; + config.isPitchDisabled = (flags & MA_SOUND_FLAG_NO_PITCH) != 0; + config.isSpatializationDisabled = (flags & MA_SOUND_FLAG_NO_SPATIALIZATION) != 0; + + return config; +} + + +static void ma_engine_node_update_pitch_if_required(ma_engine_node* pEngineNode) +{ + ma_bool32 isUpdateRequired = MA_FALSE; + float newPitch; + + MA_ASSERT(pEngineNode != NULL); + + newPitch = c89atomic_load_explicit_f32(&pEngineNode->pitch, c89atomic_memory_order_acquire); + + if (pEngineNode->oldPitch != newPitch) { + pEngineNode->oldPitch = newPitch; + isUpdateRequired = MA_TRUE; + } + + if (pEngineNode->oldDopplerPitch != pEngineNode->spatializer.dopplerPitch) { + pEngineNode->oldDopplerPitch = pEngineNode->spatializer.dopplerPitch; + isUpdateRequired = MA_TRUE; + } + + if (isUpdateRequired) { + float basePitch = (float)pEngineNode->sampleRate / ma_engine_get_sample_rate(pEngineNode->pEngine); + ma_linear_resampler_set_rate_ratio(&pEngineNode->resampler, basePitch * pEngineNode->oldPitch * pEngineNode->oldDopplerPitch); + } +} + +static ma_bool32 ma_engine_node_is_pitching_enabled(const ma_engine_node* pEngineNode) +{ + MA_ASSERT(pEngineNode != NULL); + + /* Don't try to be clever by skiping resampling in the pitch=1 case or else you'll glitch when moving away from 1. */ + return !c89atomic_load_explicit_32(&pEngineNode->isPitchDisabled, c89atomic_memory_order_acquire); +} + +static ma_bool32 ma_engine_node_is_spatialization_enabled(const ma_engine_node* pEngineNode) +{ + MA_ASSERT(pEngineNode != NULL); + + return !c89atomic_load_explicit_32(&pEngineNode->isSpatializationDisabled, c89atomic_memory_order_acquire); +} + +static ma_uint64 ma_engine_node_get_required_input_frame_count(const ma_engine_node* pEngineNode, ma_uint64 outputFrameCount) +{ + ma_uint64 inputFrameCount = 0; + + if (ma_engine_node_is_pitching_enabled(pEngineNode)) { + ma_result result = ma_linear_resampler_get_required_input_frame_count(&pEngineNode->resampler, outputFrameCount, &inputFrameCount); + if (result != MA_SUCCESS) { + inputFrameCount = 0; + } + } else { + inputFrameCount = outputFrameCount; /* No resampling, so 1:1. */ + } + + return inputFrameCount; +} + +static void ma_engine_node_process_pcm_frames__general(ma_engine_node* pEngineNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_uint32 frameCountIn; + ma_uint32 frameCountOut; + ma_uint32 totalFramesProcessedIn; + ma_uint32 totalFramesProcessedOut; + ma_uint32 channelsIn; + ma_uint32 channelsOut; + ma_bool32 isPitchingEnabled; + ma_bool32 isFadingEnabled; + ma_bool32 isSpatializationEnabled; + ma_bool32 isPanningEnabled; + + frameCountIn = *pFrameCountIn; + frameCountOut = *pFrameCountOut; + + channelsIn = ma_spatializer_get_input_channels(&pEngineNode->spatializer); + channelsOut = ma_spatializer_get_output_channels(&pEngineNode->spatializer); + + totalFramesProcessedIn = 0; + totalFramesProcessedOut = 0; + + isPitchingEnabled = ma_engine_node_is_pitching_enabled(pEngineNode); + isFadingEnabled = pEngineNode->fader.volumeBeg != 1 || pEngineNode->fader.volumeEnd != 1; + isSpatializationEnabled = ma_engine_node_is_spatialization_enabled(pEngineNode); + isPanningEnabled = pEngineNode->panner.pan != 0 && channelsOut != 1; + + /* Keep going while we've still got data available for processing. */ + while (totalFramesProcessedOut < frameCountOut) { + /* + We need to process in a specific order. We always do resampling first because it's likely + we're going to be increasing the channel count after spatialization. Also, I want to do + fading based on the output sample rate. + + We'll first read into a buffer from the resampler. Then we'll do all processing that + operates on the on the input channel count. We'll then get the spatializer to output to + the output buffer and then do all effects from that point directly in the output buffer + in-place. + + Note that we're always running the resampler. If we try to be clever and skip resampling + when the pitch is 1, we'll get a glitch when we move away from 1, back to 1, and then + away from 1 again. We'll want to implement any pitch=1 optimizations in the resampler + itself. + + There's a small optimization here that we'll utilize since it might be a fairly common + case. When the input and output channel counts are the same, we'll read straight into the + output buffer from the resampler and do everything in-place. + */ + const float* pRunningFramesIn; + float* pRunningFramesOut; + float* pWorkingBuffer; /* This is the buffer that we'll be processing frames in. This is in input channels. */ + float temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE / sizeof(float)]; + ma_uint32 tempCapInFrames = ma_countof(temp) / channelsIn; + ma_uint32 framesAvailableIn; + ma_uint32 framesAvailableOut; + ma_uint32 framesJustProcessedIn; + ma_uint32 framesJustProcessedOut; + ma_bool32 isWorkingBufferValid = MA_FALSE; + + framesAvailableIn = frameCountIn - totalFramesProcessedIn; + framesAvailableOut = frameCountOut - totalFramesProcessedOut; + + pRunningFramesIn = ma_offset_pcm_frames_const_ptr_f32(ppFramesIn[0], totalFramesProcessedIn, channelsIn); + pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(ppFramesOut[0], totalFramesProcessedOut, channelsOut); + + if (channelsIn == channelsOut) { + /* Fast path. Channel counts are the same. No need for an intermediary input buffer. */ + pWorkingBuffer = pRunningFramesOut; + } else { + /* Slow path. Channel counts are different. Need to use an intermediary input buffer. */ + pWorkingBuffer = temp; + if (framesAvailableOut > tempCapInFrames) { + framesAvailableOut = tempCapInFrames; + } + } + + /* First is resampler. */ + if (isPitchingEnabled) { + ma_uint64 resampleFrameCountIn = framesAvailableIn; + ma_uint64 resampleFrameCountOut = framesAvailableOut; + + ma_linear_resampler_process_pcm_frames(&pEngineNode->resampler, pRunningFramesIn, &resampleFrameCountIn, pWorkingBuffer, &resampleFrameCountOut); + isWorkingBufferValid = MA_TRUE; + + framesJustProcessedIn = (ma_uint32)resampleFrameCountIn; + framesJustProcessedOut = (ma_uint32)resampleFrameCountOut; + } else { + framesJustProcessedIn = framesAvailableIn; + framesJustProcessedOut = framesAvailableOut; + } + + /* Fading. */ + if (isFadingEnabled) { + if (isWorkingBufferValid) { + ma_fader_process_pcm_frames(&pEngineNode->fader, pWorkingBuffer, pWorkingBuffer, framesJustProcessedOut); /* In-place processing. */ + } else { + ma_fader_process_pcm_frames(&pEngineNode->fader, pWorkingBuffer, pRunningFramesIn, framesJustProcessedOut); + isWorkingBufferValid = MA_TRUE; + } + } + + /* + If at this point we still haven't actually done anything with the working buffer we need + to just read straight from the input buffer. + */ + if (isWorkingBufferValid == MA_FALSE) { + pWorkingBuffer = (float*)pRunningFramesIn; /* Naughty const cast, but it's safe at this point because we won't ever be writing to it from this point out. */ + } + + /* Spatialization. */ + if (isSpatializationEnabled) { + ma_uint32 iListener; + + /* + When determining the listener to use, we first check to see if the sound is pinned to a + specific listener. If so, we use that. Otherwise we just use the closest listener. + */ + if (pEngineNode->pinnedListenerIndex != MA_LISTENER_INDEX_CLOSEST && pEngineNode->pinnedListenerIndex < ma_engine_get_listener_count(pEngineNode->pEngine)) { + iListener = pEngineNode->pinnedListenerIndex; + } else { + iListener = ma_engine_find_closest_listener(pEngineNode->pEngine, pEngineNode->spatializer.position.x, pEngineNode->spatializer.position.y, pEngineNode->spatializer.position.z); + } + + ma_spatializer_process_pcm_frames(&pEngineNode->spatializer, &pEngineNode->pEngine->listeners[iListener], pRunningFramesOut, pWorkingBuffer, framesJustProcessedOut); + } else { + /* No spatialization, but we still need to do channel conversion. */ + if (channelsIn == channelsOut) { + /* No channel conversion required. Just copy straight to the output buffer. */ + ma_copy_pcm_frames(pRunningFramesOut, pWorkingBuffer, framesJustProcessedOut, ma_format_f32, channelsOut); + } else { + /* Channel conversion required. TODO: Add support for channel maps here. */ + ma_channel_map_apply_f32(pRunningFramesOut, NULL, channelsOut, pWorkingBuffer, NULL, channelsIn, framesJustProcessedOut, ma_channel_mix_mode_simple, pEngineNode->pEngine->monoExpansionMode); + } + } + + /* At this point we can guarantee that the output buffer contains valid data. We can process everything in place now. */ + + /* Panning. */ + if (isPanningEnabled) { + ma_panner_process_pcm_frames(&pEngineNode->panner, pRunningFramesOut, pRunningFramesOut, framesJustProcessedOut); /* In-place processing. */ + } + + /* We're done for this chunk. */ + totalFramesProcessedIn += framesJustProcessedIn; + totalFramesProcessedOut += framesJustProcessedOut; + + /* If we didn't process any output frames this iteration it means we've either run out of input data, or run out of room in the output buffer. */ + if (framesJustProcessedOut == 0) { + break; + } + } + + /* At this point we're done processing. */ + *pFrameCountIn = totalFramesProcessedIn; + *pFrameCountOut = totalFramesProcessedOut; +} + +static void ma_engine_node_process_pcm_frames__sound(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + /* For sounds, we need to first read from the data source. Then we need to apply the engine effects (pan, pitch, fades, etc.). */ + ma_result result = MA_SUCCESS; + ma_sound* pSound = (ma_sound*)pNode; + ma_uint32 frameCount = *pFrameCountOut; + ma_uint32 totalFramesRead = 0; + ma_format dataSourceFormat; + ma_uint32 dataSourceChannels; + ma_uint8 temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; + ma_uint32 tempCapInFrames; + + /* This is a data source node which means no input buses. */ + (void)ppFramesIn; + (void)pFrameCountIn; + + /* If we're marked at the end we need to stop the sound and do nothing. */ + if (ma_sound_at_end(pSound)) { + ma_sound_stop(pSound); + *pFrameCountOut = 0; + return; + } + + /* If we're seeking, do so now before reading. */ + if (pSound->seekTarget != MA_SEEK_TARGET_NONE) { + ma_data_source_seek_to_pcm_frame(pSound->pDataSource, pSound->seekTarget); + + /* Any time-dependant effects need to have their times updated. */ + ma_node_set_time(pSound, pSound->seekTarget); + + pSound->seekTarget = MA_SEEK_TARGET_NONE; + } + + /* + We want to update the pitch once. For sounds, this can be either at the start or at the end. If + we don't force this to only ever be updating once, we could end up in a situation where + retrieving the required input frame count ends up being different to what we actually retrieve. + What could happen is that the required input frame count is calculated, the pitch is update, + and then this processing function is called resulting in a different number of input frames + being processed. Do not call this in ma_engine_node_process_pcm_frames__general() or else + you'll hit the aforementioned bug. + */ + ma_engine_node_update_pitch_if_required(&pSound->engineNode); + + /* + For the convenience of the caller, we're doing to allow data sources to use non-floating-point formats and channel counts that differ + from the main engine. + */ + result = ma_data_source_get_data_format(pSound->pDataSource, &dataSourceFormat, &dataSourceChannels, NULL, NULL, 0); + if (result == MA_SUCCESS) { + tempCapInFrames = sizeof(temp) / ma_get_bytes_per_frame(dataSourceFormat, dataSourceChannels); + + /* Keep reading until we've read as much as was requested or we reach the end of the data source. */ + while (totalFramesRead < frameCount) { + ma_uint32 framesRemaining = frameCount - totalFramesRead; + ma_uint32 framesToRead; + ma_uint64 framesJustRead; + ma_uint32 frameCountIn; + ma_uint32 frameCountOut; + const float* pRunningFramesIn; + float* pRunningFramesOut; + + /* + The first thing we need to do is read into the temporary buffer. We can calculate exactly + how many input frames we'll need after resampling. + */ + framesToRead = (ma_uint32)ma_engine_node_get_required_input_frame_count(&pSound->engineNode, framesRemaining); + if (framesToRead > tempCapInFrames) { + framesToRead = tempCapInFrames; + } + + result = ma_data_source_read_pcm_frames(pSound->pDataSource, temp, framesToRead, &framesJustRead); + + /* If we reached the end of the sound we'll want to mark it as at the end and stop it. This should never be returned for looping sounds. */ + if (result == MA_AT_END) { + c89atomic_exchange_32(&pSound->atEnd, MA_TRUE); /* This will be set to false in ma_sound_start(). */ + } + + pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(ppFramesOut[0], totalFramesRead, ma_engine_get_channels(ma_sound_get_engine(pSound))); + + frameCountIn = (ma_uint32)framesJustRead; + frameCountOut = framesRemaining; + + /* Convert if necessary. */ + if (dataSourceFormat == ma_format_f32) { + /* Fast path. No data conversion necessary. */ + pRunningFramesIn = (float*)temp; + ma_engine_node_process_pcm_frames__general(&pSound->engineNode, &pRunningFramesIn, &frameCountIn, &pRunningFramesOut, &frameCountOut); + } else { + /* Slow path. Need to do sample format conversion to f32. If we give the f32 buffer the same count as the first temp buffer, we're guaranteed it'll be large enough. */ + float tempf32[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; /* Do not do `MA_DATA_CONVERTER_STACK_BUFFER_SIZE/sizeof(float)` here like we've done in other places. */ + ma_convert_pcm_frames_format(tempf32, ma_format_f32, temp, dataSourceFormat, framesJustRead, dataSourceChannels, ma_dither_mode_none); + + /* Now that we have our samples in f32 format we can process like normal. */ + pRunningFramesIn = tempf32; + ma_engine_node_process_pcm_frames__general(&pSound->engineNode, &pRunningFramesIn, &frameCountIn, &pRunningFramesOut, &frameCountOut); + } + + /* We should have processed all of our input frames since we calculated the required number of input frames at the top. */ + MA_ASSERT(frameCountIn == framesJustRead); + totalFramesRead += (ma_uint32)frameCountOut; /* Safe cast. */ + + if (result != MA_SUCCESS || ma_sound_at_end(pSound)) { + break; /* Might have reached the end. */ + } + } + } + + *pFrameCountOut = totalFramesRead; +} + +static void ma_engine_node_process_pcm_frames__group(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + /* + Make sure the pitch is updated before trying to read anything. It's important that this is done + only once and not in ma_engine_node_process_pcm_frames__general(). The reason for this is that + ma_engine_node_process_pcm_frames__general() will call ma_engine_node_get_required_input_frame_count(), + and if another thread modifies the pitch just after that call it can result in a glitch due to + the input rate changing. + */ + ma_engine_node_update_pitch_if_required((ma_engine_node*)pNode); + + /* For groups, the input data has already been read and we just need to apply the effect. */ + ma_engine_node_process_pcm_frames__general((ma_engine_node*)pNode, ppFramesIn, pFrameCountIn, ppFramesOut, pFrameCountOut); +} + +static ma_result ma_engine_node_get_required_input_frame_count__group(ma_node* pNode, ma_uint32 outputFrameCount, ma_uint32* pInputFrameCount) +{ + ma_uint64 inputFrameCount; + + MA_ASSERT(pInputFrameCount != NULL); + + /* Our pitch will affect this calculation. We need to update it. */ + ma_engine_node_update_pitch_if_required((ma_engine_node*)pNode); + + inputFrameCount = ma_engine_node_get_required_input_frame_count((ma_engine_node*)pNode, outputFrameCount); + if (inputFrameCount > 0xFFFFFFFF) { + inputFrameCount = 0xFFFFFFFF; /* Will never happen because miniaudio will only ever process in relatively small chunks. */ + } + + *pInputFrameCount = (ma_uint32)inputFrameCount; + + return MA_SUCCESS; +} + + +static ma_node_vtable g_ma_engine_node_vtable__sound = +{ + ma_engine_node_process_pcm_frames__sound, + NULL, /* onGetRequiredInputFrameCount */ + 0, /* Sounds are data source nodes which means they have zero inputs (their input is drawn from the data source itself). */ + 1, /* Sounds have one output bus. */ + 0 /* Default flags. */ +}; + +static ma_node_vtable g_ma_engine_node_vtable__group = +{ + ma_engine_node_process_pcm_frames__group, + ma_engine_node_get_required_input_frame_count__group, + 1, /* Groups have one input bus. */ + 1, /* Groups have one output bus. */ + MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES /* The engine node does resampling so should let miniaudio know about it. */ +}; + + + +static ma_node_config ma_engine_node_base_node_config_init(const ma_engine_node_config* pConfig) +{ + ma_node_config baseNodeConfig; + + if (pConfig->type == ma_engine_node_type_sound) { + /* Sound. */ + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_engine_node_vtable__sound; + baseNodeConfig.initialState = ma_node_state_stopped; /* Sounds are stopped by default. */ + } else { + /* Group. */ + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_engine_node_vtable__group; + baseNodeConfig.initialState = ma_node_state_started; /* Groups are started by default. */ + } + + return baseNodeConfig; +} + +static ma_spatializer_config ma_engine_node_spatializer_config_init(const ma_node_config* pBaseNodeConfig) +{ + return ma_spatializer_config_init(pBaseNodeConfig->pInputChannels[0], pBaseNodeConfig->pOutputChannels[0]); +} + +typedef struct +{ + size_t sizeInBytes; + size_t baseNodeOffset; + size_t resamplerOffset; + size_t spatializerOffset; +} ma_engine_node_heap_layout; + +static ma_result ma_engine_node_get_heap_layout(const ma_engine_node_config* pConfig, ma_engine_node_heap_layout* pHeapLayout) +{ + ma_result result; + size_t tempHeapSize; + ma_node_config baseNodeConfig; + ma_linear_resampler_config resamplerConfig; + ma_spatializer_config spatializerConfig; + ma_uint32 channelsIn; + ma_uint32 channelsOut; + + MA_ASSERT(pHeapLayout); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->pEngine == NULL) { + return MA_INVALID_ARGS; /* An engine must be specified. */ + } + + pHeapLayout->sizeInBytes = 0; + + channelsIn = (pConfig->channelsIn != 0) ? pConfig->channelsIn : ma_engine_get_channels(pConfig->pEngine); + channelsOut = (pConfig->channelsOut != 0) ? pConfig->channelsOut : ma_engine_get_channels(pConfig->pEngine); + + + /* Base node. */ + baseNodeConfig = ma_engine_node_base_node_config_init(pConfig); + baseNodeConfig.pInputChannels = &channelsIn; + baseNodeConfig.pOutputChannels = &channelsOut; + + result = ma_node_get_heap_size(&baseNodeConfig, &tempHeapSize); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the size of the heap for the base node. */ + } + + pHeapLayout->baseNodeOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(tempHeapSize); + + + /* Resmapler. */ + resamplerConfig = ma_linear_resampler_config_init(ma_format_f32, channelsIn, 1, 1); /* Input and output sample rates don't affect the calculation of the heap size. */ + resamplerConfig.lpfOrder = 0; + + result = ma_linear_resampler_get_heap_size(&resamplerConfig, &tempHeapSize); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the size of the heap for the resampler. */ + } + + pHeapLayout->resamplerOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(tempHeapSize); + + + /* Spatializer. */ + spatializerConfig = ma_engine_node_spatializer_config_init(&baseNodeConfig); + + result = ma_spatializer_get_heap_size(&spatializerConfig, &tempHeapSize); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the size of the heap for the spatializer. */ + } + + pHeapLayout->spatializerOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(tempHeapSize); + + + return MA_SUCCESS; +} + +MA_API ma_result ma_engine_node_get_heap_size(const ma_engine_node_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_engine_node_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_engine_node_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_engine_node_init_preallocated(const ma_engine_node_config* pConfig, void* pHeap, ma_engine_node* pEngineNode) +{ + ma_result result; + ma_engine_node_heap_layout heapLayout; + ma_node_config baseNodeConfig; + ma_linear_resampler_config resamplerConfig; + ma_fader_config faderConfig; + ma_spatializer_config spatializerConfig; + ma_panner_config pannerConfig; + ma_uint32 channelsIn; + ma_uint32 channelsOut; + + if (pEngineNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pEngineNode); + + result = ma_engine_node_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + if (pConfig->pinnedListenerIndex != MA_LISTENER_INDEX_CLOSEST && pConfig->pinnedListenerIndex >= ma_engine_get_listener_count(pConfig->pEngine)) { + return MA_INVALID_ARGS; /* Invalid listener. */ + } + + pEngineNode->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pEngineNode->pEngine = pConfig->pEngine; + pEngineNode->sampleRate = (pConfig->sampleRate > 0) ? pConfig->sampleRate : ma_engine_get_sample_rate(pEngineNode->pEngine); + pEngineNode->pitch = 1; + pEngineNode->oldPitch = 1; + pEngineNode->oldDopplerPitch = 1; + pEngineNode->isPitchDisabled = pConfig->isPitchDisabled; + pEngineNode->isSpatializationDisabled = pConfig->isSpatializationDisabled; + pEngineNode->pinnedListenerIndex = pConfig->pinnedListenerIndex; + + + channelsIn = (pConfig->channelsIn != 0) ? pConfig->channelsIn : ma_engine_get_channels(pConfig->pEngine); + channelsOut = (pConfig->channelsOut != 0) ? pConfig->channelsOut : ma_engine_get_channels(pConfig->pEngine); + + + /* Base node. */ + baseNodeConfig = ma_engine_node_base_node_config_init(pConfig); + baseNodeConfig.pInputChannels = &channelsIn; + baseNodeConfig.pOutputChannels = &channelsOut; + + result = ma_node_init_preallocated(&pConfig->pEngine->nodeGraph, &baseNodeConfig, ma_offset_ptr(pHeap, heapLayout.baseNodeOffset), &pEngineNode->baseNode); + if (result != MA_SUCCESS) { + goto error0; + } + + + /* + We can now initialize the effects we need in order to implement the engine node. There's a + defined order of operations here, mainly centered around when we convert our channels from the + data source's native channel count to the engine's channel count. As a rule, we want to do as + much computation as possible before spatialization because there's a chance that will increase + the channel count, thereby increasing the amount of work needing to be done to process. + */ + + /* We'll always do resampling first. */ + resamplerConfig = ma_linear_resampler_config_init(ma_format_f32, baseNodeConfig.pInputChannels[0], pEngineNode->sampleRate, ma_engine_get_sample_rate(pEngineNode->pEngine)); + resamplerConfig.lpfOrder = 0; /* <-- Need to disable low-pass filtering for pitch shifting for now because there's cases where the biquads are becoming unstable. Need to figure out a better fix for this. */ + + result = ma_linear_resampler_init_preallocated(&resamplerConfig, ma_offset_ptr(pHeap, heapLayout.resamplerOffset), &pEngineNode->resampler); + if (result != MA_SUCCESS) { + goto error1; + } + + + /* After resampling will come the fader. */ + faderConfig = ma_fader_config_init(ma_format_f32, baseNodeConfig.pInputChannels[0], ma_engine_get_sample_rate(pEngineNode->pEngine)); + + result = ma_fader_init(&faderConfig, &pEngineNode->fader); + if (result != MA_SUCCESS) { + goto error2; + } + + + /* + Spatialization comes next. We spatialize based ont he node's output channel count. It's up the caller to + ensure channels counts link up correctly in the node graph. + */ + spatializerConfig = ma_engine_node_spatializer_config_init(&baseNodeConfig); + spatializerConfig.gainSmoothTimeInFrames = pEngineNode->pEngine->gainSmoothTimeInFrames; + + result = ma_spatializer_init_preallocated(&spatializerConfig, ma_offset_ptr(pHeap, heapLayout.spatializerOffset), &pEngineNode->spatializer); + if (result != MA_SUCCESS) { + goto error2; + } + + + /* + After spatialization comes panning. We need to do this after spatialization because otherwise we wouldn't + be able to pan mono sounds. + */ + pannerConfig = ma_panner_config_init(ma_format_f32, baseNodeConfig.pOutputChannels[0]); + + result = ma_panner_init(&pannerConfig, &pEngineNode->panner); + if (result != MA_SUCCESS) { + goto error3; + } + + return MA_SUCCESS; + + /* No need for allocation callbacks here because we use a preallocated heap. */ +error3: ma_spatializer_uninit(&pEngineNode->spatializer, NULL); +error2: ma_linear_resampler_uninit(&pEngineNode->resampler, NULL); +error1: ma_node_uninit(&pEngineNode->baseNode, NULL); +error0: return result; +} + +MA_API ma_result ma_engine_node_init(const ma_engine_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_engine_node* pEngineNode) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_engine_node_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_engine_node_init_preallocated(pConfig, pHeap, pEngineNode); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pEngineNode->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_engine_node_uninit(ma_engine_node* pEngineNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + /* + The base node always needs to be uninitialized first to ensure it's detached from the graph completely before we + destroy anything that might be in the middle of being used by the processing function. + */ + ma_node_uninit(&pEngineNode->baseNode, pAllocationCallbacks); + + /* Now that the node has been uninitialized we can safely uninitialize the rest. */ + ma_spatializer_uninit(&pEngineNode->spatializer, pAllocationCallbacks); + ma_linear_resampler_uninit(&pEngineNode->resampler, pAllocationCallbacks); + + /* Free the heap last. */ + if (pEngineNode->_ownsHeap) { + ma_free(pEngineNode->_pHeap, pAllocationCallbacks); + } +} + + +MA_API ma_sound_config ma_sound_config_init(void) +{ + ma_sound_config config; + + MA_ZERO_OBJECT(&config); + config.rangeEndInPCMFrames = ~((ma_uint64)0); + config.loopPointEndInPCMFrames = ~((ma_uint64)0); + + return config; +} + +MA_API ma_sound_group_config ma_sound_group_config_init(void) +{ + ma_sound_group_config config; + + MA_ZERO_OBJECT(&config); + + return config; +} + + +MA_API ma_engine_config ma_engine_config_init(void) +{ + ma_engine_config config; + + MA_ZERO_OBJECT(&config); + config.listenerCount = 1; /* Always want at least one listener. */ + config.monoExpansionMode = ma_mono_expansion_mode_default; + + return config; +} + + +#if !defined(MA_NO_DEVICE_IO) +static void ma_engine_data_callback_internal(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) +{ + ma_engine* pEngine = (ma_engine*)pDevice->pUserData; + + (void)pFramesIn; + + /* + Experiment: Try processing a resource manager job if we're on the Emscripten build. + + This serves two purposes: + + 1) It ensures jobs are actually processed at some point since we cannot guarantee that the + caller is doing the right thing and calling ma_resource_manager_process_next_job(); and + + 2) It's an attempt at working around an issue where processing jobs on the Emscripten main + loop doesn't work as well as it should. When trying to load sounds without the `DECODE` + flag or with the `ASYNC` flag, the sound data is just not able to be loaded in time + before the callback is processed. I think it's got something to do with the single- + threaded nature of Web, but I'm not entirely sure. + */ + #if !defined(MA_NO_RESOURCE_MANAGER) && defined(MA_EMSCRIPTEN) + { + if (pEngine->pResourceManager != NULL) { + if ((pEngine->pResourceManager->config.flags & MA_RESOURCE_MANAGER_FLAG_NO_THREADING) != 0) { + ma_resource_manager_process_next_job(pEngine->pResourceManager); + } + } + } + #endif + + ma_engine_read_pcm_frames(pEngine, pFramesOut, frameCount, NULL); +} +#endif + +MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEngine) +{ + ma_result result; + ma_node_graph_config nodeGraphConfig; + ma_engine_config engineConfig; + ma_spatializer_listener_config listenerConfig; + ma_uint32 iListener; + + if (pEngine != NULL) { + MA_ZERO_OBJECT(pEngine); + } + + /* The config is allowed to be NULL in which case we use defaults for everything. */ + if (pConfig != NULL) { + engineConfig = *pConfig; + } else { + engineConfig = ma_engine_config_init(); + } + + pEngine->monoExpansionMode = engineConfig.monoExpansionMode; + ma_allocation_callbacks_init_copy(&pEngine->allocationCallbacks, &engineConfig.allocationCallbacks); + + #if !defined(MA_NO_RESOURCE_MANAGER) + { + pEngine->pResourceManager = engineConfig.pResourceManager; + } + #endif + + #if !defined(MA_NO_DEVICE_IO) + { + pEngine->pDevice = engineConfig.pDevice; + + /* If we don't have a device, we need one. */ + if (pEngine->pDevice == NULL && engineConfig.noDevice == MA_FALSE) { + ma_device_config deviceConfig; + + pEngine->pDevice = (ma_device*)ma_malloc(sizeof(*pEngine->pDevice), &pEngine->allocationCallbacks); + if (pEngine->pDevice == NULL) { + return MA_OUT_OF_MEMORY; + } + + deviceConfig = ma_device_config_init(ma_device_type_playback); + deviceConfig.playback.pDeviceID = engineConfig.pPlaybackDeviceID; + deviceConfig.playback.format = ma_format_f32; + deviceConfig.playback.channels = engineConfig.channels; + deviceConfig.sampleRate = engineConfig.sampleRate; + deviceConfig.dataCallback = ma_engine_data_callback_internal; + deviceConfig.pUserData = pEngine; + deviceConfig.periodSizeInFrames = engineConfig.periodSizeInFrames; + deviceConfig.periodSizeInMilliseconds = engineConfig.periodSizeInMilliseconds; + deviceConfig.noPreSilencedOutputBuffer = MA_TRUE; /* We'll always be outputting to every frame in the callback so there's no need for a pre-silenced buffer. */ + deviceConfig.noClip = MA_TRUE; /* The engine will do clipping itself. */ + + if (engineConfig.pContext == NULL) { + ma_context_config contextConfig = ma_context_config_init(); + contextConfig.allocationCallbacks = pEngine->allocationCallbacks; + contextConfig.pLog = engineConfig.pLog; + + /* If the engine config does not specify a log, use the resource manager's if we have one. */ + #ifndef MA_NO_RESOURCE_MANAGER + { + if (contextConfig.pLog == NULL && engineConfig.pResourceManager != NULL) { + contextConfig.pLog = ma_resource_manager_get_log(engineConfig.pResourceManager); + } + } + #endif + + result = ma_device_init_ex(NULL, 0, &contextConfig, &deviceConfig, pEngine->pDevice); + } else { + result = ma_device_init(engineConfig.pContext, &deviceConfig, pEngine->pDevice); + } + + if (result != MA_SUCCESS) { + ma_free(pEngine->pDevice, &pEngine->allocationCallbacks); + pEngine->pDevice = NULL; + return result; + } + + pEngine->ownsDevice = MA_TRUE; + } + + /* Update the channel count and sample rate of the engine config so we can reference it below. */ + if (pEngine->pDevice != NULL) { + engineConfig.channels = pEngine->pDevice->playback.channels; + engineConfig.sampleRate = pEngine->pDevice->sampleRate; + } + } + #endif + + if (engineConfig.channels == 0 || engineConfig.sampleRate == 0) { + return MA_INVALID_ARGS; + } + + pEngine->sampleRate = engineConfig.sampleRate; + + /* The engine always uses either the log that was passed into the config, or the context's log is available. */ + if (engineConfig.pLog != NULL) { + pEngine->pLog = engineConfig.pLog; + } else { + #if !defined(MA_NO_DEVICE_IO) + { + pEngine->pLog = ma_device_get_log(pEngine->pDevice); + } + #else + { + pEngine->pLog = NULL; + } + #endif + } + + + /* The engine is a node graph. This needs to be initialized after we have the device so we can can determine the channel count. */ + nodeGraphConfig = ma_node_graph_config_init(engineConfig.channels); + + result = ma_node_graph_init(&nodeGraphConfig, &pEngine->allocationCallbacks, &pEngine->nodeGraph); + if (result != MA_SUCCESS) { + goto on_error_1; + } + + + /* We need at least one listener. */ + if (engineConfig.listenerCount == 0) { + engineConfig.listenerCount = 1; + } + + if (engineConfig.listenerCount > MA_ENGINE_MAX_LISTENERS) { + result = MA_INVALID_ARGS; /* Too many listeners. */ + goto on_error_1; + } + + for (iListener = 0; iListener < engineConfig.listenerCount; iListener += 1) { + listenerConfig = ma_spatializer_listener_config_init(ma_node_graph_get_channels(&pEngine->nodeGraph)); + + /* + If we're using a device, use the device's channel map for the listener. Otherwise just use + miniaudio's default channel map. + */ + #if !defined(MA_NO_DEVICE_IO) + { + if (pEngine->pDevice != NULL) { + listenerConfig.pChannelMapOut = pEngine->pDevice->playback.channelMap; + } + } + #endif + + result = ma_spatializer_listener_init(&listenerConfig, &pEngine->allocationCallbacks, &pEngine->listeners[iListener]); /* TODO: Change this to a pre-allocated heap. */ + if (result != MA_SUCCESS) { + goto on_error_2; + } + + pEngine->listenerCount += 1; + } + + + /* Gain smoothing for spatialized sounds. */ + pEngine->gainSmoothTimeInFrames = engineConfig.gainSmoothTimeInFrames; + if (pEngine->gainSmoothTimeInFrames == 0) { + ma_uint32 gainSmoothTimeInMilliseconds = engineConfig.gainSmoothTimeInMilliseconds; + if (gainSmoothTimeInMilliseconds == 0) { + gainSmoothTimeInMilliseconds = 8; + } + + pEngine->gainSmoothTimeInFrames = (gainSmoothTimeInMilliseconds * ma_engine_get_sample_rate(pEngine)) / 1000; /* 8ms by default. */ + } + + + /* We need a resource manager. */ + #ifndef MA_NO_RESOURCE_MANAGER + { + if (pEngine->pResourceManager == NULL) { + ma_resource_manager_config resourceManagerConfig; + + pEngine->pResourceManager = (ma_resource_manager*)ma_malloc(sizeof(*pEngine->pResourceManager), &pEngine->allocationCallbacks); + if (pEngine->pResourceManager == NULL) { + result = MA_OUT_OF_MEMORY; + goto on_error_2; + } + + resourceManagerConfig = ma_resource_manager_config_init(); + resourceManagerConfig.pLog = pEngine->pLog; /* Always use the engine's log for internally-managed resource managers. */ + resourceManagerConfig.decodedFormat = ma_format_f32; + resourceManagerConfig.decodedChannels = 0; /* Leave the decoded channel count as 0 so we can get good spatialization. */ + resourceManagerConfig.decodedSampleRate = ma_engine_get_sample_rate(pEngine); + ma_allocation_callbacks_init_copy(&resourceManagerConfig.allocationCallbacks, &pEngine->allocationCallbacks); + resourceManagerConfig.pVFS = engineConfig.pResourceManagerVFS; + + /* The Emscripten build cannot use threads. */ + #if defined(MA_EMSCRIPTEN) + { + resourceManagerConfig.jobThreadCount = 0; + resourceManagerConfig.flags |= MA_RESOURCE_MANAGER_FLAG_NO_THREADING; + } + #endif + + result = ma_resource_manager_init(&resourceManagerConfig, pEngine->pResourceManager); + if (result != MA_SUCCESS) { + goto on_error_3; + } + + pEngine->ownsResourceManager = MA_TRUE; + } + } + #endif + + /* Setup some stuff for inlined sounds. That is sounds played with ma_engine_play_sound(). */ + pEngine->inlinedSoundLock = 0; + pEngine->pInlinedSoundHead = NULL; + + /* Start the engine if required. This should always be the last step. */ + #if !defined(MA_NO_DEVICE_IO) + { + if (engineConfig.noAutoStart == MA_FALSE && pEngine->pDevice != NULL) { + result = ma_engine_start(pEngine); + if (result != MA_SUCCESS) { + goto on_error_4; /* Failed to start the engine. */ + } + } + } + #endif + + return MA_SUCCESS; + +#if !defined(MA_NO_DEVICE_IO) +on_error_4: +#endif +#if !defined(MA_NO_RESOURCE_MANAGER) +on_error_3: + if (pEngine->ownsResourceManager) { + ma_free(pEngine->pResourceManager, &pEngine->allocationCallbacks); + } +#endif /* MA_NO_RESOURCE_MANAGER */ +on_error_2: + for (iListener = 0; iListener < pEngine->listenerCount; iListener += 1) { + ma_spatializer_listener_uninit(&pEngine->listeners[iListener], &pEngine->allocationCallbacks); + } + + ma_node_graph_uninit(&pEngine->nodeGraph, &pEngine->allocationCallbacks); +on_error_1: + #if !defined(MA_NO_DEVICE_IO) + { + if (pEngine->ownsDevice) { + ma_device_uninit(pEngine->pDevice); + ma_free(pEngine->pDevice, &pEngine->allocationCallbacks); + } + } + #endif + + return result; +} + +MA_API void ma_engine_uninit(ma_engine* pEngine) +{ + ma_uint32 iListener; + + if (pEngine == NULL) { + return; + } + + /* The device must be uninitialized before the node graph to ensure the audio thread doesn't try accessing it. */ + #if !defined(MA_NO_DEVICE_IO) + { + if (pEngine->ownsDevice) { + ma_device_uninit(pEngine->pDevice); + ma_free(pEngine->pDevice, &pEngine->allocationCallbacks); + } else { + if (pEngine->pDevice != NULL) { + ma_device_stop(pEngine->pDevice); + } + } + } + #endif + + /* + All inlined sounds need to be deleted. I'm going to use a lock here just to future proof in case + I want to do some kind of garbage collection later on. + */ + ma_spinlock_lock(&pEngine->inlinedSoundLock); + { + for (;;) { + ma_sound_inlined* pSoundToDelete = pEngine->pInlinedSoundHead; + if (pSoundToDelete == NULL) { + break; /* Done. */ + } + + pEngine->pInlinedSoundHead = pSoundToDelete->pNext; + + ma_sound_uninit(&pSoundToDelete->sound); + ma_free(pSoundToDelete, &pEngine->allocationCallbacks); + } + } + ma_spinlock_unlock(&pEngine->inlinedSoundLock); + + for (iListener = 0; iListener < pEngine->listenerCount; iListener += 1) { + ma_spatializer_listener_uninit(&pEngine->listeners[iListener], &pEngine->allocationCallbacks); + } + + /* Make sure the node graph is uninitialized after the audio thread has been shutdown to prevent accessing of the node graph after being uninitialized. */ + ma_node_graph_uninit(&pEngine->nodeGraph, &pEngine->allocationCallbacks); + + /* Uninitialize the resource manager last to ensure we don't have a thread still trying to access it. */ +#ifndef MA_NO_RESOURCE_MANAGER + if (pEngine->ownsResourceManager) { + ma_resource_manager_uninit(pEngine->pResourceManager); + ma_free(pEngine->pResourceManager, &pEngine->allocationCallbacks); + } +#endif +} + +MA_API ma_result ma_engine_read_pcm_frames(ma_engine* pEngine, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + return ma_node_graph_read_pcm_frames(&pEngine->nodeGraph, pFramesOut, frameCount, pFramesRead); +} + +MA_API ma_node_graph* ma_engine_get_node_graph(ma_engine* pEngine) +{ + if (pEngine == NULL) { + return NULL; + } + + return &pEngine->nodeGraph; +} + +#if !defined(MA_NO_RESOURCE_MANAGER) +MA_API ma_resource_manager* ma_engine_get_resource_manager(ma_engine* pEngine) +{ + if (pEngine == NULL) { + return NULL; + } + + #if !defined(MA_NO_RESOURCE_MANAGER) + { + return pEngine->pResourceManager; + } + #else + { + return NULL; + } + #endif +} +#endif + +MA_API ma_device* ma_engine_get_device(ma_engine* pEngine) +{ + if (pEngine == NULL) { + return NULL; + } + + #if !defined(MA_NO_DEVICE_IO) + { + return pEngine->pDevice; + } + #else + { + return NULL; + } + #endif +} + +MA_API ma_log* ma_engine_get_log(ma_engine* pEngine) +{ + if (pEngine == NULL) { + return NULL; + } + + if (pEngine->pLog != NULL) { + return pEngine->pLog; + } else { + #if !defined(MA_NO_DEVICE_IO) + { + return ma_device_get_log(ma_engine_get_device(pEngine)); + } + #else + { + return NULL; + } + #endif + } +} + +MA_API ma_node* ma_engine_get_endpoint(ma_engine* pEngine) +{ + return ma_node_graph_get_endpoint(&pEngine->nodeGraph); +} + +MA_API ma_uint64 ma_engine_get_time(const ma_engine* pEngine) +{ + return ma_node_graph_get_time(&pEngine->nodeGraph); +} + +MA_API ma_uint64 ma_engine_set_time(ma_engine* pEngine, ma_uint64 globalTime) +{ + return ma_node_graph_set_time(&pEngine->nodeGraph, globalTime); +} + +MA_API ma_uint32 ma_engine_get_channels(const ma_engine* pEngine) +{ + return ma_node_graph_get_channels(&pEngine->nodeGraph); +} + +MA_API ma_uint32 ma_engine_get_sample_rate(const ma_engine* pEngine) +{ + if (pEngine == NULL) { + return 0; + } + + return pEngine->sampleRate; +} + + +MA_API ma_result ma_engine_start(ma_engine* pEngine) +{ + ma_result result; + + if (pEngine == NULL) { + return MA_INVALID_ARGS; + } + + #if !defined(MA_NO_DEVICE_IO) + { + if (pEngine->pDevice != NULL) { + result = ma_device_start(pEngine->pDevice); + } else { + result = MA_INVALID_OPERATION; /* The engine is running without a device which means there's no real notion of "starting" the engine. */ + } + } + #else + { + result = MA_INVALID_OPERATION; /* Device IO is disabled, so there's no real notion of "starting" the engine. */ + } + #endif + + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_engine_stop(ma_engine* pEngine) +{ + ma_result result; + + if (pEngine == NULL) { + return MA_INVALID_ARGS; + } + + #if !defined(MA_NO_DEVICE_IO) + { + if (pEngine->pDevice != NULL) { + result = ma_device_stop(pEngine->pDevice); + } else { + result = MA_INVALID_OPERATION; /* The engine is running without a device which means there's no real notion of "stopping" the engine. */ + } + } + #else + { + result = MA_INVALID_OPERATION; /* Device IO is disabled, so there's no real notion of "stopping" the engine. */ + } + #endif + + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_engine_set_volume(ma_engine* pEngine, float volume) +{ + if (pEngine == NULL) { + return MA_INVALID_ARGS; + } + + return ma_node_set_output_bus_volume(ma_node_graph_get_endpoint(&pEngine->nodeGraph), 0, volume); +} + +MA_API ma_result ma_engine_set_gain_db(ma_engine* pEngine, float gainDB) +{ + if (pEngine == NULL) { + return MA_INVALID_ARGS; + } + + return ma_node_set_output_bus_volume(ma_node_graph_get_endpoint(&pEngine->nodeGraph), 0, ma_volume_db_to_linear(gainDB)); +} + + +MA_API ma_uint32 ma_engine_get_listener_count(const ma_engine* pEngine) +{ + if (pEngine == NULL) { + return 0; + } + + return pEngine->listenerCount; +} + +MA_API ma_uint32 ma_engine_find_closest_listener(const ma_engine* pEngine, float absolutePosX, float absolutePosY, float absolutePosZ) +{ + ma_uint32 iListener; + ma_uint32 iListenerClosest; + float closestLen2 = MA_FLT_MAX; + + if (pEngine == NULL || pEngine->listenerCount == 1) { + return 0; + } + + iListenerClosest = 0; + for (iListener = 0; iListener < pEngine->listenerCount; iListener += 1) { + if (ma_engine_listener_is_enabled(pEngine, iListener)) { + float len2 = ma_vec3f_len2(ma_vec3f_sub(pEngine->listeners[iListener].position, ma_vec3f_init_3f(absolutePosX, absolutePosY, absolutePosZ))); + if (closestLen2 > len2) { + closestLen2 = len2; + iListenerClosest = iListener; + } + } + } + + MA_ASSERT(iListenerClosest < 255); + return iListenerClosest; +} + +MA_API void ma_engine_listener_set_position(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return; + } + + ma_spatializer_listener_set_position(&pEngine->listeners[listenerIndex], x, y, z); +} + +MA_API ma_vec3f ma_engine_listener_get_position(const ma_engine* pEngine, ma_uint32 listenerIndex) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return ma_spatializer_listener_get_position(&pEngine->listeners[listenerIndex]); +} + +MA_API void ma_engine_listener_set_direction(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return; + } + + ma_spatializer_listener_set_direction(&pEngine->listeners[listenerIndex], x, y, z); +} + +MA_API ma_vec3f ma_engine_listener_get_direction(const ma_engine* pEngine, ma_uint32 listenerIndex) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return ma_vec3f_init_3f(0, 0, -1); + } + + return ma_spatializer_listener_get_direction(&pEngine->listeners[listenerIndex]); +} + +MA_API void ma_engine_listener_set_velocity(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return; + } + + ma_spatializer_listener_set_velocity(&pEngine->listeners[listenerIndex], x, y, z); +} + +MA_API ma_vec3f ma_engine_listener_get_velocity(const ma_engine* pEngine, ma_uint32 listenerIndex) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return ma_spatializer_listener_get_velocity(&pEngine->listeners[listenerIndex]); +} + +MA_API void ma_engine_listener_set_cone(ma_engine* pEngine, ma_uint32 listenerIndex, float innerAngleInRadians, float outerAngleInRadians, float outerGain) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return; + } + + ma_spatializer_listener_set_cone(&pEngine->listeners[listenerIndex], innerAngleInRadians, outerAngleInRadians, outerGain); +} + +MA_API void ma_engine_listener_get_cone(const ma_engine* pEngine, ma_uint32 listenerIndex, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain) +{ + if (pInnerAngleInRadians != NULL) { + *pInnerAngleInRadians = 0; + } + + if (pOuterAngleInRadians != NULL) { + *pOuterAngleInRadians = 0; + } + + if (pOuterGain != NULL) { + *pOuterGain = 0; + } + + ma_spatializer_listener_get_cone(&pEngine->listeners[listenerIndex], pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain); +} + +MA_API void ma_engine_listener_set_world_up(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return; + } + + ma_spatializer_listener_set_world_up(&pEngine->listeners[listenerIndex], x, y, z); +} + +MA_API ma_vec3f ma_engine_listener_get_world_up(const ma_engine* pEngine, ma_uint32 listenerIndex) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return ma_vec3f_init_3f(0, 1, 0); + } + + return ma_spatializer_listener_get_world_up(&pEngine->listeners[listenerIndex]); +} + +MA_API void ma_engine_listener_set_enabled(ma_engine* pEngine, ma_uint32 listenerIndex, ma_bool32 isEnabled) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return; + } + + ma_spatializer_listener_set_enabled(&pEngine->listeners[listenerIndex], isEnabled); +} + +MA_API ma_bool32 ma_engine_listener_is_enabled(const ma_engine* pEngine, ma_uint32 listenerIndex) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return MA_FALSE; + } + + return ma_spatializer_listener_is_enabled(&pEngine->listeners[listenerIndex]); +} + + +#ifndef MA_NO_RESOURCE_MANAGER +MA_API ma_result ma_engine_play_sound_ex(ma_engine* pEngine, const char* pFilePath, ma_node* pNode, ma_uint32 nodeInputBusIndex) +{ + ma_result result = MA_SUCCESS; + ma_sound_inlined* pSound = NULL; + ma_sound_inlined* pNextSound = NULL; + + if (pEngine == NULL || pFilePath == NULL) { + return MA_INVALID_ARGS; + } + + /* Attach to the endpoint node if nothing is specicied. */ + if (pNode == NULL) { + pNode = ma_node_graph_get_endpoint(&pEngine->nodeGraph); + nodeInputBusIndex = 0; + } + + /* + We want to check if we can recycle an already-allocated inlined sound. Since this is just a + helper I'm not *too* concerned about performance here and I'm happy to use a lock to keep + the implementation simple. Maybe this can be optimized later if there's enough demand, but + if this function is being used it probably means the caller doesn't really care too much. + + What we do is check the atEnd flag. When this is true, we can recycle the sound. Otherwise + we just keep iterating. If we reach the end without finding a sound to recycle we just + allocate a new one. This doesn't scale well for a massive number of sounds being played + simultaneously as we don't ever actually free the sound objects. Some kind of garbage + collection routine might be valuable for this which I'll think about. + */ + ma_spinlock_lock(&pEngine->inlinedSoundLock); + { + ma_uint32 soundFlags = 0; + + for (pNextSound = pEngine->pInlinedSoundHead; pNextSound != NULL; pNextSound = pNextSound->pNext) { + if (ma_sound_at_end(&pNextSound->sound)) { + /* + The sound is at the end which means it's available for recycling. All we need to do + is uninitialize it and reinitialize it. All we're doing is recycling memory. + */ + pSound = pNextSound; + c89atomic_fetch_sub_32(&pEngine->inlinedSoundCount, 1); + break; + } + } + + if (pSound != NULL) { + /* + We actually want to detach the sound from the list here. The reason is because we want the sound + to be in a consistent state at the non-recycled case to simplify the logic below. + */ + if (pEngine->pInlinedSoundHead == pSound) { + pEngine->pInlinedSoundHead = pSound->pNext; + } + + if (pSound->pPrev != NULL) { + pSound->pPrev->pNext = pSound->pNext; + } + if (pSound->pNext != NULL) { + pSound->pNext->pPrev = pSound->pPrev; + } + + /* Now the previous sound needs to be uninitialized. */ + ma_sound_uninit(&pNextSound->sound); + } else { + /* No sound available for recycling. Allocate one now. */ + pSound = (ma_sound_inlined*)ma_malloc(sizeof(*pSound), &pEngine->allocationCallbacks); + } + + if (pSound != NULL) { /* Safety check for the allocation above. */ + /* + At this point we should have memory allocated for the inlined sound. We just need + to initialize it like a normal sound now. + */ + soundFlags |= MA_SOUND_FLAG_ASYNC; /* For inlined sounds we don't want to be sitting around waiting for stuff to load so force an async load. */ + soundFlags |= MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT; /* We want specific control over where the sound is attached in the graph. We'll attach it manually just before playing the sound. */ + soundFlags |= MA_SOUND_FLAG_NO_PITCH; /* Pitching isn't usable with inlined sounds, so disable it to save on speed. */ + soundFlags |= MA_SOUND_FLAG_NO_SPATIALIZATION; /* Not currently doing spatialization with inlined sounds, but this might actually change later. For now disable spatialization. Will be removed if we ever add support for spatialization here. */ + + result = ma_sound_init_from_file(pEngine, pFilePath, soundFlags, NULL, NULL, &pSound->sound); + if (result == MA_SUCCESS) { + /* Now attach the sound to the graph. */ + result = ma_node_attach_output_bus(pSound, 0, pNode, nodeInputBusIndex); + if (result == MA_SUCCESS) { + /* At this point the sound should be loaded and we can go ahead and add it to the list. The new item becomes the new head. */ + pSound->pNext = pEngine->pInlinedSoundHead; + pSound->pPrev = NULL; + + pEngine->pInlinedSoundHead = pSound; /* <-- This is what attaches the sound to the list. */ + if (pSound->pNext != NULL) { + pSound->pNext->pPrev = pSound; + } + } else { + ma_free(pSound, &pEngine->allocationCallbacks); + } + } else { + ma_free(pSound, &pEngine->allocationCallbacks); + } + } else { + result = MA_OUT_OF_MEMORY; + } + } + ma_spinlock_unlock(&pEngine->inlinedSoundLock); + + if (result != MA_SUCCESS) { + return result; + } + + /* Finally we can start playing the sound. */ + result = ma_sound_start(&pSound->sound); + if (result != MA_SUCCESS) { + /* Failed to start the sound. We need to mark it for recycling and return an error. */ + c89atomic_exchange_32(&pSound->sound.atEnd, MA_TRUE); + return result; + } + + c89atomic_fetch_add_32(&pEngine->inlinedSoundCount, 1); + return result; +} + +MA_API ma_result ma_engine_play_sound(ma_engine* pEngine, const char* pFilePath, ma_sound_group* pGroup) +{ + return ma_engine_play_sound_ex(pEngine, pFilePath, pGroup, 0); +} +#endif + + +static ma_result ma_sound_preinit(ma_engine* pEngine, ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pSound); + pSound->seekTarget = MA_SEEK_TARGET_NONE; + + if (pEngine == NULL) { + return MA_INVALID_ARGS; + } + + return MA_SUCCESS; +} + +static ma_result ma_sound_init_from_data_source_internal(ma_engine* pEngine, const ma_sound_config* pConfig, ma_sound* pSound) +{ + ma_result result; + ma_engine_node_config engineNodeConfig; + ma_engine_node_type type; /* Will be set to ma_engine_node_type_group if no data source is specified. */ + + /* Do not clear pSound to zero here - that's done at a higher level with ma_sound_preinit(). */ + MA_ASSERT(pEngine != NULL); + MA_ASSERT(pSound != NULL); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + pSound->pDataSource = pConfig->pDataSource; + + if (pConfig->pDataSource != NULL) { + type = ma_engine_node_type_sound; + } else { + type = ma_engine_node_type_group; + } + + /* + Sounds are engine nodes. Before we can initialize this we need to determine the channel count. + If we can't do this we need to abort. It's up to the caller to ensure they're using a data + source that provides this information upfront. + */ + engineNodeConfig = ma_engine_node_config_init(pEngine, type, pConfig->flags); + engineNodeConfig.channelsIn = pConfig->channelsIn; + engineNodeConfig.channelsOut = pConfig->channelsOut; + + /* If we're loading from a data source the input channel count needs to be the data source's native channel count. */ + if (pConfig->pDataSource != NULL) { + result = ma_data_source_get_data_format(pConfig->pDataSource, NULL, &engineNodeConfig.channelsIn, &engineNodeConfig.sampleRate, NULL, 0); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the channel count. */ + } + + if (engineNodeConfig.channelsIn == 0) { + return MA_INVALID_OPERATION; /* Invalid channel count. */ + } + + if (engineNodeConfig.channelsOut == MA_SOUND_SOURCE_CHANNEL_COUNT) { + engineNodeConfig.channelsOut = engineNodeConfig.channelsIn; + } + } + + + /* Getting here means we should have a valid channel count and we can initialize the engine node. */ + result = ma_engine_node_init(&engineNodeConfig, &pEngine->allocationCallbacks, &pSound->engineNode); + if (result != MA_SUCCESS) { + return result; + } + + /* If no attachment is specified, attach the sound straight to the endpoint. */ + if (pConfig->pInitialAttachment == NULL) { + /* No group. Attach straight to the endpoint by default, unless the caller has requested that do not. */ + if ((pConfig->flags & MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT) == 0) { + result = ma_node_attach_output_bus(pSound, 0, ma_node_graph_get_endpoint(&pEngine->nodeGraph), 0); + } + } else { + /* An attachment is specified. Attach to it by default. The sound has only a single output bus, and the config will specify which input bus to attach to. */ + result = ma_node_attach_output_bus(pSound, 0, pConfig->pInitialAttachment, pConfig->initialAttachmentInputBusIndex); + } + + if (result != MA_SUCCESS) { + ma_engine_node_uninit(&pSound->engineNode, &pEngine->allocationCallbacks); + return result; + } + + + /* Apply initial range and looping state to the data source if applicable. */ + if (pConfig->rangeBegInPCMFrames != 0 || pConfig->rangeEndInPCMFrames != ~((ma_uint64)0)) { + ma_data_source_set_range_in_pcm_frames(ma_sound_get_data_source(pSound), pConfig->rangeBegInPCMFrames, pConfig->rangeEndInPCMFrames); + } + + if (pConfig->loopPointBegInPCMFrames != 0 || pConfig->loopPointEndInPCMFrames != ~((ma_uint64)0)) { + ma_data_source_set_range_in_pcm_frames(ma_sound_get_data_source(pSound), pConfig->loopPointBegInPCMFrames, pConfig->loopPointEndInPCMFrames); + } + + ma_sound_set_looping(pSound, pConfig->isLooping); + + return MA_SUCCESS; +} + +#ifndef MA_NO_RESOURCE_MANAGER +MA_API ma_result ma_sound_init_from_file_internal(ma_engine* pEngine, const ma_sound_config* pConfig, ma_sound* pSound) +{ + ma_result result = MA_SUCCESS; + ma_uint32 flags; + ma_sound_config config; + ma_resource_manager_pipeline_notifications notifications; + + /* + The engine requires knowledge of the channel count of the underlying data source before it can + initialize the sound. Therefore, we need to make the resource manager wait until initialization + of the underlying data source to be initialized so we can get access to the channel count. To + do this, the MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT is forced. + + Because we're initializing the data source before the sound, there's a chance the notification + will get triggered before this function returns. This is OK, so long as the caller is aware of + it and can avoid accessing the sound from within the notification. + */ + flags = pConfig->flags | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT; + + pSound->pResourceManagerDataSource = (ma_resource_manager_data_source*)ma_malloc(sizeof(*pSound->pResourceManagerDataSource), &pEngine->allocationCallbacks); + if (pSound->pResourceManagerDataSource == NULL) { + return MA_OUT_OF_MEMORY; + } + + notifications = ma_resource_manager_pipeline_notifications_init(); + notifications.done.pFence = pConfig->pDoneFence; + + /* + We must wrap everything around the fence if one was specified. This ensures ma_fence_wait() does + not return prematurely before the sound has finished initializing. + */ + if (notifications.done.pFence) { ma_fence_acquire(notifications.done.pFence); } + { + ma_resource_manager_data_source_config resourceManagerDataSourceConfig = ma_resource_manager_data_source_config_init(); + resourceManagerDataSourceConfig.pFilePath = pConfig->pFilePath; + resourceManagerDataSourceConfig.pFilePathW = pConfig->pFilePathW; + resourceManagerDataSourceConfig.flags = flags; + resourceManagerDataSourceConfig.pNotifications = ¬ifications; + resourceManagerDataSourceConfig.initialSeekPointInPCMFrames = pConfig->initialSeekPointInPCMFrames; + resourceManagerDataSourceConfig.rangeBegInPCMFrames = pConfig->rangeBegInPCMFrames; + resourceManagerDataSourceConfig.rangeEndInPCMFrames = pConfig->rangeEndInPCMFrames; + resourceManagerDataSourceConfig.loopPointBegInPCMFrames = pConfig->loopPointBegInPCMFrames; + resourceManagerDataSourceConfig.loopPointEndInPCMFrames = pConfig->loopPointEndInPCMFrames; + resourceManagerDataSourceConfig.isLooping = pConfig->isLooping; + + result = ma_resource_manager_data_source_init_ex(pEngine->pResourceManager, &resourceManagerDataSourceConfig, pSound->pResourceManagerDataSource); + if (result != MA_SUCCESS) { + goto done; + } + + pSound->ownsDataSource = MA_TRUE; /* <-- Important. Not setting this will result in the resource manager data source never getting uninitialized. */ + + /* We need to use a slightly customized version of the config so we'll need to make a copy. */ + config = *pConfig; + config.pFilePath = NULL; + config.pFilePathW = NULL; + config.pDataSource = pSound->pResourceManagerDataSource; + + result = ma_sound_init_from_data_source_internal(pEngine, &config, pSound); + if (result != MA_SUCCESS) { + ma_resource_manager_data_source_uninit(pSound->pResourceManagerDataSource); + ma_free(pSound->pResourceManagerDataSource, &pEngine->allocationCallbacks); + MA_ZERO_OBJECT(pSound); + goto done; + } + } +done: + if (notifications.done.pFence) { ma_fence_release(notifications.done.pFence); } + return result; +} + +MA_API ma_result ma_sound_init_from_file(ma_engine* pEngine, const char* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, ma_fence* pDoneFence, ma_sound* pSound) +{ + ma_sound_config config = ma_sound_config_init(); + config.pFilePath = pFilePath; + config.flags = flags; + config.pInitialAttachment = pGroup; + config.pDoneFence = pDoneFence; + return ma_sound_init_ex(pEngine, &config, pSound); +} + +MA_API ma_result ma_sound_init_from_file_w(ma_engine* pEngine, const wchar_t* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, ma_fence* pDoneFence, ma_sound* pSound) +{ + ma_sound_config config = ma_sound_config_init(); + config.pFilePathW = pFilePath; + config.flags = flags; + config.pInitialAttachment = pGroup; + config.pDoneFence = pDoneFence; + return ma_sound_init_ex(pEngine, &config, pSound); +} + +MA_API ma_result ma_sound_init_copy(ma_engine* pEngine, const ma_sound* pExistingSound, ma_uint32 flags, ma_sound_group* pGroup, ma_sound* pSound) +{ + ma_result result; + ma_sound_config config; + + result = ma_sound_preinit(pEngine, pSound); + if (result != MA_SUCCESS) { + return result; + } + + if (pExistingSound == NULL) { + return MA_INVALID_ARGS; + } + + /* Cloning only works for data buffers (not streams) that are loaded from the resource manager. */ + if (pExistingSound->pResourceManagerDataSource == NULL) { + return MA_INVALID_OPERATION; + } + + /* + We need to make a clone of the data source. If the data source is not a data buffer (i.e. a stream) + the this will fail. + */ + pSound->pResourceManagerDataSource = (ma_resource_manager_data_source*)ma_malloc(sizeof(*pSound->pResourceManagerDataSource), &pEngine->allocationCallbacks); + if (pSound->pResourceManagerDataSource == NULL) { + return MA_OUT_OF_MEMORY; + } + + result = ma_resource_manager_data_source_init_copy(pEngine->pResourceManager, pExistingSound->pResourceManagerDataSource, pSound->pResourceManagerDataSource); + if (result != MA_SUCCESS) { + ma_free(pSound->pResourceManagerDataSource, &pEngine->allocationCallbacks); + return result; + } + + config = ma_sound_config_init(); + config.pDataSource = pSound->pResourceManagerDataSource; + config.flags = flags; + config.pInitialAttachment = pGroup; + + result = ma_sound_init_from_data_source_internal(pEngine, &config, pSound); + if (result != MA_SUCCESS) { + ma_resource_manager_data_source_uninit(pSound->pResourceManagerDataSource); + ma_free(pSound->pResourceManagerDataSource, &pEngine->allocationCallbacks); + MA_ZERO_OBJECT(pSound); + return result; + } + + return MA_SUCCESS; +} +#endif + +MA_API ma_result ma_sound_init_from_data_source(ma_engine* pEngine, ma_data_source* pDataSource, ma_uint32 flags, ma_sound_group* pGroup, ma_sound* pSound) +{ + ma_sound_config config = ma_sound_config_init(); + config.pDataSource = pDataSource; + config.flags = flags; + config.pInitialAttachment = pGroup; + return ma_sound_init_ex(pEngine, &config, pSound); +} + +MA_API ma_result ma_sound_init_ex(ma_engine* pEngine, const ma_sound_config* pConfig, ma_sound* pSound) +{ + ma_result result; + + result = ma_sound_preinit(pEngine, pSound); + if (result != MA_SUCCESS) { + return result; + } + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + /* We need to load the sound differently depending on whether or not we're loading from a file. */ +#ifndef MA_NO_RESOURCE_MANAGER + if (pConfig->pFilePath != NULL || pConfig->pFilePathW != NULL) { + return ma_sound_init_from_file_internal(pEngine, pConfig, pSound); + } else +#endif + { + /* + Getting here means we're not loading from a file. We may be loading from an already-initialized + data source, or none at all. If we aren't specifying any data source, we'll be initializing the + the equivalent to a group. ma_data_source_init_from_data_source_internal() will deal with this + for us, so no special treatment required here. + */ + return ma_sound_init_from_data_source_internal(pEngine, pConfig, pSound); + } +} + +MA_API void ma_sound_uninit(ma_sound* pSound) +{ + if (pSound == NULL) { + return; + } + + /* + Always uninitialize the node first. This ensures it's detached from the graph and does not return until it has done + so which makes thread safety beyond this point trivial. + */ + ma_engine_node_uninit(&pSound->engineNode, &pSound->engineNode.pEngine->allocationCallbacks); + + /* Once the sound is detached from the group we can guarantee that it won't be referenced by the mixer thread which means it's safe for us to destroy the data source. */ +#ifndef MA_NO_RESOURCE_MANAGER + if (pSound->ownsDataSource) { + ma_resource_manager_data_source_uninit(pSound->pResourceManagerDataSource); + ma_free(pSound->pResourceManagerDataSource, &pSound->engineNode.pEngine->allocationCallbacks); + pSound->pDataSource = NULL; + } +#else + MA_ASSERT(pSound->ownsDataSource == MA_FALSE); +#endif +} + +MA_API ma_engine* ma_sound_get_engine(const ma_sound* pSound) +{ + if (pSound == NULL) { + return NULL; + } + + return pSound->engineNode.pEngine; +} + +MA_API ma_data_source* ma_sound_get_data_source(const ma_sound* pSound) +{ + if (pSound == NULL) { + return NULL; + } + + return pSound->pDataSource; +} + +MA_API ma_result ma_sound_start(ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + /* If the sound is already playing, do nothing. */ + if (ma_sound_is_playing(pSound)) { + return MA_SUCCESS; + } + + /* If the sound is at the end it means we want to start from the start again. */ + if (ma_sound_at_end(pSound)) { + ma_result result = ma_data_source_seek_to_pcm_frame(pSound->pDataSource, 0); + if (result != MA_SUCCESS && result != MA_NOT_IMPLEMENTED) { + return result; /* Failed to seek back to the start. */ + } + + /* Make sure we clear the end indicator. */ + c89atomic_exchange_32(&pSound->atEnd, MA_FALSE); + } + + /* Make sure the sound is started. If there's a start delay, the sound won't actually start until the start time is reached. */ + ma_node_set_state(pSound, ma_node_state_started); + + return MA_SUCCESS; +} + +MA_API ma_result ma_sound_stop(ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + /* This will stop the sound immediately. Use ma_sound_set_stop_time() to stop the sound at a specific time. */ + ma_node_set_state(pSound, ma_node_state_stopped); + + return MA_SUCCESS; +} + +MA_API void ma_sound_set_volume(ma_sound* pSound, float volume) +{ + if (pSound == NULL) { + return; + } + + /* The volume is controlled via the output bus. */ + ma_node_set_output_bus_volume(pSound, 0, volume); +} + +MA_API float ma_sound_get_volume(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_node_get_output_bus_volume(pSound, 0); +} + +MA_API void ma_sound_set_pan(ma_sound* pSound, float pan) +{ + if (pSound == NULL) { + return; + } + + ma_panner_set_pan(&pSound->engineNode.panner, pan); +} + +MA_API float ma_sound_get_pan(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_panner_get_pan(&pSound->engineNode.panner); +} + +MA_API void ma_sound_set_pan_mode(ma_sound* pSound, ma_pan_mode panMode) +{ + if (pSound == NULL) { + return; + } + + ma_panner_set_mode(&pSound->engineNode.panner, panMode); +} + +MA_API ma_pan_mode ma_sound_get_pan_mode(const ma_sound* pSound) +{ + if (pSound == NULL) { + return ma_pan_mode_balance; + } + + return ma_panner_get_mode(&pSound->engineNode.panner); +} + +MA_API void ma_sound_set_pitch(ma_sound* pSound, float pitch) +{ + if (pSound == NULL) { + return; + } + + c89atomic_exchange_explicit_f32(&pSound->engineNode.pitch, pitch, c89atomic_memory_order_release); +} + +MA_API float ma_sound_get_pitch(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return c89atomic_load_f32(&pSound->engineNode.pitch); /* Naughty const-cast for this. */ +} + +MA_API void ma_sound_set_spatialization_enabled(ma_sound* pSound, ma_bool32 enabled) +{ + if (pSound == NULL) { + return; + } + + c89atomic_exchange_explicit_32(&pSound->engineNode.isSpatializationDisabled, !enabled, c89atomic_memory_order_release); +} + +MA_API ma_bool32 ma_sound_is_spatialization_enabled(const ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_FALSE; + } + + return ma_engine_node_is_spatialization_enabled(&pSound->engineNode); +} + +MA_API void ma_sound_set_pinned_listener_index(ma_sound* pSound, ma_uint32 listenerIndex) +{ + if (pSound == NULL || listenerIndex >= ma_engine_get_listener_count(ma_sound_get_engine(pSound))) { + return; + } + + c89atomic_exchange_explicit_32(&pSound->engineNode.pinnedListenerIndex, listenerIndex, c89atomic_memory_order_release); +} + +MA_API ma_uint32 ma_sound_get_pinned_listener_index(const ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_LISTENER_INDEX_CLOSEST; + } + + return c89atomic_load_explicit_32(&pSound->engineNode.pinnedListenerIndex, c89atomic_memory_order_acquire); +} + +MA_API void ma_sound_set_position(ma_sound* pSound, float x, float y, float z) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_position(&pSound->engineNode.spatializer, x, y, z); +} + +MA_API ma_vec3f ma_sound_get_position(const ma_sound* pSound) +{ + if (pSound == NULL) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return ma_spatializer_get_position(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_direction(ma_sound* pSound, float x, float y, float z) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_direction(&pSound->engineNode.spatializer, x, y, z); +} + +MA_API ma_vec3f ma_sound_get_direction(const ma_sound* pSound) +{ + if (pSound == NULL) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return ma_spatializer_get_direction(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_velocity(ma_sound* pSound, float x, float y, float z) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_velocity(&pSound->engineNode.spatializer, x, y, z); +} + +MA_API ma_vec3f ma_sound_get_velocity(const ma_sound* pSound) +{ + if (pSound == NULL) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return ma_spatializer_get_velocity(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_attenuation_model(ma_sound* pSound, ma_attenuation_model attenuationModel) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_attenuation_model(&pSound->engineNode.spatializer, attenuationModel); +} + +MA_API ma_attenuation_model ma_sound_get_attenuation_model(const ma_sound* pSound) +{ + if (pSound == NULL) { + return ma_attenuation_model_none; + } + + return ma_spatializer_get_attenuation_model(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_positioning(ma_sound* pSound, ma_positioning positioning) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_positioning(&pSound->engineNode.spatializer, positioning); +} + +MA_API ma_positioning ma_sound_get_positioning(const ma_sound* pSound) +{ + if (pSound == NULL) { + return ma_positioning_absolute; + } + + return ma_spatializer_get_positioning(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_rolloff(ma_sound* pSound, float rolloff) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_rolloff(&pSound->engineNode.spatializer, rolloff); +} + +MA_API float ma_sound_get_rolloff(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_spatializer_get_rolloff(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_min_gain(ma_sound* pSound, float minGain) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_min_gain(&pSound->engineNode.spatializer, minGain); +} + +MA_API float ma_sound_get_min_gain(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_spatializer_get_min_gain(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_max_gain(ma_sound* pSound, float maxGain) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_max_gain(&pSound->engineNode.spatializer, maxGain); +} + +MA_API float ma_sound_get_max_gain(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_spatializer_get_max_gain(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_min_distance(ma_sound* pSound, float minDistance) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_min_distance(&pSound->engineNode.spatializer, minDistance); +} + +MA_API float ma_sound_get_min_distance(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_spatializer_get_min_distance(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_max_distance(ma_sound* pSound, float maxDistance) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_max_distance(&pSound->engineNode.spatializer, maxDistance); +} + +MA_API float ma_sound_get_max_distance(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_spatializer_get_max_distance(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_cone(ma_sound* pSound, float innerAngleInRadians, float outerAngleInRadians, float outerGain) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_cone(&pSound->engineNode.spatializer, innerAngleInRadians, outerAngleInRadians, outerGain); +} + +MA_API void ma_sound_get_cone(const ma_sound* pSound, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain) +{ + if (pInnerAngleInRadians != NULL) { + *pInnerAngleInRadians = 0; + } + + if (pOuterAngleInRadians != NULL) { + *pOuterAngleInRadians = 0; + } + + if (pOuterGain != NULL) { + *pOuterGain = 0; + } + + ma_spatializer_get_cone(&pSound->engineNode.spatializer, pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain); +} + +MA_API void ma_sound_set_doppler_factor(ma_sound* pSound, float dopplerFactor) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_doppler_factor(&pSound->engineNode.spatializer, dopplerFactor); +} + +MA_API float ma_sound_get_doppler_factor(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_spatializer_get_doppler_factor(&pSound->engineNode.spatializer); +} + + +MA_API void ma_sound_set_fade_in_pcm_frames(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames) +{ + if (pSound == NULL) { + return; + } + + ma_fader_set_fade(&pSound->engineNode.fader, volumeBeg, volumeEnd, fadeLengthInFrames); +} + +MA_API void ma_sound_set_fade_in_milliseconds(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds) +{ + if (pSound == NULL) { + return; + } + + ma_sound_set_fade_in_pcm_frames(pSound, volumeBeg, volumeEnd, (fadeLengthInMilliseconds * pSound->engineNode.fader.config.sampleRate) / 1000); +} + +MA_API float ma_sound_get_current_fade_volume(ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + return ma_fader_get_current_volume(&pSound->engineNode.fader); +} + +MA_API void ma_sound_set_start_time_in_pcm_frames(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInFrames) +{ + if (pSound == NULL) { + return; + } + + ma_node_set_state_time(pSound, ma_node_state_started, absoluteGlobalTimeInFrames); +} + +MA_API void ma_sound_set_start_time_in_milliseconds(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInMilliseconds) +{ + if (pSound == NULL) { + return; + } + + ma_sound_set_start_time_in_pcm_frames(pSound, absoluteGlobalTimeInMilliseconds * ma_engine_get_sample_rate(ma_sound_get_engine(pSound)) / 1000); +} + +MA_API void ma_sound_set_stop_time_in_pcm_frames(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInFrames) +{ + if (pSound == NULL) { + return; + } + + ma_node_set_state_time(pSound, ma_node_state_stopped, absoluteGlobalTimeInFrames); +} + +MA_API void ma_sound_set_stop_time_in_milliseconds(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInMilliseconds) +{ + if (pSound == NULL) { + return; + } + + ma_sound_set_stop_time_in_pcm_frames(pSound, absoluteGlobalTimeInMilliseconds * ma_engine_get_sample_rate(ma_sound_get_engine(pSound)) / 1000); +} + +MA_API ma_bool32 ma_sound_is_playing(const ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_FALSE; + } + + return ma_node_get_state_by_time(pSound, ma_engine_get_time(ma_sound_get_engine(pSound))) == ma_node_state_started; +} + +MA_API ma_uint64 ma_sound_get_time_in_pcm_frames(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_node_get_time(pSound); +} + +MA_API void ma_sound_set_looping(ma_sound* pSound, ma_bool32 isLooping) +{ + if (pSound == NULL) { + return; + } + + /* Looping is only a valid concept if the sound is backed by a data source. */ + if (pSound->pDataSource == NULL) { + return; + } + + /* The looping state needs to be applied to the data source in order for any looping to actually happen. */ + ma_data_source_set_looping(pSound->pDataSource, isLooping); +} + +MA_API ma_bool32 ma_sound_is_looping(const ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_FALSE; + } + + /* There is no notion of looping for sounds that are not backed by a data source. */ + if (pSound->pDataSource == NULL) { + return MA_FALSE; + } + + return ma_data_source_is_looping(pSound->pDataSource); +} + +MA_API ma_bool32 ma_sound_at_end(const ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_FALSE; + } + + /* There is no notion of an end of a sound if it's not backed by a data source. */ + if (pSound->pDataSource == NULL) { + return MA_FALSE; + } + + return c89atomic_load_32(&pSound->atEnd); +} + +MA_API ma_result ma_sound_seek_to_pcm_frame(ma_sound* pSound, ma_uint64 frameIndex) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + /* Seeking is only valid for sounds that are backed by a data source. */ + if (pSound->pDataSource == NULL) { + return MA_INVALID_OPERATION; + } + + /* + Resource manager data sources are thread safe which means we can just seek immediately. However, we cannot guarantee that other data sources are + thread safe as well so in that case we'll need to get the mixing thread to seek for us to ensure we don't try seeking at the same time as reading. + */ +#ifndef MA_NO_RESOURCE_MANAGER + if (pSound->pDataSource == pSound->pResourceManagerDataSource) { + ma_result result = ma_resource_manager_data_source_seek_to_pcm_frame(pSound->pResourceManagerDataSource, frameIndex); + if (result != MA_SUCCESS) { + return result; + } + + /* Time dependant effects need to have their timers updated. */ + return ma_node_set_time(&pSound->engineNode, frameIndex); + } +#endif + + /* Getting here means the data source is not a resource manager data source so we'll need to get the mixing thread to do the seeking for us. */ + pSound->seekTarget = frameIndex; + + return MA_SUCCESS; +} + +MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + /* The data format is retrieved directly from the data source if the sound is backed by one. Otherwise we pull it from the node. */ + if (pSound->pDataSource == NULL) { + ma_uint32 channels; + + if (pFormat != NULL) { + *pFormat = ma_format_f32; + } + + channels = ma_node_get_input_channels(&pSound->engineNode, 0); + if (pChannels != NULL) { + *pChannels = channels; + } + + if (pSampleRate != NULL) { + *pSampleRate = pSound->engineNode.resampler.config.sampleRateIn; + } + + if (pChannelMap != NULL) { + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, channels); + } + + return MA_SUCCESS; + } else { + return ma_data_source_get_data_format(pSound->pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); + } +} + +MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64* pCursor) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + /* The notion of a cursor is only valid for sounds that are backed by a data source. */ + if (pSound->pDataSource == NULL) { + return MA_INVALID_OPERATION; + } + + return ma_data_source_get_cursor_in_pcm_frames(pSound->pDataSource, pCursor); +} + +MA_API ma_result ma_sound_get_length_in_pcm_frames(ma_sound* pSound, ma_uint64* pLength) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + /* The notion of a sound length is only valid for sounds that are backed by a data source. */ + if (pSound->pDataSource == NULL) { + return MA_INVALID_OPERATION; + } + + return ma_data_source_get_length_in_pcm_frames(pSound->pDataSource, pLength); +} + + +MA_API ma_result ma_sound_group_init(ma_engine* pEngine, ma_uint32 flags, ma_sound_group* pParentGroup, ma_sound_group* pGroup) +{ + ma_sound_group_config config = ma_sound_group_config_init(); + config.flags = flags; + config.pInitialAttachment = pParentGroup; + return ma_sound_group_init_ex(pEngine, &config, pGroup); +} + +MA_API ma_result ma_sound_group_init_ex(ma_engine* pEngine, const ma_sound_group_config* pConfig, ma_sound_group* pGroup) +{ + ma_sound_config soundConfig; + + if (pGroup == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pGroup); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + /* A sound group is just a sound without a data source. */ + soundConfig = *pConfig; + soundConfig.pFilePath = NULL; + soundConfig.pFilePathW = NULL; + soundConfig.pDataSource = NULL; + + /* + Groups need to have spatialization disabled by default because I think it'll be pretty rare + that programs will want to spatialize groups (but not unheard of). Certainly it feels like + disabling this by default feels like the right option. Spatialization can be enabled with a + call to ma_sound_group_set_spatialization_enabled(). + */ + soundConfig.flags |= MA_SOUND_FLAG_NO_SPATIALIZATION; + + return ma_sound_init_ex(pEngine, &soundConfig, pGroup); +} + +MA_API void ma_sound_group_uninit(ma_sound_group* pGroup) +{ + ma_sound_uninit(pGroup); +} + +MA_API ma_engine* ma_sound_group_get_engine(const ma_sound_group* pGroup) +{ + return ma_sound_get_engine(pGroup); +} + +MA_API ma_result ma_sound_group_start(ma_sound_group* pGroup) +{ + return ma_sound_start(pGroup); +} + +MA_API ma_result ma_sound_group_stop(ma_sound_group* pGroup) +{ + return ma_sound_stop(pGroup); +} + +MA_API void ma_sound_group_set_volume(ma_sound_group* pGroup, float volume) +{ + ma_sound_set_volume(pGroup, volume); +} + +MA_API float ma_sound_group_get_volume(const ma_sound_group* pGroup) +{ + return ma_sound_get_volume(pGroup); +} + +MA_API void ma_sound_group_set_pan(ma_sound_group* pGroup, float pan) +{ + ma_sound_set_pan(pGroup, pan); +} + +MA_API float ma_sound_group_get_pan(const ma_sound_group* pGroup) +{ + return ma_sound_get_pan(pGroup); +} + +MA_API void ma_sound_group_set_pan_mode(ma_sound_group* pGroup, ma_pan_mode panMode) +{ + ma_sound_set_pan_mode(pGroup, panMode); +} + +MA_API ma_pan_mode ma_sound_group_get_pan_mode(const ma_sound_group* pGroup) +{ + return ma_sound_get_pan_mode(pGroup); +} + +MA_API void ma_sound_group_set_pitch(ma_sound_group* pGroup, float pitch) +{ + ma_sound_set_pitch(pGroup, pitch); +} + +MA_API float ma_sound_group_get_pitch(const ma_sound_group* pGroup) +{ + return ma_sound_get_pitch(pGroup); +} + +MA_API void ma_sound_group_set_spatialization_enabled(ma_sound_group* pGroup, ma_bool32 enabled) +{ + ma_sound_set_spatialization_enabled(pGroup, enabled); +} + +MA_API ma_bool32 ma_sound_group_is_spatialization_enabled(const ma_sound_group* pGroup) +{ + return ma_sound_is_spatialization_enabled(pGroup); +} + +MA_API void ma_sound_group_set_pinned_listener_index(ma_sound_group* pGroup, ma_uint32 listenerIndex) +{ + ma_sound_set_pinned_listener_index(pGroup, listenerIndex); +} + +MA_API ma_uint32 ma_sound_group_get_pinned_listener_index(const ma_sound_group* pGroup) +{ + return ma_sound_get_pinned_listener_index(pGroup); +} + +MA_API void ma_sound_group_set_position(ma_sound_group* pGroup, float x, float y, float z) +{ + ma_sound_set_position(pGroup, x, y, z); +} + +MA_API ma_vec3f ma_sound_group_get_position(const ma_sound_group* pGroup) +{ + return ma_sound_get_position(pGroup); +} + +MA_API void ma_sound_group_set_direction(ma_sound_group* pGroup, float x, float y, float z) +{ + ma_sound_set_direction(pGroup, x, y, z); +} + +MA_API ma_vec3f ma_sound_group_get_direction(const ma_sound_group* pGroup) +{ + return ma_sound_get_direction(pGroup); +} + +MA_API void ma_sound_group_set_velocity(ma_sound_group* pGroup, float x, float y, float z) +{ + ma_sound_set_velocity(pGroup, x, y, z); +} + +MA_API ma_vec3f ma_sound_group_get_velocity(const ma_sound_group* pGroup) +{ + return ma_sound_get_velocity(pGroup); +} + +MA_API void ma_sound_group_set_attenuation_model(ma_sound_group* pGroup, ma_attenuation_model attenuationModel) +{ + ma_sound_set_attenuation_model(pGroup, attenuationModel); +} + +MA_API ma_attenuation_model ma_sound_group_get_attenuation_model(const ma_sound_group* pGroup) +{ + return ma_sound_get_attenuation_model(pGroup); +} + +MA_API void ma_sound_group_set_positioning(ma_sound_group* pGroup, ma_positioning positioning) +{ + ma_sound_set_positioning(pGroup, positioning); +} + +MA_API ma_positioning ma_sound_group_get_positioning(const ma_sound_group* pGroup) +{ + return ma_sound_get_positioning(pGroup); +} + +MA_API void ma_sound_group_set_rolloff(ma_sound_group* pGroup, float rolloff) +{ + ma_sound_set_rolloff(pGroup, rolloff); +} + +MA_API float ma_sound_group_get_rolloff(const ma_sound_group* pGroup) +{ + return ma_sound_get_rolloff(pGroup); +} + +MA_API void ma_sound_group_set_min_gain(ma_sound_group* pGroup, float minGain) +{ + ma_sound_set_min_gain(pGroup, minGain); +} + +MA_API float ma_sound_group_get_min_gain(const ma_sound_group* pGroup) +{ + return ma_sound_get_min_gain(pGroup); +} + +MA_API void ma_sound_group_set_max_gain(ma_sound_group* pGroup, float maxGain) +{ + ma_sound_set_max_gain(pGroup, maxGain); +} + +MA_API float ma_sound_group_get_max_gain(const ma_sound_group* pGroup) +{ + return ma_sound_get_max_gain(pGroup); +} + +MA_API void ma_sound_group_set_min_distance(ma_sound_group* pGroup, float minDistance) +{ + ma_sound_set_min_distance(pGroup, minDistance); +} + +MA_API float ma_sound_group_get_min_distance(const ma_sound_group* pGroup) +{ + return ma_sound_get_min_distance(pGroup); +} + +MA_API void ma_sound_group_set_max_distance(ma_sound_group* pGroup, float maxDistance) +{ + ma_sound_set_max_distance(pGroup, maxDistance); +} + +MA_API float ma_sound_group_get_max_distance(const ma_sound_group* pGroup) +{ + return ma_sound_get_max_distance(pGroup); +} + +MA_API void ma_sound_group_set_cone(ma_sound_group* pGroup, float innerAngleInRadians, float outerAngleInRadians, float outerGain) +{ + ma_sound_set_cone(pGroup, innerAngleInRadians, outerAngleInRadians, outerGain); +} + +MA_API void ma_sound_group_get_cone(const ma_sound_group* pGroup, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain) +{ + ma_sound_get_cone(pGroup, pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain); +} + +MA_API void ma_sound_group_set_doppler_factor(ma_sound_group* pGroup, float dopplerFactor) +{ + ma_sound_set_doppler_factor(pGroup, dopplerFactor); +} + +MA_API float ma_sound_group_get_doppler_factor(const ma_sound_group* pGroup) +{ + return ma_sound_get_doppler_factor(pGroup); +} + +MA_API void ma_sound_group_set_fade_in_pcm_frames(ma_sound_group* pGroup, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames) +{ + ma_sound_set_fade_in_pcm_frames(pGroup, volumeBeg, volumeEnd, fadeLengthInFrames); +} + +MA_API void ma_sound_group_set_fade_in_milliseconds(ma_sound_group* pGroup, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds) +{ + ma_sound_set_fade_in_milliseconds(pGroup, volumeBeg, volumeEnd, fadeLengthInMilliseconds); +} + +MA_API float ma_sound_group_get_current_fade_volume(ma_sound_group* pGroup) +{ + return ma_sound_get_current_fade_volume(pGroup); +} + +MA_API void ma_sound_group_set_start_time_in_pcm_frames(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInFrames) +{ + ma_sound_set_start_time_in_pcm_frames(pGroup, absoluteGlobalTimeInFrames); +} + +MA_API void ma_sound_group_set_start_time_in_milliseconds(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInMilliseconds) +{ + ma_sound_set_start_time_in_milliseconds(pGroup, absoluteGlobalTimeInMilliseconds); +} + +MA_API void ma_sound_group_set_stop_time_in_pcm_frames(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInFrames) +{ + ma_sound_set_stop_time_in_pcm_frames(pGroup, absoluteGlobalTimeInFrames); +} + +MA_API void ma_sound_group_set_stop_time_in_milliseconds(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInMilliseconds) +{ + ma_sound_set_stop_time_in_milliseconds(pGroup, absoluteGlobalTimeInMilliseconds); +} + +MA_API ma_bool32 ma_sound_group_is_playing(const ma_sound_group* pGroup) +{ + return ma_sound_is_playing(pGroup); +} + +MA_API ma_uint64 ma_sound_group_get_time_in_pcm_frames(const ma_sound_group* pGroup) +{ + return ma_sound_get_time_in_pcm_frames(pGroup); +} +#endif /* MA_NO_ENGINE */ + + + /************************************************************************************************************************************************************** *************************************************************************************************************************************************************** diff --git a/extras/miniaudio_split/miniaudio.h b/extras/miniaudio_split/miniaudio.h index b71993b5..8e087203 100644 --- a/extras/miniaudio_split/miniaudio.h +++ b/extras/miniaudio_split/miniaudio.h @@ -1,6 +1,6 @@ /* Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file. -miniaudio - v0.10.43 - 2021-12-10 +miniaudio - v0.11.0 - 2021-12-18 David Reid - mackron@gmail.com @@ -19,8 +19,8 @@ extern "C" { #define MA_XSTRINGIFY(x) MA_STRINGIFY(x) #define MA_VERSION_MAJOR 0 -#define MA_VERSION_MINOR 10 -#define MA_VERSION_REVISION 43 +#define MA_VERSION_MINOR 11 +#define MA_VERSION_REVISION 0 #define MA_VERSION_STRING MA_XSTRINGIFY(MA_VERSION_MAJOR) "." MA_XSTRINGIFY(MA_VERSION_MINOR) "." MA_XSTRINGIFY(MA_VERSION_REVISION) #if defined(_MSC_VER) && !defined(__clang__) @@ -35,7 +35,7 @@ extern "C" { #pragma GCC diagnostic ignored "-Wc11-extensions" /* anonymous unions are a C11 extension */ #endif #endif - + /* Platform/backend detection. */ #ifdef _WIN32 #define MA_WIN32 @@ -68,6 +68,12 @@ extern "C" { #endif #endif +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) + #define MA_SIZEOF_PTR 8 +#else + #define MA_SIZEOF_PTR 4 +#endif + #include /* For size_t. */ /* Sized types. */ @@ -77,7 +83,7 @@ typedef signed short ma_int16; typedef unsigned short ma_uint16; typedef signed int ma_int32; typedef unsigned int ma_uint32; -#if defined(_MSC_VER) +#if defined(_MSC_VER) && !defined(__clang__) typedef signed __int64 ma_int64; typedef unsigned __int64 ma_uint64; #else @@ -94,7 +100,7 @@ typedef unsigned int ma_uint32; #pragma GCC diagnostic pop #endif #endif -#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) +#if MA_SIZEOF_PTR == 8 typedef ma_uint64 ma_uintptr; #else typedef ma_uint32 ma_uintptr; @@ -176,8 +182,8 @@ typedef ma_uint16 wchar_t; #endif #endif -/* SIMD alignment in bytes. Currently set to 64 bytes in preparation for future AVX-512 optimizations. */ -#define MA_SIMD_ALIGNMENT 64 +/* SIMD alignment in bytes. Currently set to 32 bytes in preparation for future AVX optimizations. */ +#define MA_SIMD_ALIGNMENT 32 /* @@ -210,19 +216,32 @@ MA_LOG_LEVEL_ERROR #define MA_LOG_LEVEL_WARNING 2 #define MA_LOG_LEVEL_ERROR 1 -/* Deprecated. */ -#define MA_LOG_LEVEL_VERBOSE MA_LOG_LEVEL_DEBUG - -/* Deprecated. */ -#ifndef MA_LOG_LEVEL -#define MA_LOG_LEVEL MA_LOG_LEVEL_ERROR -#endif - /* -An annotation for variables which must be used atomically. This doesn't actually do anything - it's -just used as a way for humans to identify variables that should be used atomically. +Variables needing to be accessed atomically should be declared with this macro for two reasons: + + 1) It allows people who read the code to identify a variable as such; and + 2) It forces alignment on platforms where it's required or optimal. + +Note that for x86/64, alignment is not strictly necessary, but does have some performance +implications. Where supported by the compiler, alignment will be used, but otherwise if the CPU +architecture does not require it, it will simply leave it unaligned. This is the case with old +versions of Visual Studio, which I've confirmed with at least VC6. */ -#define MA_ATOMIC +#if defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) + #include + #define MA_ATOMIC(alignment, type) alignas(alignment) type +#else + #if defined(__GNUC__) + /* GCC-style compilers. */ + #define MA_ATOMIC(alignment, type) type __attribute__((aligned(alignment))) + #elif defined(_MSC_VER) && _MSC_VER > 1200 /* 1200 = VC6. Alignment not supported, but not necessary because x86 is the only supported target. */ + /* MSVC. */ + #define MA_ATOMIC(alignment, type) __declspec(align(alignment)) type + #else + /* Other compilers. */ + #define MA_ATOMIC(alignment, type) type + #endif +#endif typedef struct ma_context ma_context; typedef struct ma_device ma_device; @@ -365,10 +384,9 @@ typedef int ma_result; #define MA_MIN_CHANNELS 1 #ifndef MA_MAX_CHANNELS -#define MA_MAX_CHANNELS 32 +#define MA_MAX_CHANNELS 254 #endif - #ifndef MA_MAX_FILTER_ORDER #define MA_MAX_FILTER_ORDER 8 #endif @@ -433,17 +451,12 @@ typedef enum ma_standard_sample_rate_count = 14 /* Need to maintain the count manually. Make sure this is updated if items are added to enum. */ } ma_standard_sample_rate; -/* These are deprecated. Use ma_standard_sample_rate_min and ma_standard_sample_rate_max. */ -#define MA_MIN_SAMPLE_RATE (ma_uint32)ma_standard_sample_rate_min -#define MA_MAX_SAMPLE_RATE (ma_uint32)ma_standard_sample_rate_max - typedef enum { ma_channel_mix_mode_rectangular = 0, /* Simple averaging based on the plane(s) the channel is sitting on. */ ma_channel_mix_mode_simple, /* Drop excess channels; zeroed out extra channels. */ ma_channel_mix_mode_custom_weights, /* Use custom weights specified in ma_channel_router_config. */ - ma_channel_mix_mode_planar_blend = ma_channel_mix_mode_rectangular, ma_channel_mix_mode_default = ma_channel_mix_mode_rectangular } ma_channel_mix_mode; @@ -481,6 +494,9 @@ typedef struct } ma_lcg; +/* Spinlocks are 32-bit for compatibility reasons. */ +typedef ma_uint32 ma_spinlock; + #ifndef MA_NO_THREADING /* Thread priorities should be ordered such that the default priority of the worker thread is 0. */ typedef enum @@ -495,9 +511,6 @@ typedef enum ma_thread_priority_default = 0 } ma_thread_priority; -/* Spinlocks are 32-bit for compatibility reasons. */ -typedef ma_uint32 ma_spinlock; - #if defined(MA_WIN32) typedef ma_handle ma_thread; #endif @@ -574,6 +587,36 @@ Logging #define MA_MAX_LOG_CALLBACKS 4 #endif + +/* +The callback for handling log messages. + + +Parameters +---------- +pUserData (in) + The user data pointer that was passed into ma_log_register_callback(). + +logLevel (in) + The log level. This can be one of the following: + + +----------------------+ + | Log Level | + +----------------------+ + | MA_LOG_LEVEL_DEBUG | + | MA_LOG_LEVEL_INFO | + | MA_LOG_LEVEL_WARNING | + | MA_LOG_LEVEL_ERROR | + +----------------------+ + +pMessage (in) + The log message. + + +Remarks +------- +Do not modify the state of the device from inside the callback. +*/ typedef void (* ma_log_callback_proc)(void* pUserData, ma_uint32 level, const char* pMessage); typedef struct @@ -638,11 +681,18 @@ typedef struct ma_biquad_coefficient b2; ma_biquad_coefficient a1; ma_biquad_coefficient a2; - ma_biquad_coefficient r1[MA_MAX_CHANNELS]; - ma_biquad_coefficient r2[MA_MAX_CHANNELS]; + ma_biquad_coefficient* pR1; + ma_biquad_coefficient* pR2; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_biquad; -MA_API ma_result ma_biquad_init(const ma_biquad_config* pConfig, ma_biquad* pBQ); +MA_API ma_result ma_biquad_get_heap_size(const ma_biquad_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_biquad_init_preallocated(const ma_biquad_config* pConfig, void* pHeap, ma_biquad* pBQ); +MA_API ma_result ma_biquad_init(const ma_biquad_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_biquad* pBQ); +MA_API void ma_biquad_uninit(ma_biquad* pBQ, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_biquad_reinit(const ma_biquad_config* pConfig, ma_biquad* pBQ); MA_API ma_result ma_biquad_process_pcm_frames(ma_biquad* pBQ, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_biquad_get_latency(const ma_biquad* pBQ); @@ -670,10 +720,17 @@ typedef struct ma_format format; ma_uint32 channels; ma_biquad_coefficient a; - ma_biquad_coefficient r1[MA_MAX_CHANNELS]; + ma_biquad_coefficient* pR1; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_lpf1; -MA_API ma_result ma_lpf1_init(const ma_lpf1_config* pConfig, ma_lpf1* pLPF); +MA_API ma_result ma_lpf1_get_heap_size(const ma_lpf1_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_lpf1_init_preallocated(const ma_lpf1_config* pConfig, void* pHeap, ma_lpf1* pLPF); +MA_API ma_result ma_lpf1_init(const ma_lpf1_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf1* pLPF); +MA_API void ma_lpf1_uninit(ma_lpf1* pLPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_lpf1_reinit(const ma_lpf1_config* pConfig, ma_lpf1* pLPF); MA_API ma_result ma_lpf1_process_pcm_frames(ma_lpf1* pLPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_lpf1_get_latency(const ma_lpf1* pLPF); @@ -683,7 +740,10 @@ typedef struct ma_biquad bq; /* The second order low-pass filter is implemented as a biquad filter. */ } ma_lpf2; -MA_API ma_result ma_lpf2_init(const ma_lpf2_config* pConfig, ma_lpf2* pLPF); +MA_API ma_result ma_lpf2_get_heap_size(const ma_lpf2_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_lpf2_init_preallocated(const ma_lpf2_config* pConfig, void* pHeap, ma_lpf2* pHPF); +MA_API ma_result ma_lpf2_init(const ma_lpf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf2* pLPF); +MA_API void ma_lpf2_uninit(ma_lpf2* pLPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_lpf2_reinit(const ma_lpf2_config* pConfig, ma_lpf2* pLPF); MA_API ma_result ma_lpf2_process_pcm_frames(ma_lpf2* pLPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_lpf2_get_latency(const ma_lpf2* pLPF); @@ -707,11 +767,18 @@ typedef struct ma_uint32 sampleRate; ma_uint32 lpf1Count; ma_uint32 lpf2Count; - ma_lpf1 lpf1[1]; - ma_lpf2 lpf2[MA_MAX_FILTER_ORDER/2]; + ma_lpf1* pLPF1; + ma_lpf2* pLPF2; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_lpf; -MA_API ma_result ma_lpf_init(const ma_lpf_config* pConfig, ma_lpf* pLPF); +MA_API ma_result ma_lpf_get_heap_size(const ma_lpf_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_lpf_init_preallocated(const ma_lpf_config* pConfig, void* pHeap, ma_lpf* pLPF); +MA_API ma_result ma_lpf_init(const ma_lpf_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf* pLPF); +MA_API void ma_lpf_uninit(ma_lpf* pLPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_lpf_reinit(const ma_lpf_config* pConfig, ma_lpf* pLPF); MA_API ma_result ma_lpf_process_pcm_frames(ma_lpf* pLPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_lpf_get_latency(const ma_lpf* pLPF); @@ -739,10 +806,17 @@ typedef struct ma_format format; ma_uint32 channels; ma_biquad_coefficient a; - ma_biquad_coefficient r1[MA_MAX_CHANNELS]; + ma_biquad_coefficient* pR1; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_hpf1; -MA_API ma_result ma_hpf1_init(const ma_hpf1_config* pConfig, ma_hpf1* pHPF); +MA_API ma_result ma_hpf1_get_heap_size(const ma_hpf1_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_hpf1_init_preallocated(const ma_hpf1_config* pConfig, void* pHeap, ma_hpf1* pLPF); +MA_API ma_result ma_hpf1_init(const ma_hpf1_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf1* pHPF); +MA_API void ma_hpf1_uninit(ma_hpf1* pHPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_hpf1_reinit(const ma_hpf1_config* pConfig, ma_hpf1* pHPF); MA_API ma_result ma_hpf1_process_pcm_frames(ma_hpf1* pHPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_hpf1_get_latency(const ma_hpf1* pHPF); @@ -752,7 +826,10 @@ typedef struct ma_biquad bq; /* The second order high-pass filter is implemented as a biquad filter. */ } ma_hpf2; -MA_API ma_result ma_hpf2_init(const ma_hpf2_config* pConfig, ma_hpf2* pHPF); +MA_API ma_result ma_hpf2_get_heap_size(const ma_hpf2_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_hpf2_init_preallocated(const ma_hpf2_config* pConfig, void* pHeap, ma_hpf2* pHPF); +MA_API ma_result ma_hpf2_init(const ma_hpf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf2* pHPF); +MA_API void ma_hpf2_uninit(ma_hpf2* pHPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_hpf2_reinit(const ma_hpf2_config* pConfig, ma_hpf2* pHPF); MA_API ma_result ma_hpf2_process_pcm_frames(ma_hpf2* pHPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_hpf2_get_latency(const ma_hpf2* pHPF); @@ -776,11 +853,18 @@ typedef struct ma_uint32 sampleRate; ma_uint32 hpf1Count; ma_uint32 hpf2Count; - ma_hpf1 hpf1[1]; - ma_hpf2 hpf2[MA_MAX_FILTER_ORDER/2]; + ma_hpf1* pHPF1; + ma_hpf2* pHPF2; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_hpf; -MA_API ma_result ma_hpf_init(const ma_hpf_config* pConfig, ma_hpf* pHPF); +MA_API ma_result ma_hpf_get_heap_size(const ma_hpf_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_hpf_init_preallocated(const ma_hpf_config* pConfig, void* pHeap, ma_hpf* pLPF); +MA_API ma_result ma_hpf_init(const ma_hpf_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf* pHPF); +MA_API void ma_hpf_uninit(ma_hpf* pHPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_hpf_reinit(const ma_hpf_config* pConfig, ma_hpf* pHPF); MA_API ma_result ma_hpf_process_pcm_frames(ma_hpf* pHPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_hpf_get_latency(const ma_hpf* pHPF); @@ -807,7 +891,10 @@ typedef struct ma_biquad bq; /* The second order band-pass filter is implemented as a biquad filter. */ } ma_bpf2; -MA_API ma_result ma_bpf2_init(const ma_bpf2_config* pConfig, ma_bpf2* pBPF); +MA_API ma_result ma_bpf2_get_heap_size(const ma_bpf2_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_bpf2_init_preallocated(const ma_bpf2_config* pConfig, void* pHeap, ma_bpf2* pBPF); +MA_API ma_result ma_bpf2_init(const ma_bpf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_bpf2* pBPF); +MA_API void ma_bpf2_uninit(ma_bpf2* pBPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_bpf2_reinit(const ma_bpf2_config* pConfig, ma_bpf2* pBPF); MA_API ma_result ma_bpf2_process_pcm_frames(ma_bpf2* pBPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_bpf2_get_latency(const ma_bpf2* pBPF); @@ -829,10 +916,17 @@ typedef struct ma_format format; ma_uint32 channels; ma_uint32 bpf2Count; - ma_bpf2 bpf2[MA_MAX_FILTER_ORDER/2]; + ma_bpf2* pBPF2; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_bpf; -MA_API ma_result ma_bpf_init(const ma_bpf_config* pConfig, ma_bpf* pBPF); +MA_API ma_result ma_bpf_get_heap_size(const ma_bpf_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_bpf_init_preallocated(const ma_bpf_config* pConfig, void* pHeap, ma_bpf* pBPF); +MA_API ma_result ma_bpf_init(const ma_bpf_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_bpf* pBPF); +MA_API void ma_bpf_uninit(ma_bpf* pBPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_bpf_reinit(const ma_bpf_config* pConfig, ma_bpf* pBPF); MA_API ma_result ma_bpf_process_pcm_frames(ma_bpf* pBPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_bpf_get_latency(const ma_bpf* pBPF); @@ -859,7 +953,10 @@ typedef struct ma_biquad bq; } ma_notch2; -MA_API ma_result ma_notch2_init(const ma_notch2_config* pConfig, ma_notch2* pFilter); +MA_API ma_result ma_notch2_get_heap_size(const ma_notch2_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_notch2_init_preallocated(const ma_notch2_config* pConfig, void* pHeap, ma_notch2* pFilter); +MA_API ma_result ma_notch2_init(const ma_notch2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_notch2* pFilter); +MA_API void ma_notch2_uninit(ma_notch2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_notch2_reinit(const ma_notch2_config* pConfig, ma_notch2* pFilter); MA_API ma_result ma_notch2_process_pcm_frames(ma_notch2* pFilter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_notch2_get_latency(const ma_notch2* pFilter); @@ -887,7 +984,10 @@ typedef struct ma_biquad bq; } ma_peak2; -MA_API ma_result ma_peak2_init(const ma_peak2_config* pConfig, ma_peak2* pFilter); +MA_API ma_result ma_peak2_get_heap_size(const ma_peak2_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_peak2_init_preallocated(const ma_peak2_config* pConfig, void* pHeap, ma_peak2* pFilter); +MA_API ma_result ma_peak2_init(const ma_peak2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_peak2* pFilter); +MA_API void ma_peak2_uninit(ma_peak2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_peak2_reinit(const ma_peak2_config* pConfig, ma_peak2* pFilter); MA_API ma_result ma_peak2_process_pcm_frames(ma_peak2* pFilter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_peak2_get_latency(const ma_peak2* pFilter); @@ -915,7 +1015,10 @@ typedef struct ma_biquad bq; } ma_loshelf2; -MA_API ma_result ma_loshelf2_init(const ma_loshelf2_config* pConfig, ma_loshelf2* pFilter); +MA_API ma_result ma_loshelf2_get_heap_size(const ma_loshelf2_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_loshelf2_init_preallocated(const ma_loshelf2_config* pConfig, void* pHeap, ma_loshelf2* pFilter); +MA_API ma_result ma_loshelf2_init(const ma_loshelf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_loshelf2* pFilter); +MA_API void ma_loshelf2_uninit(ma_loshelf2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_loshelf2_reinit(const ma_loshelf2_config* pConfig, ma_loshelf2* pFilter); MA_API ma_result ma_loshelf2_process_pcm_frames(ma_loshelf2* pFilter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_loshelf2_get_latency(const ma_loshelf2* pFilter); @@ -943,13 +1046,296 @@ typedef struct ma_biquad bq; } ma_hishelf2; -MA_API ma_result ma_hishelf2_init(const ma_hishelf2_config* pConfig, ma_hishelf2* pFilter); +MA_API ma_result ma_hishelf2_get_heap_size(const ma_hishelf2_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_hishelf2_init_preallocated(const ma_hishelf2_config* pConfig, void* pHeap, ma_hishelf2* pFilter); +MA_API ma_result ma_hishelf2_init(const ma_hishelf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hishelf2* pFilter); +MA_API void ma_hishelf2_uninit(ma_hishelf2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_hishelf2_reinit(const ma_hishelf2_config* pConfig, ma_hishelf2* pFilter); MA_API ma_result ma_hishelf2_process_pcm_frames(ma_hishelf2* pFilter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_hishelf2_get_latency(const ma_hishelf2* pFilter); +/* +Delay +*/ +typedef struct +{ + ma_uint32 channels; + ma_uint32 sampleRate; + ma_uint32 delayInFrames; + ma_bool32 delayStart; /* Set to true to delay the start of the output; false otherwise. */ + float wet; /* 0..1. Default = 1. */ + float dry; /* 0..1. Default = 1. */ + float decay; /* 0..1. Default = 0 (no feedback). Feedback decay. Use this for echo. */ +} ma_delay_config; + +MA_API ma_delay_config ma_delay_config_init(ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 delayInFrames, float decay); + + +typedef struct +{ + ma_delay_config config; + ma_uint32 cursor; /* Feedback is written to this cursor. Always equal or in front of the read cursor. */ + ma_uint32 bufferSizeInFrames; /* The maximum of config.startDelayInFrames and config.feedbackDelayInFrames. */ + float* pBuffer; +} ma_delay; + +MA_API ma_result ma_delay_init(const ma_delay_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_delay* pDelay); +MA_API void ma_delay_uninit(ma_delay* pDelay, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_delay_process_pcm_frames(ma_delay* pDelay, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount); +MA_API void ma_delay_set_wet(ma_delay* pDelay, float value); +MA_API float ma_delay_get_wet(const ma_delay* pDelay); +MA_API void ma_delay_set_dry(ma_delay* pDelay, float value); +MA_API float ma_delay_get_dry(const ma_delay* pDelay); +MA_API void ma_delay_set_decay(ma_delay* pDelay, float value); +MA_API float ma_delay_get_decay(const ma_delay* pDelay); + + +/* Gainer for smooth volume changes. */ +typedef struct +{ + ma_uint32 channels; + ma_uint32 smoothTimeInFrames; +} ma_gainer_config; + +MA_API ma_gainer_config ma_gainer_config_init(ma_uint32 channels, ma_uint32 smoothTimeInFrames); + + +typedef struct +{ + ma_gainer_config config; + ma_uint32 t; + float* pOldGains; + float* pNewGains; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; +} ma_gainer; + +MA_API ma_result ma_gainer_get_heap_size(const ma_gainer_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_gainer_init_preallocated(const ma_gainer_config* pConfig, void* pHeap, ma_gainer* pGainer); +MA_API ma_result ma_gainer_init(const ma_gainer_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_gainer* pGainer); +MA_API void ma_gainer_uninit(ma_gainer* pGainer, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_gainer_process_pcm_frames(ma_gainer* pGainer, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); +MA_API ma_result ma_gainer_set_gain(ma_gainer* pGainer, float newGain); +MA_API ma_result ma_gainer_set_gains(ma_gainer* pGainer, float* pNewGains); + + + +/* Stereo panner. */ +typedef enum +{ + ma_pan_mode_balance = 0, /* Does not blend one side with the other. Technically just a balance. Compatible with other popular audio engines and therefore the default. */ + ma_pan_mode_pan /* A true pan. The sound from one side will "move" to the other side and blend with it. */ +} ma_pan_mode; + +typedef struct +{ + ma_format format; + ma_uint32 channels; + ma_pan_mode mode; + float pan; +} ma_panner_config; + +MA_API ma_panner_config ma_panner_config_init(ma_format format, ma_uint32 channels); + + +typedef struct +{ + ma_format format; + ma_uint32 channels; + ma_pan_mode mode; + float pan; /* -1..1 where 0 is no pan, -1 is left side, +1 is right side. Defaults to 0. */ +} ma_panner; + +MA_API ma_result ma_panner_init(const ma_panner_config* pConfig, ma_panner* pPanner); +MA_API ma_result ma_panner_process_pcm_frames(ma_panner* pPanner, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); +MA_API void ma_panner_set_mode(ma_panner* pPanner, ma_pan_mode mode); +MA_API ma_pan_mode ma_panner_get_mode(const ma_panner* pPanner); +MA_API void ma_panner_set_pan(ma_panner* pPanner, float pan); +MA_API float ma_panner_get_pan(const ma_panner* pPanner); + + + +/* Fader. */ +typedef struct +{ + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; +} ma_fader_config; + +MA_API ma_fader_config ma_fader_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate); + +typedef struct +{ + ma_fader_config config; + float volumeBeg; /* If volumeBeg and volumeEnd is equal to 1, no fading happens (ma_fader_process_pcm_frames() will run as a passthrough). */ + float volumeEnd; + ma_uint64 lengthInFrames; /* The total length of the fade. */ + ma_uint64 cursorInFrames; /* The current time in frames. Incremented by ma_fader_process_pcm_frames(). */ +} ma_fader; + +MA_API ma_result ma_fader_init(const ma_fader_config* pConfig, ma_fader* pFader); +MA_API ma_result ma_fader_process_pcm_frames(ma_fader* pFader, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); +MA_API void ma_fader_get_data_format(const ma_fader* pFader, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate); +MA_API void ma_fader_set_fade(ma_fader* pFader, float volumeBeg, float volumeEnd, ma_uint64 lengthInFrames); +MA_API float ma_fader_get_current_volume(ma_fader* pFader); + + + +/* Spatializer. */ +typedef struct +{ + float x; + float y; + float z; +} ma_vec3f; + +typedef enum +{ + ma_attenuation_model_none, /* No distance attenuation and no spatialization. */ + ma_attenuation_model_inverse, /* Equivalent to OpenAL's AL_INVERSE_DISTANCE_CLAMPED. */ + ma_attenuation_model_linear, /* Linear attenuation. Equivalent to OpenAL's AL_LINEAR_DISTANCE_CLAMPED. */ + ma_attenuation_model_exponential /* Exponential attenuation. Equivalent to OpenAL's AL_EXPONENT_DISTANCE_CLAMPED. */ +} ma_attenuation_model; + +typedef enum +{ + ma_positioning_absolute, + ma_positioning_relative +} ma_positioning; + +typedef enum +{ + ma_handedness_right, + ma_handedness_left +} ma_handedness; + + +typedef struct +{ + ma_uint32 channelsOut; + ma_channel* pChannelMapOut; + ma_handedness handedness; /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */ + float coneInnerAngleInRadians; + float coneOuterAngleInRadians; + float coneOuterGain; + float speedOfSound; + ma_vec3f worldUp; +} ma_spatializer_listener_config; + +MA_API ma_spatializer_listener_config ma_spatializer_listener_config_init(ma_uint32 channelsOut); + + +typedef struct +{ + ma_spatializer_listener_config config; + ma_vec3f position; /* The absolute position of the listener. */ + ma_vec3f direction; /* The direction the listener is facing. The world up vector is config.worldUp. */ + ma_vec3f velocity; + ma_bool32 isEnabled; + + /* Memory management. */ + ma_bool32 _ownsHeap; + void* _pHeap; +} ma_spatializer_listener; + +MA_API ma_result ma_spatializer_listener_get_heap_size(const ma_spatializer_listener_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_spatializer_listener_init_preallocated(const ma_spatializer_listener_config* pConfig, void* pHeap, ma_spatializer_listener* pListener); +MA_API ma_result ma_spatializer_listener_init(const ma_spatializer_listener_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_spatializer_listener* pListener); +MA_API void ma_spatializer_listener_uninit(ma_spatializer_listener* pListener, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_channel* ma_spatializer_listener_get_channel_map(ma_spatializer_listener* pListener); +MA_API void ma_spatializer_listener_set_cone(ma_spatializer_listener* pListener, float innerAngleInRadians, float outerAngleInRadians, float outerGain); +MA_API void ma_spatializer_listener_get_cone(const ma_spatializer_listener* pListener, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain); +MA_API void ma_spatializer_listener_set_position(ma_spatializer_listener* pListener, float x, float y, float z); +MA_API ma_vec3f ma_spatializer_listener_get_position(const ma_spatializer_listener* pListener); +MA_API void ma_spatializer_listener_set_direction(ma_spatializer_listener* pListener, float x, float y, float z); +MA_API ma_vec3f ma_spatializer_listener_get_direction(const ma_spatializer_listener* pListener); +MA_API void ma_spatializer_listener_set_velocity(ma_spatializer_listener* pListener, float x, float y, float z); +MA_API ma_vec3f ma_spatializer_listener_get_velocity(const ma_spatializer_listener* pListener); +MA_API void ma_spatializer_listener_set_speed_of_sound(ma_spatializer_listener* pListener, float speedOfSound); +MA_API float ma_spatializer_listener_get_speed_of_sound(const ma_spatializer_listener* pListener); +MA_API void ma_spatializer_listener_set_world_up(ma_spatializer_listener* pListener, float x, float y, float z); +MA_API ma_vec3f ma_spatializer_listener_get_world_up(const ma_spatializer_listener* pListener); +MA_API void ma_spatializer_listener_set_enabled(ma_spatializer_listener* pListener, ma_bool32 isEnabled); +MA_API ma_bool32 ma_spatializer_listener_is_enabled(const ma_spatializer_listener* pListener); + + +typedef struct +{ + ma_uint32 channelsIn; + ma_uint32 channelsOut; + ma_channel* pChannelMapIn; + ma_attenuation_model attenuationModel; + ma_positioning positioning; + ma_handedness handedness; /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */ + float minGain; + float maxGain; + float minDistance; + float maxDistance; + float rolloff; + float coneInnerAngleInRadians; + float coneOuterAngleInRadians; + float coneOuterGain; + float dopplerFactor; /* Set to 0 to disable doppler effect. This will run on a fast path. */ + ma_uint32 gainSmoothTimeInFrames; /* When the gain of a channel changes during spatialization, the transition will be linearly interpolated over this number of frames. */ +} ma_spatializer_config; + +MA_API ma_spatializer_config ma_spatializer_config_init(ma_uint32 channelsIn, ma_uint32 channelsOut); + + +typedef struct +{ + ma_spatializer_config config; + ma_vec3f position; + ma_vec3f direction; + ma_vec3f velocity; /* For doppler effect. */ + float dopplerPitch; /* Will be updated by ma_spatializer_process_pcm_frames() and can be used by higher level functions to apply a pitch shift for doppler effect. */ + ma_gainer gainer; /* For smooth gain transitions. */ + float* pNewChannelGainsOut; /* An offset of _pHeap. Used by ma_spatializer_process_pcm_frames() to store new channel gains. The number of elements in this array is equal to config.channelsOut. */ + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; +} ma_spatializer; + +MA_API ma_result ma_spatializer_get_heap_size(const ma_spatializer_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_spatializer_init_preallocated(const ma_spatializer_config* pConfig, void* pHeap, ma_spatializer* pSpatializer); +MA_API ma_result ma_spatializer_init(const ma_spatializer_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_spatializer* pSpatializer); +MA_API void ma_spatializer_uninit(ma_spatializer* pSpatializer, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_spatializer_process_pcm_frames(ma_spatializer* pSpatializer, ma_spatializer_listener* pListener, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); +MA_API ma_uint32 ma_spatializer_get_input_channels(const ma_spatializer* pSpatializer); +MA_API ma_uint32 ma_spatializer_get_output_channels(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_attenuation_model(ma_spatializer* pSpatializer, ma_attenuation_model attenuationModel); +MA_API ma_attenuation_model ma_spatializer_get_attenuation_model(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_positioning(ma_spatializer* pSpatializer, ma_positioning positioning); +MA_API ma_positioning ma_spatializer_get_positioning(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_rolloff(ma_spatializer* pSpatializer, float rolloff); +MA_API float ma_spatializer_get_rolloff(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_min_gain(ma_spatializer* pSpatializer, float minGain); +MA_API float ma_spatializer_get_min_gain(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_max_gain(ma_spatializer* pSpatializer, float maxGain); +MA_API float ma_spatializer_get_max_gain(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_min_distance(ma_spatializer* pSpatializer, float minDistance); +MA_API float ma_spatializer_get_min_distance(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_max_distance(ma_spatializer* pSpatializer, float maxDistance); +MA_API float ma_spatializer_get_max_distance(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_cone(ma_spatializer* pSpatializer, float innerAngleInRadians, float outerAngleInRadians, float outerGain); +MA_API void ma_spatializer_get_cone(const ma_spatializer* pSpatializer, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain); +MA_API void ma_spatializer_set_doppler_factor(ma_spatializer* pSpatializer, float dopplerFactor); +MA_API float ma_spatializer_get_doppler_factor(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_position(ma_spatializer* pSpatializer, float x, float y, float z); +MA_API ma_vec3f ma_spatializer_get_position(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_direction(ma_spatializer* pSpatializer, float x, float y, float z); +MA_API ma_vec3f ma_spatializer_get_direction(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_velocity(ma_spatializer* pSpatializer, float x, float y, float z); +MA_API ma_vec3f ma_spatializer_get_velocity(const ma_spatializer* pSpatializer); + + + /************************************************************************************************************************************************************ ************************************************************************************************************************************************************* @@ -987,75 +1373,104 @@ typedef struct ma_uint32 inTimeFrac; union { - float f32[MA_MAX_CHANNELS]; - ma_int16 s16[MA_MAX_CHANNELS]; + float* f32; + ma_int16* s16; } x0; /* The previous input frame. */ union { - float f32[MA_MAX_CHANNELS]; - ma_int16 s16[MA_MAX_CHANNELS]; + float* f32; + ma_int16* s16; } x1; /* The next input frame. */ ma_lpf lpf; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_linear_resampler; -MA_API ma_result ma_linear_resampler_init(const ma_linear_resampler_config* pConfig, ma_linear_resampler* pResampler); -MA_API void ma_linear_resampler_uninit(ma_linear_resampler* pResampler); +MA_API ma_result ma_linear_resampler_get_heap_size(const ma_linear_resampler_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_linear_resampler_init_preallocated(const ma_linear_resampler_config* pConfig, void* pHeap, ma_linear_resampler* pResampler); +MA_API ma_result ma_linear_resampler_init(const ma_linear_resampler_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_linear_resampler* pResampler); +MA_API void ma_linear_resampler_uninit(ma_linear_resampler* pResampler, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_linear_resampler_process_pcm_frames(ma_linear_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut); MA_API ma_result ma_linear_resampler_set_rate(ma_linear_resampler* pResampler, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut); MA_API ma_result ma_linear_resampler_set_rate_ratio(ma_linear_resampler* pResampler, float ratioInOut); -MA_API ma_uint64 ma_linear_resampler_get_required_input_frame_count(const ma_linear_resampler* pResampler, ma_uint64 outputFrameCount); -MA_API ma_uint64 ma_linear_resampler_get_expected_output_frame_count(const ma_linear_resampler* pResampler, ma_uint64 inputFrameCount); MA_API ma_uint64 ma_linear_resampler_get_input_latency(const ma_linear_resampler* pResampler); MA_API ma_uint64 ma_linear_resampler_get_output_latency(const ma_linear_resampler* pResampler); +MA_API ma_result ma_linear_resampler_get_required_input_frame_count(const ma_linear_resampler* pResampler, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount); +MA_API ma_result ma_linear_resampler_get_expected_output_frame_count(const ma_linear_resampler* pResampler, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount); + + +typedef struct ma_resampler_config ma_resampler_config; + +typedef void ma_resampling_backend; +typedef struct +{ + ma_result (* onGetHeapSize )(void* pUserData, const ma_resampler_config* pConfig, size_t* pHeapSizeInBytes); + ma_result (* onInit )(void* pUserData, const ma_resampler_config* pConfig, void* pHeap, ma_resampling_backend** ppBackend); + void (* onUninit )(void* pUserData, ma_resampling_backend* pBackend, const ma_allocation_callbacks* pAllocationCallbacks); + ma_result (* onProcess )(void* pUserData, ma_resampling_backend* pBackend, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut); + ma_result (* onSetRate )(void* pUserData, ma_resampling_backend* pBackend, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut); /* Optional. Rate changes will be disabled. */ + ma_uint64 (* onGetInputLatency )(void* pUserData, const ma_resampling_backend* pBackend); /* Optional. Latency will be reported as 0. */ + ma_uint64 (* onGetOutputLatency )(void* pUserData, const ma_resampling_backend* pBackend); /* Optional. Latency will be reported as 0. */ + ma_result (* onGetRequiredInputFrameCount )(void* pUserData, const ma_resampling_backend* pBackend, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount); /* Optional. Latency mitigation will be disabled. */ + ma_result (* onGetExpectedOutputFrameCount)(void* pUserData, const ma_resampling_backend* pBackend, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount); /* Optional. Latency mitigation will be disabled. */ +} ma_resampling_backend_vtable; typedef enum { - ma_resample_algorithm_linear = 0, /* Fastest, lowest quality. Optional low-pass filtering. Default. */ - ma_resample_algorithm_speex + ma_resample_algorithm_linear = 0, /* Fastest, lowest quality. Optional low-pass filtering. Default. */ + ma_resample_algorithm_custom, } ma_resample_algorithm; -typedef struct +struct ma_resampler_config { ma_format format; /* Must be either ma_format_f32 or ma_format_s16. */ ma_uint32 channels; ma_uint32 sampleRateIn; ma_uint32 sampleRateOut; - ma_resample_algorithm algorithm; + ma_resample_algorithm algorithm; /* When set to ma_resample_algorithm_custom, pBackendVTable will be used. */ + ma_resampling_backend_vtable* pBackendVTable; + void* pBackendUserData; struct { ma_uint32 lpfOrder; - double lpfNyquistFactor; } linear; - struct - { - int quality; /* 0 to 10. Defaults to 3. */ - } speex; -} ma_resampler_config; +}; MA_API ma_resampler_config ma_resampler_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, ma_resample_algorithm algorithm); typedef struct { - ma_resampler_config config; + ma_resampling_backend* pBackend; + ma_resampling_backend_vtable* pBackendVTable; + void* pBackendUserData; + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRateIn; + ma_uint32 sampleRateOut; union { ma_linear_resampler linear; - struct - { - void* pSpeexResamplerState; /* SpeexResamplerState* */ - } speex; - } state; + } state; /* State for stock resamplers so we can avoid a malloc. For stock resamplers, pBackend will point here. */ + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_resampler; +MA_API ma_result ma_resampler_get_heap_size(const ma_resampler_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_resampler_init_preallocated(const ma_resampler_config* pConfig, void* pHeap, ma_resampler* pResampler); + /* Initializes a new resampler object from a config. */ -MA_API ma_result ma_resampler_init(const ma_resampler_config* pConfig, ma_resampler* pResampler); +MA_API ma_result ma_resampler_init(const ma_resampler_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_resampler* pResampler); /* Uninitializes a resampler. */ -MA_API void ma_resampler_uninit(ma_resampler* pResampler); +MA_API void ma_resampler_uninit(ma_resampler* pResampler, const ma_allocation_callbacks* pAllocationCallbacks); /* Converts the given input data. @@ -1094,23 +1509,6 @@ The ration is in/out. */ MA_API ma_result ma_resampler_set_rate_ratio(ma_resampler* pResampler, float ratio); - -/* -Calculates the number of whole input frames that would need to be read from the client in order to output the specified -number of output frames. - -The returned value does not include cached input frames. It only returns the number of extra frames that would need to be -read from the input buffer in order to output the specified number of output frames. -*/ -MA_API ma_uint64 ma_resampler_get_required_input_frame_count(const ma_resampler* pResampler, ma_uint64 outputFrameCount); - -/* -Calculates the number of whole output frames that would be output after fully reading and consuming the specified number of -input frames. -*/ -MA_API ma_uint64 ma_resampler_get_expected_output_frame_count(const ma_resampler* pResampler, ma_uint64 inputFrameCount); - - /* Retrieves the latency introduced by the resampler in input frames. */ @@ -1121,6 +1519,20 @@ Retrieves the latency introduced by the resampler in output frames. */ MA_API ma_uint64 ma_resampler_get_output_latency(const ma_resampler* pResampler); +/* +Calculates the number of whole input frames that would need to be read from the client in order to output the specified +number of output frames. + +The returned value does not include cached input frames. It only returns the number of extra frames that would need to be +read from the input buffer in order to output the specified number of output frames. +*/ +MA_API ma_result ma_resampler_get_required_input_frame_count(const ma_resampler* pResampler, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount); + +/* +Calculates the number of whole output frames that would be output after fully reading and consuming the specified number of +input frames. +*/ +MA_API ma_result ma_resampler_get_expected_output_frame_count(const ma_resampler* pResampler, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount); /************************************************************************************************************************************************************** @@ -1128,15 +1540,33 @@ MA_API ma_uint64 ma_resampler_get_output_latency(const ma_resampler* pResampler) Channel Conversion **************************************************************************************************************************************************************/ +typedef enum +{ + ma_channel_conversion_path_unknown, + ma_channel_conversion_path_passthrough, + ma_channel_conversion_path_mono_out, /* Converting to mono. */ + ma_channel_conversion_path_mono_in, /* Converting from mono. */ + ma_channel_conversion_path_shuffle, /* Simple shuffle. Will use this when all channels are present in both input and output channel maps, but just in a different order. */ + ma_channel_conversion_path_weights /* Blended based on weights. */ +} ma_channel_conversion_path; + +typedef enum +{ + ma_mono_expansion_mode_duplicate = 0, /* The default. */ + ma_mono_expansion_mode_average, /* Average the mono channel across all channels. */ + ma_mono_expansion_mode_stereo_only, /* Duplicate to the left and right channels only and ignore the others. */ + ma_mono_expansion_mode_default = ma_mono_expansion_mode_duplicate +} ma_mono_expansion_mode; + typedef struct { ma_format format; ma_uint32 channelsIn; ma_uint32 channelsOut; - ma_channel channelMapIn[MA_MAX_CHANNELS]; - ma_channel channelMapOut[MA_MAX_CHANNELS]; + const ma_channel* pChannelMapIn; + const ma_channel* pChannelMapOut; ma_channel_mix_mode mixingMode; - float weights[MA_MAX_CHANNELS][MA_MAX_CHANNELS]; /* [in][out]. Only used when mixingMode is set to ma_channel_mix_mode_custom_weights. */ + float** ppWeights; /* [in][out]. Only used when mixingMode is set to ma_channel_mix_mode_custom_weights. */ } ma_channel_converter_config; MA_API ma_channel_converter_config ma_channel_converter_config_init(ma_format format, ma_uint32 channelsIn, const ma_channel* pChannelMapIn, ma_uint32 channelsOut, const ma_channel* pChannelMapOut, ma_channel_mix_mode mixingMode); @@ -1146,24 +1576,29 @@ typedef struct ma_format format; ma_uint32 channelsIn; ma_uint32 channelsOut; - ma_channel channelMapIn[MA_MAX_CHANNELS]; - ma_channel channelMapOut[MA_MAX_CHANNELS]; ma_channel_mix_mode mixingMode; + ma_channel_conversion_path conversionPath; + ma_channel* pChannelMapIn; + ma_channel* pChannelMapOut; + ma_uint8* pShuffleTable; /* Indexed by output channel index. */ union { - float f32[MA_MAX_CHANNELS][MA_MAX_CHANNELS]; - ma_int32 s16[MA_MAX_CHANNELS][MA_MAX_CHANNELS]; - } weights; - ma_bool8 isPassthrough; - ma_bool8 isSimpleShuffle; - ma_bool8 isSimpleMonoExpansion; - ma_bool8 isStereoToMono; - ma_uint8 shuffleTable[MA_MAX_CHANNELS]; + float** f32; + ma_int32** s16; + } weights; /* [in][out] */ + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_channel_converter; -MA_API ma_result ma_channel_converter_init(const ma_channel_converter_config* pConfig, ma_channel_converter* pConverter); -MA_API void ma_channel_converter_uninit(ma_channel_converter* pConverter); +MA_API ma_result ma_channel_converter_get_heap_size(const ma_channel_converter_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_channel_converter_init_preallocated(const ma_channel_converter_config* pConfig, void* pHeap, ma_channel_converter* pConverter); +MA_API ma_result ma_channel_converter_init(const ma_channel_converter_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_channel_converter* pConverter); +MA_API void ma_channel_converter_uninit(ma_channel_converter* pConverter, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_channel_converter_process_pcm_frames(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); +MA_API ma_result ma_channel_converter_get_input_channel_map(const ma_channel_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_channel_converter_get_output_channel_map(const ma_channel_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap); /************************************************************************************************************************************************************** @@ -1179,33 +1614,39 @@ typedef struct ma_uint32 channelsOut; ma_uint32 sampleRateIn; ma_uint32 sampleRateOut; - ma_channel channelMapIn[MA_MAX_CHANNELS]; - ma_channel channelMapOut[MA_MAX_CHANNELS]; + ma_channel* pChannelMapIn; + ma_channel* pChannelMapOut; ma_dither_mode ditherMode; ma_channel_mix_mode channelMixMode; - float channelWeights[MA_MAX_CHANNELS][MA_MAX_CHANNELS]; /* [in][out]. Only used when channelMixMode is set to ma_channel_mix_mode_custom_weights. */ - struct - { - ma_resample_algorithm algorithm; - ma_bool32 allowDynamicSampleRate; - struct - { - ma_uint32 lpfOrder; - double lpfNyquistFactor; - } linear; - struct - { - int quality; - } speex; - } resampling; + float** ppChannelWeights; /* [in][out]. Only used when mixingMode is set to ma_channel_mix_mode_custom_weights. */ + ma_bool32 allowDynamicSampleRate; + ma_resampler_config resampling; } ma_data_converter_config; MA_API ma_data_converter_config ma_data_converter_config_init_default(void); MA_API ma_data_converter_config ma_data_converter_config_init(ma_format formatIn, ma_format formatOut, ma_uint32 channelsIn, ma_uint32 channelsOut, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut); + +typedef enum +{ + ma_data_converter_execution_path_passthrough, /* No conversion. */ + ma_data_converter_execution_path_format_only, /* Only format conversion. */ + ma_data_converter_execution_path_channels_only, /* Only channel conversion. */ + ma_data_converter_execution_path_resample_only, /* Only resampling. */ + ma_data_converter_execution_path_resample_first, /* All conversions, but resample as the first step. */ + ma_data_converter_execution_path_channels_first /* All conversions, but channels as the first step. */ +} ma_data_converter_execution_path; + typedef struct { - ma_data_converter_config config; + ma_format formatIn; + ma_format formatOut; + ma_uint32 channelsIn; + ma_uint32 channelsOut; + ma_uint32 sampleRateIn; + ma_uint32 sampleRateOut; + ma_dither_mode ditherMode; + ma_data_converter_execution_path executionPath; /* The execution path the data converter will follow when processing. */ ma_channel_converter channelConverter; ma_resampler resampler; ma_bool8 hasPreFormatConversion; @@ -1213,17 +1654,25 @@ typedef struct ma_bool8 hasChannelConverter; ma_bool8 hasResampler; ma_bool8 isPassthrough; + + /* Memory management. */ + ma_bool8 _ownsHeap; + void* _pHeap; } ma_data_converter; -MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, ma_data_converter* pConverter); -MA_API void ma_data_converter_uninit(ma_data_converter* pConverter); +MA_API ma_result ma_data_converter_get_heap_size(const ma_data_converter_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_data_converter_init_preallocated(const ma_data_converter_config* pConfig, void* pHeap, ma_data_converter* pConverter); +MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_converter* pConverter); +MA_API void ma_data_converter_uninit(ma_data_converter* pConverter, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_data_converter_process_pcm_frames(ma_data_converter* pConverter, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut); MA_API ma_result ma_data_converter_set_rate(ma_data_converter* pConverter, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut); MA_API ma_result ma_data_converter_set_rate_ratio(ma_data_converter* pConverter, float ratioInOut); -MA_API ma_uint64 ma_data_converter_get_required_input_frame_count(const ma_data_converter* pConverter, ma_uint64 outputFrameCount); -MA_API ma_uint64 ma_data_converter_get_expected_output_frame_count(const ma_data_converter* pConverter, ma_uint64 inputFrameCount); MA_API ma_uint64 ma_data_converter_get_input_latency(const ma_data_converter* pConverter); MA_API ma_uint64 ma_data_converter_get_output_latency(const ma_data_converter* pConverter); +MA_API ma_result ma_data_converter_get_required_input_frame_count(const ma_data_converter* pConverter, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount); +MA_API ma_result ma_data_converter_get_expected_output_frame_count(const ma_data_converter* pConverter, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount); +MA_API ma_result ma_data_converter_get_input_channel_map(const ma_data_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_data_converter_get_output_channel_map(const ma_data_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap); /************************************************************************************************************************************************************ @@ -1275,9 +1724,6 @@ This is used in the shuffle table to indicate that the channel index is undefine */ #define MA_CHANNEL_INDEX_NULL 255 -/* Retrieves the channel position of the specified channel based on miniaudio's default channel map. */ -MA_API ma_channel ma_channel_map_get_default_channel(ma_uint32 channelCount, ma_uint32 channelIndex); - /* Retrieves the channel position of the specified channel in the given channel map. @@ -1290,14 +1736,14 @@ Initializes a blank channel map. When a blank channel map is specified anywhere it indicates that the native channel map should be used. */ -MA_API void ma_channel_map_init_blank(ma_uint32 channels, ma_channel* pChannelMap); +MA_API void ma_channel_map_init_blank(ma_channel* pChannelMap, ma_uint32 channels); /* Helper for retrieving a standard channel map. -The output channel map buffer must have a capacity of at least `channels`. +The output channel map buffer must have a capacity of at least `channelMapCap`. */ -MA_API void ma_get_standard_channel_map(ma_standard_channel_map standardChannelMap, ma_uint32 channels, ma_channel* pChannelMap); +MA_API void ma_channel_map_init_standard(ma_standard_channel_map standardChannelMap, ma_channel* pChannelMap, size_t channelMapCap, ma_uint32 channels); /* Copies a channel map. @@ -1311,7 +1757,7 @@ Copies a channel map if one is specified, otherwise copies the default channel m The output buffer must have a capacity of at least `channels`. If not NULL, the input channel map must also have a capacity of at least `channels`. */ -MA_API void ma_channel_map_copy_or_default(ma_channel* pOut, const ma_channel* pIn, ma_uint32 channels); +MA_API void ma_channel_map_copy_or_default(ma_channel* pOut, size_t channelMapCapOut, const ma_channel* pIn, ma_uint32 channels); /* @@ -1326,7 +1772,7 @@ Invalid channel maps: The channel map buffer must have a capacity of at least `channels`. */ -MA_API ma_bool32 ma_channel_map_valid(ma_uint32 channels, const ma_channel* pChannelMap); +MA_API ma_bool32 ma_channel_map_is_valid(const ma_channel* pChannelMap, ma_uint32 channels); /* Helper for comparing two channel maps for equality. @@ -1335,14 +1781,14 @@ This assumes the channel count is the same between the two. Both channels map buffers must have a capacity of at least `channels`. */ -MA_API ma_bool32 ma_channel_map_equal(ma_uint32 channels, const ma_channel* pChannelMapA, const ma_channel* pChannelMapB); +MA_API ma_bool32 ma_channel_map_is_equal(const ma_channel* pChannelMapA, const ma_channel* pChannelMapB, ma_uint32 channels); /* Helper for determining if a channel map is blank (all channels set to MA_CHANNEL_NONE). The channel map buffer must have a capacity of at least `channels`. */ -MA_API ma_bool32 ma_channel_map_blank(ma_uint32 channels, const ma_channel* pChannelMap); +MA_API ma_bool32 ma_channel_map_is_blank(const ma_channel* pChannelMap, ma_uint32 channels); /* Helper for determining whether or not a channel is present in the given channel map. @@ -1382,10 +1828,10 @@ typedef struct ma_uint32 subbufferSizeInBytes; ma_uint32 subbufferCount; ma_uint32 subbufferStrideInBytes; - MA_ATOMIC ma_uint32 encodedReadOffset; /* Most significant bit is the loop flag. Lower 31 bits contains the actual offset in bytes. Must be used atomically. */ - MA_ATOMIC ma_uint32 encodedWriteOffset; /* Most significant bit is the loop flag. Lower 31 bits contains the actual offset in bytes. Must be used atomically. */ - ma_bool8 ownsBuffer; /* Used to know whether or not miniaudio is responsible for free()-ing the buffer. */ - ma_bool8 clearOnWriteAcquire; /* When set, clears the acquired write buffer before returning from ma_rb_acquire_write(). */ + MA_ATOMIC(4, ma_uint32) encodedReadOffset; /* Most significant bit is the loop flag. Lower 31 bits contains the actual offset in bytes. Must be used atomically. */ + MA_ATOMIC(4, ma_uint32) encodedWriteOffset; /* Most significant bit is the loop flag. Lower 31 bits contains the actual offset in bytes. Must be used atomically. */ + ma_bool8 ownsBuffer; /* Used to know whether or not miniaudio is responsible for free()-ing the buffer. */ + ma_bool8 clearOnWriteAcquire; /* When set, clears the acquired write buffer before returning from ma_rb_acquire_write(). */ ma_allocation_callbacks allocationCallbacks; } ma_rb; @@ -1394,9 +1840,9 @@ MA_API ma_result ma_rb_init(size_t bufferSizeInBytes, void* pOptionalPreallocate MA_API void ma_rb_uninit(ma_rb* pRB); MA_API void ma_rb_reset(ma_rb* pRB); MA_API ma_result ma_rb_acquire_read(ma_rb* pRB, size_t* pSizeInBytes, void** ppBufferOut); -MA_API ma_result ma_rb_commit_read(ma_rb* pRB, size_t sizeInBytes, void* pBufferOut); +MA_API ma_result ma_rb_commit_read(ma_rb* pRB, size_t sizeInBytes); MA_API ma_result ma_rb_acquire_write(ma_rb* pRB, size_t* pSizeInBytes, void** ppBufferOut); -MA_API ma_result ma_rb_commit_write(ma_rb* pRB, size_t sizeInBytes, void* pBufferOut); +MA_API ma_result ma_rb_commit_write(ma_rb* pRB, size_t sizeInBytes); MA_API ma_result ma_rb_seek_read(ma_rb* pRB, size_t offsetInBytes); MA_API ma_result ma_rb_seek_write(ma_rb* pRB, size_t offsetInBytes); MA_API ma_int32 ma_rb_pointer_distance(ma_rb* pRB); /* Returns the distance between the write pointer and the read pointer. Should never be negative for a correct program. Will return the number of bytes that can be read before the read pointer hits the write pointer. */ @@ -1420,9 +1866,9 @@ MA_API ma_result ma_pcm_rb_init(ma_format format, ma_uint32 channels, ma_uint32 MA_API void ma_pcm_rb_uninit(ma_pcm_rb* pRB); MA_API void ma_pcm_rb_reset(ma_pcm_rb* pRB); MA_API ma_result ma_pcm_rb_acquire_read(ma_pcm_rb* pRB, ma_uint32* pSizeInFrames, void** ppBufferOut); -MA_API ma_result ma_pcm_rb_commit_read(ma_pcm_rb* pRB, ma_uint32 sizeInFrames, void* pBufferOut); +MA_API ma_result ma_pcm_rb_commit_read(ma_pcm_rb* pRB, ma_uint32 sizeInFrames); MA_API ma_result ma_pcm_rb_acquire_write(ma_pcm_rb* pRB, ma_uint32* pSizeInFrames, void** ppBufferOut); -MA_API ma_result ma_pcm_rb_commit_write(ma_pcm_rb* pRB, ma_uint32 sizeInFrames, void* pBufferOut); +MA_API ma_result ma_pcm_rb_commit_write(ma_pcm_rb* pRB, ma_uint32 sizeInFrames); MA_API ma_result ma_pcm_rb_seek_read(ma_pcm_rb* pRB, ma_uint32 offsetInFrames); MA_API ma_result ma_pcm_rb_seek_write(ma_pcm_rb* pRB, ma_uint32 offsetInFrames); MA_API ma_int32 ma_pcm_rb_pointer_distance(ma_pcm_rb* pRB); /* Return value is in frames. */ @@ -1464,17 +1910,22 @@ Retrieves a human readable description of the given result code. MA_API const char* ma_result_description(ma_result result); /* -malloc(). Calls MA_MALLOC(). +malloc() */ MA_API void* ma_malloc(size_t sz, const ma_allocation_callbacks* pAllocationCallbacks); /* -realloc(). Calls MA_REALLOC(). +calloc() +*/ +MA_API void* ma_calloc(size_t sz, const ma_allocation_callbacks* pAllocationCallbacks); + +/* +realloc() */ MA_API void* ma_realloc(void* p, size_t sz, const ma_allocation_callbacks* pAllocationCallbacks); /* -free(). Calls MA_FREE(). +free() */ MA_API void ma_free(void* p, const ma_allocation_callbacks* pAllocationCallbacks); @@ -1622,11 +2073,14 @@ This section contains the APIs for device playback and capture. Here is where yo #define MA_HAS_NULL #endif -#define MA_STATE_UNINITIALIZED 0 -#define MA_STATE_STOPPED 1 /* The device's default state after initialization. */ -#define MA_STATE_STARTED 2 /* The device is started and is requesting and/or delivering audio data. */ -#define MA_STATE_STARTING 3 /* Transitioning from a stopped state to started. */ -#define MA_STATE_STOPPING 4 /* Transitioning from a started state to stopped. */ +typedef enum +{ + ma_device_state_uninitialized = 0, + ma_device_state_stopped = 1, /* The device's default state after initialization. */ + ma_device_state_started = 2, /* The device is started and is requesting and/or delivering audio data. */ + ma_device_state_starting = 3, /* Transitioning from a stopped state to started. */ + ma_device_state_stopping = 4 /* Transitioning from a started state to stopped. */ +} ma_device_state; #ifdef MA_SUPPORT_WASAPI /* We need a IMMNotificationClient object for WASAPI. */ @@ -1701,7 +2155,7 @@ callback. The following APIs cannot be called from inside the callback: The proper way to stop the device is to call `ma_device_stop()` from a different thread, normally the main application thread. */ -typedef void (* ma_device_callback_proc)(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); +typedef void (* ma_device_data_proc)(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); /* The callback for when the device has been stopped. @@ -1722,40 +2176,6 @@ Do not restart or uninitialize the device from the callback. */ typedef void (* ma_stop_proc)(ma_device* pDevice); -/* -The callback for handling log messages. - - -Parameters ----------- -pContext (in) - A pointer to the context the log message originated from. - -pDevice (in) - A pointer to the device the log message originate from, if any. This can be null, in which case the message came from the context. - -logLevel (in) - The log level. This can be one of the following: - - +----------------------+ - | Log Level | - +----------------------+ - | MA_LOG_LEVEL_DEBUG | - | MA_LOG_LEVEL_INFO | - | MA_LOG_LEVEL_WARNING | - | MA_LOG_LEVEL_ERROR | - +----------------------+ - -message (in) - The log message. - - -Remarks -------- -Do not modify the state of the device from inside the callback. -*/ -typedef void (* ma_log_proc)(ma_context* pContext, ma_device* pDevice, ma_uint32 logLevel, const char* message); - typedef enum { ma_device_type_playback = 1, @@ -1907,23 +2327,6 @@ typedef struct char name[256]; ma_bool32 isDefault; - /* - Detailed info. As much of this is filled as possible with ma_context_get_device_info(). Note that you are allowed to initialize - a device with settings outside of this range, but it just means the data will be converted using miniaudio's data conversion - pipeline before sending the data to/from the device. Most programs will need to not worry about these values, but it's provided - here mainly for informational purposes or in the rare case that someone might find it useful. - - These will be set to 0 when returned by ma_context_enumerate_devices() or ma_context_get_devices(). - */ - ma_uint32 formatCount; - ma_format formats[ma_format_count]; - ma_uint32 minChannels; - ma_uint32 maxChannels; - ma_uint32 minSampleRate; - ma_uint32 maxSampleRate; - - - /* Experimental. Don't use these right now. */ ma_uint32 nativeDataFormatCount; struct { @@ -1942,29 +2345,19 @@ struct ma_device_config ma_uint32 periodSizeInMilliseconds; ma_uint32 periods; ma_performance_profile performanceProfile; - ma_bool8 noPreZeroedOutputBuffer; /* When set to true, the contents of the output buffer passed into the data callback will be left undefined rather than initialized to zero. */ + ma_bool8 noPreSilencedOutputBuffer; /* When set to true, the contents of the output buffer passed into the data callback will be left undefined rather than initialized to silence. */ ma_bool8 noClip; /* When set to true, the contents of the output buffer passed into the data callback will be clipped after returning. Only applies when the playback sample format is f32. */ - ma_device_callback_proc dataCallback; + ma_bool8 noDisableDenormals; /* Do not disable denormals when firing the data callback. */ + ma_device_data_proc dataCallback; ma_stop_proc stopCallback; void* pUserData; - struct - { - ma_resample_algorithm algorithm; - struct - { - ma_uint32 lpfOrder; - } linear; - struct - { - int quality; - } speex; - } resampling; + ma_resampler_config resampling; struct { const ma_device_id* pDeviceID; ma_format format; ma_uint32 channels; - ma_channel channelMap[MA_MAX_CHANNELS]; + ma_channel* pChannelMap; ma_channel_mix_mode channelMixMode; ma_share_mode shareMode; } playback; @@ -1973,7 +2366,7 @@ struct ma_device_config const ma_device_id* pDeviceID; ma_format format; ma_uint32 channels; - ma_channel channelMap[MA_MAX_CHANNELS]; + ma_channel* pChannelMap; ma_channel_mix_mode channelMixMode; ma_share_mode shareMode; } capture; @@ -2091,7 +2484,7 @@ internally by miniaudio. On input, if the sample format is set to `ma_format_unknown`, the backend is free to use whatever sample format it desires, so long as it's supported by miniaudio. When the channel count is set to 0, the backend should use the device's native channel count. The same applies for -sample rate. For the channel map, the default should be used when `ma_channel_map_blank()` returns true (all channels set to +sample rate. For the channel map, the default should be used when `ma_channel_map_is_blank()` returns true (all channels set to `MA_CHANNEL_NONE`). On input, the `periodSizeInFrames` or `periodSizeInMilliseconds` option should always be set. The backend should inspect both of these variables. If `periodSizeInFrames` is set, it should take priority, otherwise it needs to be derived from the period size in milliseconds (`periodSizeInMilliseconds`) and the sample rate, keeping in mind that the sample rate may be 0, in which case the @@ -2110,11 +2503,11 @@ This allows miniaudio to then process any necessary data conversion and then pas If the backend requires absolute flexibility with it's data delivery, it can optionally implement the `onDeviceDataLoop()` callback which will allow it to implement the logic that will run on the audio thread. This is much more advanced and is completely optional. -The audio thread should run data delivery logic in a loop while `ma_device_get_state() == MA_STATE_STARTED` and no errors have been +The audio thread should run data delivery logic in a loop while `ma_device_get_state() == ma_device_state_started` and no errors have been encounted. Do not start or stop the device here. That will be handled from outside the `onDeviceDataLoop()` callback. The invocation of the `onDeviceDataLoop()` callback will be handled by miniaudio. When you start the device, miniaudio will fire this -callback. When the device is stopped, the `ma_device_get_state() == MA_STATE_STARTED` condition will fail and the loop will be terminated +callback. When the device is stopped, the `ma_device_get_state() == ma_device_state_started` condition will fail and the loop will be terminated which will then fall through to the part that stops the device. For an example on how to implement the `onDeviceDataLoop()` callback, look at `ma_device_audio_thread__default_read_write()`. Implement the `onDeviceDataLoopWakeup()` callback if you need a mechanism to wake up the audio thread. @@ -2137,7 +2530,6 @@ struct ma_backend_callbacks struct ma_context_config { - ma_log_proc logCallback; /* Legacy logging callback. Will be removed in version 0.11. */ ma_log* pLog; ma_thread_priority threadPriority; size_t threadStackSize; @@ -2200,7 +2592,6 @@ struct ma_context ma_backend backend; /* DirectSound, ALSA, etc. */ ma_log* pLog; ma_log log; /* Only used if the log is owned by the context. The pLog member will be set to &log in this case. */ - ma_log_proc logCallback; /* Legacy callback. Will be removed in version 0.11. */ ma_thread_priority threadPriority; size_t threadStackSize; void* pUserData; @@ -2609,8 +3000,8 @@ struct ma_device ma_context* pContext; ma_device_type type; ma_uint32 sampleRate; - MA_ATOMIC ma_uint32 state; /* The state of the device is variable and can change at any time on any thread. Must be used atomically. */ - ma_device_callback_proc onData; /* Set once at initialization time and should not be changed after. */ + MA_ATOMIC(4, ma_device_state) state; /* The state of the device is variable and can change at any time on any thread. Must be used atomically. */ + ma_device_data_proc onData; /* Set once at initialization time and should not be changed after. */ ma_stop_proc onStop; /* Set once at initialization time and should not be changed after. */ void* pUserData; /* Application defined data. */ ma_mutex startStopLock; @@ -2620,21 +3011,20 @@ struct ma_device ma_thread thread; ma_result workResult; /* This is set by the worker thread after it's finished doing a job. */ ma_bool8 isOwnerOfContext; /* When set to true, uninitializing the device will also uninitialize the context. Set to true when NULL is passed into ma_device_init(). */ - ma_bool8 noPreZeroedOutputBuffer; + ma_bool8 noPreSilencedOutputBuffer; ma_bool8 noClip; - MA_ATOMIC float masterVolumeFactor; /* Linear 0..1. Can be read and written simultaneously by different threads. Must be used atomically. */ + ma_bool8 noDisableDenormals; + MA_ATOMIC(4, float) masterVolumeFactor; /* Linear 0..1. Can be read and written simultaneously by different threads. Must be used atomically. */ ma_duplex_rb duplexRB; /* Intermediary buffer for duplex device on asynchronous backends. */ struct { ma_resample_algorithm algorithm; + ma_resampling_backend_vtable* pBackendVTable; + void* pBackendUserData; struct { ma_uint32 lpfOrder; } linear; - struct - { - int quality; - } speex; } resampling; struct { @@ -2652,6 +3042,10 @@ struct ma_device ma_uint32 internalPeriods; ma_channel_mix_mode channelMixMode; ma_data_converter converter; + void* pInputCache; /* In external format. Can be null. */ + ma_uint64 inputCacheCap; + ma_uint64 inputCacheConsumed; + ma_uint64 inputCacheRemaining; } playback; struct { @@ -2692,8 +3086,8 @@ struct ma_device ma_performance_profile originalPerformanceProfile; ma_uint32 periodSizeInFramesPlayback; ma_uint32 periodSizeInFramesCapture; - MA_ATOMIC ma_bool32 isStartedCapture; /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ - MA_ATOMIC ma_bool32 isStartedPlayback; /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ + MA_ATOMIC(4, ma_bool32) isStartedCapture; /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ + MA_ATOMIC(4, ma_bool32) isStartedPlayback; /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ ma_bool8 noAutoConvertSRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM. */ ma_bool8 noDefaultQualitySRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY. */ ma_bool8 noHardwareOffloading; @@ -2758,8 +3152,8 @@ struct ma_device struct { /*jack_client_t**/ ma_ptr pClient; - /*jack_port_t**/ ma_ptr pPortsPlayback[MA_MAX_CHANNELS]; - /*jack_port_t**/ ma_ptr pPortsCapture[MA_MAX_CHANNELS]; + /*jack_port_t**/ ma_ptr* ppPortsPlayback; + /*jack_port_t**/ ma_ptr* ppPortsCapture; float* pIntermediaryBufferPlayback; /* Typed as a float because JACK is always floating point. */ float* pIntermediaryBufferCapture; } jack; @@ -2856,7 +3250,7 @@ struct ma_device ma_uint32 currentPeriodFramesRemainingCapture; ma_uint64 lastProcessedFramePlayback; ma_uint64 lastProcessedFrameCapture; - MA_ATOMIC ma_bool32 isStarted; /* Read and written by multiple threads. Must be used atomically, and must be 32-bit for compiler compatibility. */ + MA_ATOMIC(4, ma_bool32) isStarted; /* Read and written by multiple threads. Must be used atomically, and must be 32-bit for compiler compatibility. */ } null_device; #endif }; @@ -2968,6 +3362,9 @@ can then be set directly on the structure. Below are the members of the `ma_cont | ma_thread_priority_default | |--------------------------------------| + threadStackSize + The desired size of the stack for the audio thread. Defaults to the operating system's default. + pUserData A pointer to application-defined data. This can be accessed from the context object directly such as `context.pUserData`. @@ -3022,6 +3419,12 @@ can then be set directly on the structure. Below are the members of the `ma_cont | ma_ios_session_category_option_allow_air_play | AVAudioSessionCategoryOptionAllowAirPlay | |---------------------------------------------------------------------------|------------------------------------------------------------------| + coreaudio.noAudioSessionActivate + iOS only. When set to true, does not perform an explicit [[AVAudioSession sharedInstace] setActive:true] on initialization. + + coreaudio.noAudioSessionDeactivate + iOS only. When set to true, does not perform an explicit [[AVAudioSession sharedInstace] setActive:false] on uninitialization. + jack.pClientName The name of the client to pass to `jack_client_open()`. @@ -3065,9 +3468,12 @@ ma_backend backends[] = { ma_backend_dsound }; +ma_log log; +ma_log_init(&log); +ma_log_register_callback(&log, ma_log_callback_init(my_log_callbac, pMyLogUserData)); + ma_context_config config = ma_context_config_init(); -config.logCallback = my_log_callback; -config.pUserData = pMyUserData; +config.pLog = &log; // Specify a custom log object in the config so any logs that are posted from ma_context_init() are captured. ma_context context; ma_result result = ma_context_init(backends, sizeof(backends)/sizeof(backends[0]), &config, &context); @@ -3077,6 +3483,9 @@ if (result != MA_SUCCESS) { // Couldn't find an appropriate backend. } } + +// You could also attach a log callback post-initialization: +ma_log_register_callback(ma_context_get_log(&context), ma_log_callback_init(my_log_callback, pMyLogUserData)); ``` @@ -3128,6 +3537,8 @@ Remarks Pass the returned pointer to `ma_log_post()`, `ma_log_postv()` or `ma_log_postf()` to post a log message. +You can attach your own logging callback to the log with `ma_log_register_callback()` + Return Value ------------ @@ -3269,10 +3680,6 @@ deviceType (in) pDeviceID (in) The ID of the device being queried. -shareMode (in) - The share mode to query for device capabilities. This should be set to whatever you're intending on using when initializing the device. If you're unsure, - set this to `ma_share_mode_shared`. - pDeviceInfo (out) A pointer to the `ma_device_info` structure that will receive the device information. @@ -3298,7 +3705,7 @@ the requested share mode is unsupported. This leaves pDeviceInfo unmodified in the result of an error. */ -MA_API ma_result ma_context_get_device_info(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo); +MA_API ma_result ma_context_get_device_info(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo); /* Determines if the given context supports loopback mode. @@ -3469,7 +3876,7 @@ then be set directly on the structure. Below are the members of the `ma_device_c A hint to miniaudio as to the performance requirements of your program. Can be either `ma_performance_profile_low_latency` (default) or `ma_performance_profile_conservative`. This mainly affects the size of default buffers and can usually be left at it's default value. - noPreZeroedOutputBuffer + noPreSilencedOutputBuffer When set to true, the contents of the output buffer passed into the data callback will be left undefined. When set to false (default), the contents of the output buffer will be cleared the zero. You can use this to avoid the overhead of zeroing out the buffer if you can guarantee that your data callback will write to every sample in the output buffer, or if you are doing your own clearing. @@ -3479,6 +3886,9 @@ then be set directly on the structure. Below are the members of the `ma_device_c contents of the output buffer are left alone after returning and it will be left up to the backend itself to decide whether or not the clip. This only applies when the playback sample format is f32. + noDisableDenormals + By default, miniaudio will disable denormals when the data callback is called. Setting this to true will prevent the disabling of denormals. + dataCallback The callback to fire whenever data is ready to be delivered to or from the device. @@ -3493,6 +3903,12 @@ then be set directly on the structure. Below are the members of the `ma_device_c The resampling algorithm to use when miniaudio needs to perform resampling between the rate specified by `sampleRate` and the device's native rate. The default value is `ma_resample_algorithm_linear`, and the quality can be configured with `resampling.linear.lpfOrder`. + resampling.pBackendVTable + A pointer to an optional vtable that can be used for plugging in a custom resampler. + + resampling.pBackendUserData + A pointer that will passed to callbacks in pBackendVTable. + resampling.linear.lpfOrder The linear resampler applies a low-pass filter as part of it's procesing for anti-aliasing. This setting controls the order of the filter. The higher the value, the better the quality, in general. Setting this to 0 will disable low-pass filtering altogether. The maximum value is @@ -3510,9 +3926,9 @@ then be set directly on the structure. Below are the members of the `ma_device_c The number of channels to use for playback. When set to 0 the device's native channel count will be used. This can be retrieved after initialization from the device object directly with `device.playback.channels`. - playback.channelMap + playback.pChannelMap The channel map to use for playback. When left empty, the device's native channel map will be used. This can be retrieved after initialization from the - device object direct with `device.playback.channelMap`. + device object direct with `device.playback.pChannelMap`. When set, the buffer should contain `channels` items. playback.shareMode The preferred share mode to use for playback. Can be either `ma_share_mode_shared` (default) or `ma_share_mode_exclusive`. Note that if you specify @@ -3531,9 +3947,9 @@ then be set directly on the structure. Below are the members of the `ma_device_c The number of channels to use for capture. When set to 0 the device's native channel count will be used. This can be retrieved after initialization from the device object directly with `device.capture.channels`. - capture.channelMap + capture.pChannelMap The channel map to use for capture. When left empty, the device's native channel map will be used. This can be retrieved after initialization from the - device object direct with `device.capture.channelMap`. + device object direct with `device.capture.pChannelMap`. When set, the buffer should contain `channels` items. capture.shareMode The preferred share mode to use for capture. Can be either `ma_share_mode_shared` (default) or `ma_share_mode_exclusive`. Note that if you specify @@ -3578,6 +3994,25 @@ then be set directly on the structure. Below are the members of the `ma_device_c find the closest match between the sample rate requested in the device config and the sample rates natively supported by the hardware. When set to false, the sample rate currently set by the operating system will always be used. + opensl.streamType + OpenSL only. Explicitly sets the stream type. If left unset (`ma_opensl_stream_type_default`), the + stream type will be left unset. Think of this as the type of audio you're playing. + + opensl.recordingPreset + OpenSL only. Explicitly sets the type of recording your program will be doing. When left + unset, the recording preset will be left unchanged. + + aaudio.usage + AAudio only. Explicitly sets the nature of the audio the program will be consuming. When + left unset, the usage will be left unchanged. + + aaudio.contentType + AAudio only. Sets the content type. When left unset, the content type will be left unchanged. + + aaudio.inputPreset + AAudio only. Explicitly sets the type of recording your program will be doing. When left + unset, the input preset will be left unchanged. + Once initialized, the device's config is immutable. If you need to change the config you will need to initialize a new device. @@ -3920,17 +4355,17 @@ Return Value ------------ The current state of the device. The return value will be one of the following: - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_UNINITIALIZED | Will only be returned if the device is in the middle of initialization. | - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_STOPPED | The device is stopped. The initial state of the device after initialization. | - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_STARTED | The device started and requesting and/or delivering audio data. | - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_STARTING | The device is in the process of starting. | - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_STOPPING | The device is in the process of stopping. | - +------------------------+------------------------------------------------------------------------------+ + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_uninitialized | Will only be returned if the device is in the middle of initialization. | + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_stopped | The device is stopped. The initial state of the device after initialization. | + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_started | The device started and requesting and/or delivering audio data. | + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_starting | The device is in the process of starting. | + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_stopping | The device is in the process of stopping. | + +-------------------------------+------------------------------------------------------------------------------+ Thread Safety @@ -3949,22 +4384,22 @@ Remarks The general flow of a devices state goes like this: ``` - ma_device_init() -> MA_STATE_UNINITIALIZED -> MA_STATE_STOPPED - ma_device_start() -> MA_STATE_STARTING -> MA_STATE_STARTED - ma_device_stop() -> MA_STATE_STOPPING -> MA_STATE_STOPPED + ma_device_init() -> ma_device_state_uninitialized -> ma_device_state_stopped + ma_device_start() -> ma_device_state_starting -> ma_device_state_started + ma_device_stop() -> ma_device_state_stopping -> ma_device_state_stopped ``` When the state of the device is changed with `ma_device_start()` or `ma_device_stop()` at this same time as this function is called, the value returned by this function could potentially be out of sync. If this is significant to your program you need to implement your own synchronization. */ -MA_API ma_uint32 ma_device_get_state(const ma_device* pDevice); +MA_API ma_device_state ma_device_get_state(const ma_device* pDevice); /* Sets the master volume factor for the device. -The volume factor must be between 0 (silence) and 1 (full volume). Use `ma_device_set_master_gain_db()` to use decibel notation, where 0 is full volume and +The volume factor must be between 0 (silence) and 1 (full volume). Use `ma_device_set_master_volume_db()` to use decibel notation, where 0 is full volume and values less than 0 decreases the volume. @@ -3974,14 +4409,14 @@ pDevice (in) A pointer to the device whose volume is being set. volume (in) - The new volume factor. Must be within the range of [0, 1]. + The new volume factor. Must be >= 0. Return Value ------------ MA_SUCCESS if the volume was set successfully. MA_INVALID_ARGS if pDevice is NULL. -MA_INVALID_ARGS if the volume factor is not within the range of [0, 1]. +MA_INVALID_ARGS if volume is negative. Thread Safety @@ -4004,8 +4439,8 @@ This does not change the operating system's volume. It only affects the volume f See Also -------- ma_device_get_master_volume() -ma_device_set_master_volume_gain_db() -ma_device_get_master_volume_gain_db() +ma_device_set_master_volume_db() +ma_device_get_master_volume_db() */ MA_API ma_result ma_device_set_master_volume(ma_device* pDevice, float volume); @@ -4097,7 +4532,7 @@ ma_device_get_master_volume_gain_db() ma_device_set_master_volume() ma_device_get_master_volume() */ -MA_API ma_result ma_device_set_master_gain_db(ma_device* pDevice, float gainDB); +MA_API ma_result ma_device_set_master_volume_db(ma_device* pDevice, float gainDB); /* Retrieves the master gain in decibels. @@ -4136,11 +4571,11 @@ If an error occurs, `*pGainDB` will be set to 0. See Also -------- -ma_device_set_master_volume_gain_db() +ma_device_set_master_volume_db() ma_device_set_master_volume() ma_device_get_master_volume() */ -MA_API ma_result ma_device_get_master_gain_db(ma_device* pDevice, float* pGainDB); +MA_API ma_result ma_device_get_master_volume_db(ma_device* pDevice, float* pGainDB); /* @@ -4336,7 +4771,6 @@ MA_API ma_bool32 ma_is_loopback_supported(ma_backend backend); #endif /* MA_NO_DEVICE_IO */ -#ifndef MA_NO_THREADING /* Locks a spinlock. @@ -4354,6 +4788,8 @@ Unlocks a spinlock. MA_API ma_result ma_spinlock_unlock(volatile ma_spinlock* pSpinlock); +#ifndef MA_NO_THREADING + /* Creates a mutex. @@ -4399,19 +4835,91 @@ MA_API ma_result ma_event_signal(ma_event* pEvent); #endif /* MA_NO_THREADING */ +/* +Fence +===== +This locks while the counter is larger than 0. Counter can be incremented and decremented by any +thread, but care needs to be taken when waiting. It is possible for one thread to acquire the +fence just as another thread returns from ma_fence_wait(). + +The idea behind a fence is to allow you to wait for a group of operations to complete. When an +operation starts, the counter is incremented which locks the fence. When the operation completes, +the fence will be released which decrements the counter. ma_fence_wait() will block until the +counter hits zero. + +If threading is disabled, ma_fence_wait() will spin on the counter. +*/ +typedef struct +{ +#ifndef MA_NO_THREADING + ma_event e; +#endif + ma_uint32 counter; +} ma_fence; + +MA_API ma_result ma_fence_init(ma_fence* pFence); +MA_API void ma_fence_uninit(ma_fence* pFence); +MA_API ma_result ma_fence_acquire(ma_fence* pFence); /* Increment counter. */ +MA_API ma_result ma_fence_release(ma_fence* pFence); /* Decrement counter. */ +MA_API ma_result ma_fence_wait(ma_fence* pFence); /* Wait for counter to reach 0. */ + + + +/* +Notification callback for asynchronous operations. +*/ +typedef void ma_async_notification; + +typedef struct +{ + void (* onSignal)(ma_async_notification* pNotification); +} ma_async_notification_callbacks; + +MA_API ma_result ma_async_notification_signal(ma_async_notification* pNotification); + + +/* +Simple polling notification. + +This just sets a variable when the notification has been signalled which is then polled with ma_async_notification_poll_is_signalled() +*/ +typedef struct +{ + ma_async_notification_callbacks cb; + ma_bool32 signalled; +} ma_async_notification_poll; + +MA_API ma_result ma_async_notification_poll_init(ma_async_notification_poll* pNotificationPoll); +MA_API ma_bool32 ma_async_notification_poll_is_signalled(const ma_async_notification_poll* pNotificationPoll); + + +/* +Event Notification + +This uses an ma_event. If threading is disabled (MA_NO_THREADING), initialization will fail. +*/ +typedef struct +{ + ma_async_notification_callbacks cb; +#ifndef MA_NO_THREADING + ma_event e; +#endif +} ma_async_notification_event; + +MA_API ma_result ma_async_notification_event_init(ma_async_notification_event* pNotificationEvent); +MA_API ma_result ma_async_notification_event_uninit(ma_async_notification_event* pNotificationEvent); +MA_API ma_result ma_async_notification_event_wait(ma_async_notification_event* pNotificationEvent); +MA_API ma_result ma_async_notification_event_signal(ma_async_notification_event* pNotificationEvent); + + + + /************************************************************************************************************************************************************ Utiltities ************************************************************************************************************************************************************/ -/* -Adjust buffer size based on a scaling factor. - -This just multiplies the base size by the scaling factor, making sure it's a size of at least 1. -*/ -MA_API ma_uint32 ma_scale_buffer_size(ma_uint32 baseBufferSize, float scale); - /* Calculates a buffer size in milliseconds from the specified number of frames and sample rate. */ @@ -4436,7 +4944,6 @@ For all formats except `ma_format_u8`, the output buffer will be filled with 0. makes more sense for the purpose of mixing to initialize it to the center point. */ MA_API void ma_silence_pcm_frames(void* p, ma_uint64 frameCount, ma_format format, ma_uint32 channels); -static MA_INLINE void ma_zero_pcm_frames(void* p, ma_uint64 frameCount, ma_format format, ma_uint32 channels) { ma_silence_pcm_frames(p, frameCount, format, channels); } /* @@ -4449,10 +4956,14 @@ static MA_INLINE const float* ma_offset_pcm_frames_const_ptr_f32(const float* p, /* -Clips f32 samples. +Clips samples. */ -MA_API void ma_clip_samples_f32(float* p, ma_uint64 sampleCount); -static MA_INLINE void ma_clip_pcm_frames_f32(float* p, ma_uint64 frameCount, ma_uint32 channels) { ma_clip_samples_f32(p, frameCount*channels); } +MA_API void ma_clip_samples_u8(ma_uint8* pDst, const ma_int16* pSrc, ma_uint64 count); +MA_API void ma_clip_samples_s16(ma_int16* pDst, const ma_int32* pSrc, ma_uint64 count); +MA_API void ma_clip_samples_s24(ma_uint8* pDst, const ma_int64* pSrc, ma_uint64 count); +MA_API void ma_clip_samples_s32(ma_int32* pDst, const ma_int64* pSrc, ma_uint64 count); +MA_API void ma_clip_samples_f32(float* pDst, const float* pSrc, ma_uint64 count); +MA_API void ma_clip_pcm_frames(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format format, ma_uint32 channels); /* Helper for applying a volume factor to samples. @@ -4471,11 +4982,11 @@ MA_API void ma_apply_volume_factor_s24(void* pSamples, ma_uint64 sampleCount, fl MA_API void ma_apply_volume_factor_s32(ma_int32* pSamples, ma_uint64 sampleCount, float factor); MA_API void ma_apply_volume_factor_f32(float* pSamples, ma_uint64 sampleCount, float factor); -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_u8(ma_uint8* pPCMFramesOut, const ma_uint8* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s16(ma_int16* pPCMFramesOut, const ma_int16* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s24(void* pPCMFramesOut, const void* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s32(ma_int32* pPCMFramesOut, const ma_int32* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_f32(float* pPCMFramesOut, const float* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_u8(ma_uint8* pFramesOut, const ma_uint8* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s16(ma_int16* pFramesOut, const ma_int16* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s24(void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s32(ma_int32* pFramesOut, const ma_int32* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); MA_API void ma_copy_and_apply_volume_factor_pcm_frames(void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float factor); MA_API void ma_apply_volume_factor_pcm_frames_u8(ma_uint8* pFrames, ma_uint64 frameCount, ma_uint32 channels, float factor); @@ -4485,36 +4996,105 @@ MA_API void ma_apply_volume_factor_pcm_frames_s32(ma_int32* pFrames, ma_uint64 f MA_API void ma_apply_volume_factor_pcm_frames_f32(float* pFrames, ma_uint64 frameCount, ma_uint32 channels, float factor); MA_API void ma_apply_volume_factor_pcm_frames(void* pFrames, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float factor); +MA_API void ma_copy_and_apply_volume_factor_per_channel_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float* pChannelGains); + + +MA_API void ma_copy_and_apply_volume_and_clip_samples_u8(ma_uint8* pDst, const ma_int16* pSrc, ma_uint64 count, float volume); +MA_API void ma_copy_and_apply_volume_and_clip_samples_s16(ma_int16* pDst, const ma_int32* pSrc, ma_uint64 count, float volume); +MA_API void ma_copy_and_apply_volume_and_clip_samples_s24(ma_uint8* pDst, const ma_int64* pSrc, ma_uint64 count, float volume); +MA_API void ma_copy_and_apply_volume_and_clip_samples_s32(ma_int32* pDst, const ma_int64* pSrc, ma_uint64 count, float volume); +MA_API void ma_copy_and_apply_volume_and_clip_samples_f32(float* pDst, const float* pSrc, ma_uint64 count, float volume); +MA_API void ma_copy_and_apply_volume_and_clip_pcm_frames(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float volume); + /* Helper for converting a linear factor to gain in decibels. */ -MA_API float ma_factor_to_gain_db(float factor); +MA_API float ma_volume_linear_to_db(float factor); /* Helper for converting gain in decibels to a linear factor. */ -MA_API float ma_gain_db_to_factor(float gain); +MA_API float ma_volume_db_to_linear(float gain); + +/* +Slot Allocator +-------------- +The idea of the slot allocator is for it to be used in conjunction with a fixed sized buffer. You use the slot allocator to allocator an index that can be used +as the insertion point for an object. + +Slots are reference counted to help mitigate the ABA problem in the lock-free queue we use for tracking jobs. + +The slot index is stored in the low 32 bits. The reference counter is stored in the high 32 bits: + + +-----------------+-----------------+ + | 32 Bits | 32 Bits | + +-----------------+-----------------+ + | Reference Count | Slot Index | + +-----------------+-----------------+ +*/ +typedef struct +{ + ma_uint32 capacity; /* The number of slots to make available. */ +} ma_slot_allocator_config; + +MA_API ma_slot_allocator_config ma_slot_allocator_config_init(ma_uint32 capacity); + + +typedef struct +{ + MA_ATOMIC(4, ma_uint32) bitfield; /* Must be used atomically because the allocation and freeing routines need to make copies of this which must never be optimized away by the compiler. */ +} ma_slot_allocator_group; + +typedef struct +{ + ma_slot_allocator_group* pGroups; /* Slots are grouped in chunks of 32. */ + ma_uint32* pSlots; /* 32 bits for reference counting for ABA mitigation. */ + ma_uint32 count; /* Allocation count. */ + ma_uint32 capacity; + + /* Memory management. */ + ma_bool32 _ownsHeap; + void* _pHeap; +} ma_slot_allocator; + +MA_API ma_result ma_slot_allocator_get_heap_size(const ma_slot_allocator_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_slot_allocator_init_preallocated(const ma_slot_allocator_config* pConfig, void* pHeap, ma_slot_allocator* pAllocator); +MA_API ma_result ma_slot_allocator_init(const ma_slot_allocator_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_slot_allocator* pAllocator); +MA_API void ma_slot_allocator_uninit(ma_slot_allocator* pAllocator, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_slot_allocator_alloc(ma_slot_allocator* pAllocator, ma_uint64* pSlot); +MA_API ma_result ma_slot_allocator_free(ma_slot_allocator* pAllocator, ma_uint64 slot); + + + + +/************************************************************************************************** + +Data Source + +**************************************************************************************************/ typedef void ma_data_source; +#define MA_DATA_SOURCE_SELF_MANAGED_RANGE_AND_LOOP_POINT 0x00000001 + typedef struct { ma_result (* onRead)(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); ma_result (* onSeek)(ma_data_source* pDataSource, ma_uint64 frameIndex); - ma_result (* onMap)(ma_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount); /* Returns MA_AT_END if the end has been reached. This should be considered successful. */ - ma_result (* onUnmap)(ma_data_source* pDataSource, ma_uint64 frameCount); - ma_result (* onGetDataFormat)(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate); + ma_result (* onGetDataFormat)(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); ma_result (* onGetCursor)(ma_data_source* pDataSource, ma_uint64* pCursor); ma_result (* onGetLength)(ma_data_source* pDataSource, ma_uint64* pLength); -} ma_data_source_vtable, ma_data_source_callbacks; /* TODO: Remove ma_data_source_callbacks in version 0.11. */ + ma_result (* onSetLooping)(ma_data_source* pDataSource, ma_bool32 isLooping); + ma_uint32 flags; +} ma_data_source_vtable; typedef ma_data_source* (* ma_data_source_get_next_proc)(ma_data_source* pDataSource); typedef struct { - const ma_data_source_vtable* vtable; /* Can be null, which is useful for proxies. */ + const ma_data_source_vtable* vtable; } ma_data_source_config; MA_API ma_data_source_config ma_data_source_config_init(void); @@ -4522,9 +5102,6 @@ MA_API ma_data_source_config ma_data_source_config_init(void); typedef struct { - ma_data_source_callbacks cb; /* TODO: Remove this. */ - - /* Variables below are placeholder and not yet used. */ const ma_data_source_vtable* vtable; ma_uint64 rangeBegInFrames; ma_uint64 rangeEndInFrames; /* Set to -1 for unranged (default). */ @@ -4533,19 +5110,19 @@ typedef struct ma_data_source* pCurrent; /* When non-NULL, the data source being initialized will act as a proxy and will route all operations to pCurrent. Used in conjunction with pNext/onGetNext for seamless chaining. */ ma_data_source* pNext; /* When set to NULL, onGetNext will be used. */ ma_data_source_get_next_proc onGetNext; /* Will be used when pNext is NULL. If both are NULL, no next will be used. */ + MA_ATOMIC(4, ma_bool32) isLooping; } ma_data_source_base; MA_API ma_result ma_data_source_init(const ma_data_source_config* pConfig, ma_data_source* pDataSource); MA_API void ma_data_source_uninit(ma_data_source* pDataSource); -MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead, ma_bool32 loop); /* Must support pFramesOut = NULL in which case a forward seek should be performed. */ -MA_API ma_result ma_data_source_seek_pcm_frames(ma_data_source* pDataSource, ma_uint64 frameCount, ma_uint64* pFramesSeeked, ma_bool32 loop); /* Can only seek forward. Equivalent to ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount); */ +MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); /* Must support pFramesOut = NULL in which case a forward seek should be performed. */ +MA_API ma_result ma_data_source_seek_pcm_frames(ma_data_source* pDataSource, ma_uint64 frameCount, ma_uint64* pFramesSeeked); /* Can only seek forward. Equivalent to ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount, &framesRead); */ MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex); -MA_API ma_result ma_data_source_map(ma_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount); /* Returns MA_NOT_IMPLEMENTED if mapping is not supported. */ -MA_API ma_result ma_data_source_unmap(ma_data_source* pDataSource, ma_uint64 frameCount); /* Returns MA_AT_END if the end has been reached. */ -MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate); +MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor); MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength); /* Returns MA_NOT_IMPLEMENTED if the length is unknown or cannot be determined. Decoders can return this. */ -#if defined(MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING) +MA_API ma_result ma_data_source_set_looping(ma_data_source* pDataSource, ma_bool32 isLooping); +MA_API ma_bool32 ma_data_source_is_looping(ma_data_source* pDataSource); MA_API ma_result ma_data_source_set_range_in_pcm_frames(ma_data_source* pDataSource, ma_uint64 rangeBegInFrames, ma_uint64 rangeEndInFrames); MA_API void ma_data_source_get_range_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pRangeBegInFrames, ma_uint64* pRangeEndInFrames); MA_API ma_result ma_data_source_set_loop_point_in_pcm_frames(ma_data_source* pDataSource, ma_uint64 loopBegInFrames, ma_uint64 loopEndInFrames); @@ -4556,7 +5133,6 @@ MA_API ma_result ma_data_source_set_next(ma_data_source* pDataSource, ma_data_so MA_API ma_data_source* ma_data_source_get_next(ma_data_source* pDataSource); MA_API ma_result ma_data_source_set_next_callback(ma_data_source* pDataSource, ma_data_source_get_next_proc onGetNext); MA_API ma_data_source_get_next_proc ma_data_source_get_next_callback(ma_data_source* pDataSource); -#endif typedef struct @@ -4617,6 +5193,69 @@ MA_API ma_result ma_audio_buffer_get_length_in_pcm_frames(const ma_audio_buffer* MA_API ma_result ma_audio_buffer_get_available_frames(const ma_audio_buffer* pAudioBuffer, ma_uint64* pAvailableFrames); +/* +Paged Audio Buffer +================== +A paged audio buffer is made up of a linked list of pages. It's expandable, but not shrinkable. It +can be used for cases where audio data is streamed in asynchronously while allowing data to be read +at the same time. + +This is lock-free, but not 100% thread safe. You can append a page and read from the buffer across +simultaneously across different threads, however only one thread at a time can append, and only one +thread at a time can read and seek. +*/ +typedef struct ma_paged_audio_buffer_page ma_paged_audio_buffer_page; +struct ma_paged_audio_buffer_page +{ + MA_ATOMIC(MA_SIZEOF_PTR, ma_paged_audio_buffer_page*) pNext; + ma_uint64 sizeInFrames; + ma_uint8 pAudioData[1]; +}; + +typedef struct +{ + ma_format format; + ma_uint32 channels; + ma_paged_audio_buffer_page head; /* Dummy head for the lock-free algorithm. Always has a size of 0. */ + MA_ATOMIC(MA_SIZEOF_PTR, ma_paged_audio_buffer_page*) pTail; /* Never null. Initially set to &head. */ +} ma_paged_audio_buffer_data; + +MA_API ma_result ma_paged_audio_buffer_data_init(ma_format format, ma_uint32 channels, ma_paged_audio_buffer_data* pData); +MA_API void ma_paged_audio_buffer_data_uninit(ma_paged_audio_buffer_data* pData, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_paged_audio_buffer_page* ma_paged_audio_buffer_data_get_head(ma_paged_audio_buffer_data* pData); +MA_API ma_paged_audio_buffer_page* ma_paged_audio_buffer_data_get_tail(ma_paged_audio_buffer_data* pData); +MA_API ma_result ma_paged_audio_buffer_data_get_length_in_pcm_frames(ma_paged_audio_buffer_data* pData, ma_uint64* pLength); +MA_API ma_result ma_paged_audio_buffer_data_allocate_page(ma_paged_audio_buffer_data* pData, ma_uint64 pageSizeInFrames, const void* pInitialData, const ma_allocation_callbacks* pAllocationCallbacks, ma_paged_audio_buffer_page** ppPage); +MA_API ma_result ma_paged_audio_buffer_data_free_page(ma_paged_audio_buffer_data* pData, ma_paged_audio_buffer_page* pPage, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_paged_audio_buffer_data_append_page(ma_paged_audio_buffer_data* pData, ma_paged_audio_buffer_page* pPage); +MA_API ma_result ma_paged_audio_buffer_data_allocate_and_append_page(ma_paged_audio_buffer_data* pData, ma_uint32 pageSizeInFrames, const void* pInitialData, const ma_allocation_callbacks* pAllocationCallbacks); + + +typedef struct +{ + ma_paged_audio_buffer_data* pData; /* Must not be null. */ +} ma_paged_audio_buffer_config; + +MA_API ma_paged_audio_buffer_config ma_paged_audio_buffer_config_init(ma_paged_audio_buffer_data* pData); + + +typedef struct +{ + ma_data_source_base ds; + ma_paged_audio_buffer_data* pData; /* Audio data is read from here. Cannot be null. */ + ma_paged_audio_buffer_page* pCurrent; + ma_uint64 relativeCursor; /* Relative to the current page. */ + ma_uint64 absoluteCursor; +} ma_paged_audio_buffer; + +MA_API ma_result ma_paged_audio_buffer_init(const ma_paged_audio_buffer_config* pConfig, ma_paged_audio_buffer* pPagedAudioBuffer); +MA_API void ma_paged_audio_buffer_uninit(ma_paged_audio_buffer* pPagedAudioBuffer); +MA_API ma_result ma_paged_audio_buffer_read_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); /* Returns MA_AT_END if no more pages available. */ +MA_API ma_result ma_paged_audio_buffer_seek_to_pcm_frame(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64 frameIndex); +MA_API ma_result ma_paged_audio_buffer_get_cursor_in_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64* pCursor); +MA_API ma_result ma_paged_audio_buffer_get_length_in_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64* pLength); + + /************************************************************************************************************************************************************ @@ -4684,11 +5323,6 @@ typedef ma_result (* ma_tell_proc)(void* pUserData, ma_int64* pCursor); #if !defined(MA_NO_DECODING) || !defined(MA_NO_ENCODING) -typedef enum -{ - ma_resource_format_wav -} ma_resource_format; - typedef enum { ma_encoding_format_unknown = 0, @@ -4715,25 +5349,24 @@ typedef struct ma_decoder ma_decoder; typedef struct { ma_format preferredFormat; + ma_uint32 seekPointCount; /* Set to > 0 to generate a seektable if the decoding backend supports it. */ } ma_decoding_backend_config; -MA_API ma_decoding_backend_config ma_decoding_backend_config_init(ma_format preferredFormat); +MA_API ma_decoding_backend_config ma_decoding_backend_config_init(ma_format preferredFormat, ma_uint32 seekPointCount); typedef struct { - ma_result (* onInit )(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); - ma_result (* onInitFile )(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ - ma_result (* onInitFileW )(void* pUserData, const wchar_t* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ - ma_result (* onInitMemory )(void* pUserData, const void* pData, size_t dataSize, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ - void (* onUninit )(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks); - ma_result (* onGetChannelMap)(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap); + ma_result (* onInit )(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); + ma_result (* onInitFile )(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ + ma_result (* onInitFileW )(void* pUserData, const wchar_t* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ + ma_result (* onInitMemory)(void* pUserData, const void* pData, size_t dataSize, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ + void (* onUninit )(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks); } ma_decoding_backend_vtable; -/* TODO: Convert read and seek to be consistent with the VFS API (ma_result return value, bytes read moved to an output parameter). */ -typedef size_t (* ma_decoder_read_proc)(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead); /* Returns the number of bytes read. */ -typedef ma_bool32 (* ma_decoder_seek_proc)(ma_decoder* pDecoder, ma_int64 byteOffset, ma_seek_origin origin); +typedef ma_result (* ma_decoder_read_proc)(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead, size_t* pBytesRead); /* Returns the number of bytes read. */ +typedef ma_result (* ma_decoder_seek_proc)(ma_decoder* pDecoder, ma_int64 byteOffset, ma_seek_origin origin); typedef ma_result (* ma_decoder_tell_proc)(ma_decoder* pDecoder, ma_int64* pCursor); typedef struct @@ -4741,23 +5374,13 @@ typedef struct ma_format format; /* Set to 0 or ma_format_unknown to use the stream's internal format. */ ma_uint32 channels; /* Set to 0 to use the stream's internal channels. */ ma_uint32 sampleRate; /* Set to 0 to use the stream's internal sample rate. */ - ma_channel channelMap[MA_MAX_CHANNELS]; + ma_channel* pChannelMap; ma_channel_mix_mode channelMixMode; ma_dither_mode ditherMode; - struct - { - ma_resample_algorithm algorithm; - struct - { - ma_uint32 lpfOrder; - } linear; - struct - { - int quality; - } speex; - } resampling; + ma_resampler_config resampling; ma_allocation_callbacks allocationCallbacks; ma_encoding_format encodingFormat; + ma_uint32 seekPointCount; /* When set to > 0, specifies the number of seek points to use for the generation of a seek table. Not all decoding backends support this. */ ma_decoding_backend_vtable** ppCustomBackendVTables; ma_uint32 customBackendCount; void* pCustomBackendUserData; @@ -4777,8 +5400,11 @@ struct ma_decoder ma_format outputFormat; ma_uint32 outputChannels; ma_uint32 outputSampleRate; - ma_channel outputChannelMap[MA_MAX_CHANNELS]; - ma_data_converter converter; /* <-- Data conversion is achieved by running frames through this. */ + ma_data_converter converter; /* Data conversion is achieved by running frames through this. */ + void* pInputCache; /* In input format. Can be null if it's not needed. */ + ma_uint64 inputCacheCap; /* The capacity of the input cache. */ + ma_uint64 inputCacheConsumed; /* The number of frames that have been consumed in the cache. Used for determining the next valid frame. */ + ma_uint64 inputCacheRemaining; /* The number of valid frames remaining in the cahce. */ ma_allocation_callbacks allocationCallbacks; union { @@ -4811,6 +5437,25 @@ Uninitializes a decoder. */ MA_API ma_result ma_decoder_uninit(ma_decoder* pDecoder); +/* +Reads PCM frames from the given decoder. + +This is not thread safe without your own synchronization. +*/ +MA_API ma_result ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); + +/* +Seeks to a PCM frame based on it's absolute index. + +This is not thread safe without your own synchronization. +*/ +MA_API ma_result ma_decoder_seek_to_pcm_frame(ma_decoder* pDecoder, ma_uint64 frameIndex); + +/* +Retrieves the decoder's output data format. +*/ +MA_API ma_result ma_decoder_get_data_format(ma_decoder* pDecoder, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); + /* Retrieves the current position of the read cursor in PCM frames. */ @@ -4830,21 +5475,7 @@ For MP3's, this will decode the entire file. Do not call this in time critical s This function is not thread safe without your own synchronization. */ -MA_API ma_uint64 ma_decoder_get_length_in_pcm_frames(ma_decoder* pDecoder); - -/* -Reads PCM frames from the given decoder. - -This is not thread safe without your own synchronization. -*/ -MA_API ma_uint64 ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount); - -/* -Seeks to a PCM frame based on it's absolute index. - -This is not thread safe without your own synchronization. -*/ -MA_API ma_result ma_decoder_seek_to_pcm_frame(ma_decoder* pDecoder, ma_uint64 frameIndex); +MA_API ma_result ma_decoder_get_length_in_pcm_frames(ma_decoder* pDecoder, ma_uint64* pLength); /* Retrieves the number of frames that can be read before reaching the end. @@ -4865,43 +5496,6 @@ MA_API ma_result ma_decode_from_vfs(ma_vfs* pVFS, const char* pFilePath, ma_deco MA_API ma_result ma_decode_file(const char* pFilePath, ma_decoder_config* pConfig, ma_uint64* pFrameCountOut, void** ppPCMFramesOut); MA_API ma_result ma_decode_memory(const void* pData, size_t dataSize, ma_decoder_config* pConfig, ma_uint64* pFrameCountOut, void** ppPCMFramesOut); - - - -/* -DEPRECATED - -Set the "encodingFormat" variable in the decoder config instead: - - decoderConfig.encodingFormat = ma_encoding_format_wav; - -These functions will be removed in version 0.11. -*/ -MA_API ma_result ma_decoder_init_wav(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_flac(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_mp3(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vorbis(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_memory_wav(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_memory_flac(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_memory_mp3(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_memory_vorbis(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_wav(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_flac(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_mp3(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_vorbis(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_wav_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_flac_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_mp3_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_vorbis_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_wav(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_flac(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_mp3(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_vorbis(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_wav_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_flac_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_mp3_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_vorbis_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); - #endif /* MA_NO_DECODING */ @@ -4920,18 +5514,18 @@ typedef size_t (* ma_encoder_write_proc) (ma_encoder* pEncoder, con typedef ma_bool32 (* ma_encoder_seek_proc) (ma_encoder* pEncoder, int byteOffset, ma_seek_origin origin); typedef ma_result (* ma_encoder_init_proc) (ma_encoder* pEncoder); typedef void (* ma_encoder_uninit_proc) (ma_encoder* pEncoder); -typedef ma_uint64 (* ma_encoder_write_pcm_frames_proc)(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount); +typedef ma_result (* ma_encoder_write_pcm_frames_proc)(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount, ma_uint64* pFramesWritten); typedef struct { - ma_resource_format resourceFormat; + ma_encoding_format encodingFormat; ma_format format; ma_uint32 channels; ma_uint32 sampleRate; ma_allocation_callbacks allocationCallbacks; } ma_encoder_config; -MA_API ma_encoder_config ma_encoder_config_init(ma_resource_format resourceFormat, ma_format format, ma_uint32 channels, ma_uint32 sampleRate); +MA_API ma_encoder_config ma_encoder_config_init(ma_encoding_format encodingFormat, ma_format format, ma_uint32 channels, ma_uint32 sampleRate); struct ma_encoder { @@ -4950,7 +5544,7 @@ MA_API ma_result ma_encoder_init(ma_encoder_write_proc onWrite, ma_encoder_seek_ MA_API ma_result ma_encoder_init_file(const char* pFilePath, const ma_encoder_config* pConfig, ma_encoder* pEncoder); MA_API ma_result ma_encoder_init_file_w(const wchar_t* pFilePath, const ma_encoder_config* pConfig, ma_encoder* pEncoder); MA_API void ma_encoder_uninit(ma_encoder* pEncoder); -MA_API ma_uint64 ma_encoder_write_pcm_frames(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount); +MA_API ma_result ma_encoder_write_pcm_frames(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount, ma_uint64* pFramesWritten); #endif /* MA_NO_ENCODING */ @@ -4991,7 +5585,7 @@ typedef struct MA_API ma_result ma_waveform_init(const ma_waveform_config* pConfig, ma_waveform* pWaveform); MA_API void ma_waveform_uninit(ma_waveform* pWaveform); -MA_API ma_uint64 ma_waveform_read_pcm_frames(ma_waveform* pWaveform, void* pFramesOut, ma_uint64 frameCount); +MA_API ma_result ma_waveform_read_pcm_frames(ma_waveform* pWaveform, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); MA_API ma_result ma_waveform_seek_to_pcm_frame(ma_waveform* pWaveform, ma_uint64 frameIndex); MA_API ma_result ma_waveform_set_amplitude(ma_waveform* pWaveform, double amplitude); MA_API ma_result ma_waveform_set_frequency(ma_waveform* pWaveform, double frequency); @@ -5005,6 +5599,7 @@ typedef enum ma_noise_type_brownian } ma_noise_type; + typedef struct { ma_format format; @@ -5026,26 +5621,1281 @@ typedef struct { struct { - double bin[MA_MAX_CHANNELS][16]; - double accumulation[MA_MAX_CHANNELS]; - ma_uint32 counter[MA_MAX_CHANNELS]; + double** bin; + double* accumulation; + ma_uint32* counter; } pink; struct { - double accumulation[MA_MAX_CHANNELS]; + double* accumulation; } brownian; } state; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_noise; -MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, ma_noise* pNoise); -MA_API void ma_noise_uninit(ma_noise* pNoise); -MA_API ma_uint64 ma_noise_read_pcm_frames(ma_noise* pNoise, void* pFramesOut, ma_uint64 frameCount); +MA_API ma_result ma_noise_get_heap_size(const ma_noise_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_noise_init_preallocated(const ma_noise_config* pConfig, void* pHeap, ma_noise* pNoise); +MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_noise* pNoise); +MA_API void ma_noise_uninit(ma_noise* pNoise, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_noise_read_pcm_frames(ma_noise* pNoise, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); MA_API ma_result ma_noise_set_amplitude(ma_noise* pNoise, double amplitude); MA_API ma_result ma_noise_set_seed(ma_noise* pNoise, ma_int32 seed); MA_API ma_result ma_noise_set_type(ma_noise* pNoise, ma_noise_type type); #endif /* MA_NO_GENERATION */ + + +/************************************************************************************************************************************************************ + +Resource Manager + +************************************************************************************************************************************************************/ +/* The resource manager cannot be enabled if there is no decoder. */ +#if !defined(MA_NO_RESOURCE_MANAGER) && defined(MA_NO_DECODING) +#define MA_NO_RESOURCE_MANAGER +#endif + +#ifndef MA_NO_RESOURCE_MANAGER +typedef struct ma_resource_manager ma_resource_manager; +typedef struct ma_resource_manager_data_buffer_node ma_resource_manager_data_buffer_node; +typedef struct ma_resource_manager_data_buffer ma_resource_manager_data_buffer; +typedef struct ma_resource_manager_data_stream ma_resource_manager_data_stream; +typedef struct ma_resource_manager_data_source ma_resource_manager_data_source; + +#define MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM 0x00000001 /* When set, does not load the entire data source in memory. Disk I/O will happen on job threads. */ +#define MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE 0x00000002 /* Decode data before storing in memory. When set, decoding is done at the resource manager level rather than the mixing thread. Results in faster mixing, but higher memory usage. */ +#define MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC 0x00000004 /* When set, the resource manager will load the data source asynchronously. */ +#define MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT 0x00000008 /* When set, waits for initialization of the underlying data source before returning from ma_resource_manager_data_source_init(). */ + +#define MA_RESOURCE_MANAGER_JOB_QUIT 0x00000000 +#define MA_RESOURCE_MANAGER_JOB_LOAD_DATA_BUFFER_NODE 0x00000001 +#define MA_RESOURCE_MANAGER_JOB_FREE_DATA_BUFFER_NODE 0x00000002 +#define MA_RESOURCE_MANAGER_JOB_PAGE_DATA_BUFFER_NODE 0x00000003 +#define MA_RESOURCE_MANAGER_JOB_LOAD_DATA_BUFFER 0x00000004 +#define MA_RESOURCE_MANAGER_JOB_FREE_DATA_BUFFER 0x00000005 +#define MA_RESOURCE_MANAGER_JOB_LOAD_DATA_STREAM 0x00000006 +#define MA_RESOURCE_MANAGER_JOB_FREE_DATA_STREAM 0x00000007 +#define MA_RESOURCE_MANAGER_JOB_PAGE_DATA_STREAM 0x00000008 +#define MA_RESOURCE_MANAGER_JOB_SEEK_DATA_STREAM 0x00000009 +#define MA_RESOURCE_MANAGER_JOB_CUSTOM 0x00000100 /* Number your custom job codes as (MA_RESOURCE_MANAGER_JOB_CUSTOM + 0), (MA_RESOURCE_MANAGER_JOB_CUSTOM + 1), etc. */ + + +/* +Pipeline notifications used by the resource manager. Made up of both an async notification and a fence, both of which are optional. +*/ +typedef struct +{ + ma_async_notification* pNotification; + ma_fence* pFence; +} ma_resource_manager_pipeline_stage_notification; + +typedef struct +{ + ma_resource_manager_pipeline_stage_notification init; /* Initialization of the decoder. */ + ma_resource_manager_pipeline_stage_notification done; /* Decoding fully completed. */ +} ma_resource_manager_pipeline_notifications; + +MA_API ma_resource_manager_pipeline_notifications ma_resource_manager_pipeline_notifications_init(void); + + +typedef struct +{ + union + { + struct + { + ma_uint16 code; + ma_uint16 slot; + ma_uint32 refcount; + } breakup; + ma_uint64 allocation; + } toc; /* 8 bytes. We encode the job code into the slot allocation data to save space. */ + MA_ATOMIC(8, ma_uint64) next; /* refcount + slot for the next item. Does not include the job code. */ + ma_uint32 order; /* Execution order. Used to create a data dependency and ensure a job is executed in order. Usage is contextual depending on the job type. */ + + union + { + /* Resource Managemer Jobs */ + struct + { + ma_resource_manager_data_buffer_node* pDataBufferNode; + char* pFilePath; + wchar_t* pFilePathW; + ma_bool32 decode; /* When set to true, the data buffer will be decoded. Otherwise it'll be encoded and will use a decoder for the connector. */ + ma_async_notification* pInitNotification; /* Signalled when the data buffer has been initialized and the format/channels/rate can be retrieved. */ + ma_async_notification* pDoneNotification; /* Signalled when the data buffer has been fully decoded. Will be passed through to MA_RESOURCE_MANAGER_JOB_PAGE_DATA_BUFFER_NODE when decoding. */ + ma_fence* pInitFence; /* Released when initialization of the decoder is complete. */ + ma_fence* pDoneFence; /* Released if initialization of the decoder fails. Passed through to PAGE_DATA_BUFFER_NODE untouched if init is successful. */ + } loadDataBufferNode; + struct + { + ma_resource_manager_data_buffer_node* pDataBufferNode; + ma_async_notification* pDoneNotification; + ma_fence* pDoneFence; + } freeDataBufferNode; + struct + { + ma_resource_manager_data_buffer_node* pDataBufferNode; + ma_decoder* pDecoder; + ma_async_notification* pDoneNotification; /* Signalled when the data buffer has been fully decoded. */ + ma_fence* pDoneFence; /* Passed through from LOAD_DATA_BUFFER_NODE and released when the data buffer completes decoding or an error occurs. */ + } pageDataBufferNode; + + struct + { + ma_resource_manager_data_buffer* pDataBuffer; + ma_async_notification* pInitNotification; /* Signalled when the data buffer has been initialized and the format/channels/rate can be retrieved. */ + ma_async_notification* pDoneNotification; /* Signalled when the data buffer has been fully decoded. */ + ma_fence* pInitFence; /* Released when the data buffer has been initialized and the format/channels/rate can be retrieved. */ + ma_fence* pDoneFence; /* Released when the data buffer has been fully decoded. */ + } loadDataBuffer; + struct + { + ma_resource_manager_data_buffer* pDataBuffer; + ma_async_notification* pDoneNotification; + ma_fence* pDoneFence; + } freeDataBuffer; + + struct + { + ma_resource_manager_data_stream* pDataStream; + char* pFilePath; /* Allocated when the job is posted, freed by the job thread after loading. */ + wchar_t* pFilePathW; /* ^ As above ^. Only used if pFilePath is NULL. */ + ma_uint64 initialSeekPoint; + ma_async_notification* pInitNotification; /* Signalled after the first two pages have been decoded and frames can be read from the stream. */ + ma_fence* pInitFence; + } loadDataStream; + struct + { + ma_resource_manager_data_stream* pDataStream; + ma_async_notification* pDoneNotification; + ma_fence* pDoneFence; + } freeDataStream; + struct + { + ma_resource_manager_data_stream* pDataStream; + ma_uint32 pageIndex; /* The index of the page to decode into. */ + } pageDataStream; + struct + { + ma_resource_manager_data_stream* pDataStream; + ma_uint64 frameIndex; + } seekDataStream; + + /* Others. */ + struct + { + ma_uintptr data0; + ma_uintptr data1; + } custom; + } data; +} ma_resource_manager_job; + +MA_API ma_resource_manager_job ma_resource_manager_job_init(ma_uint16 code); + + +/* +When set, ma_resource_manager_job_queue_next() will not wait and no semaphore will be signaled in +ma_resource_manager_job_queue_post(). ma_resource_manager_job_queue_next() will return MA_NO_DATA_AVAILABLE if nothing is available. + +This flag should always be used for platforms that do not support multithreading. +*/ +#define MA_RESOURCE_MANAGER_JOB_QUEUE_FLAG_NON_BLOCKING 0x00000001 + +typedef struct +{ + ma_uint32 flags; + ma_uint32 capacity; /* The maximum number of jobs that can fit in the queue at a time. */ +} ma_resource_manager_job_queue_config; + +MA_API ma_resource_manager_job_queue_config ma_resource_manager_job_queue_config_init(ma_uint32 flags, ma_uint32 capacity); + + +typedef struct +{ + ma_uint32 flags; /* Flags passed in at initialization time. */ + ma_uint32 capacity; /* The maximum number of jobs that can fit in the queue at a time. Set by the config. */ + MA_ATOMIC(8, ma_uint64) head; /* The first item in the list. Required for removing from the top of the list. */ + MA_ATOMIC(8, ma_uint64) tail; /* The last item in the list. Required for appending to the end of the list. */ +#ifndef MA_NO_THREADING + ma_semaphore sem; /* Only used when MA_RESOURCE_MANAGER_JOB_QUEUE_FLAG_NON_BLOCKING is unset. */ +#endif + ma_slot_allocator allocator; + ma_resource_manager_job* pJobs; +#ifndef MA_USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE + ma_spinlock lock; +#endif + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; +} ma_resource_manager_job_queue; + +MA_API ma_result ma_resource_manager_job_queue_get_heap_size(const ma_resource_manager_job_queue_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_resource_manager_job_queue_init_preallocated(const ma_resource_manager_job_queue_config* pConfig, void* pHeap, ma_resource_manager_job_queue* pQueue); +MA_API ma_result ma_resource_manager_job_queue_init(const ma_resource_manager_job_queue_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_resource_manager_job_queue* pQueue); +MA_API void ma_resource_manager_job_queue_uninit(ma_resource_manager_job_queue* pQueue, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_resource_manager_job_queue_post(ma_resource_manager_job_queue* pQueue, const ma_resource_manager_job* pJob); +MA_API ma_result ma_resource_manager_job_queue_next(ma_resource_manager_job_queue* pQueue, ma_resource_manager_job* pJob); /* Returns MA_CANCELLED if the next job is a quit job. */ + + +/* Maximum job thread count will be restricted to this, but this may be removed later and replaced with a heap allocation thereby removing any limitation. */ +#ifndef MA_RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT +#define MA_RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT 64 +#endif + +/* Indicates ma_resource_manager_next_job() should not block. Only valid when the job thread count is 0. */ +#define MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING 0x00000001 + +/* Disables any kind of multithreading. Implicitly enables MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING. */ +#define MA_RESOURCE_MANAGER_FLAG_NO_THREADING 0x00000002 + + +typedef struct +{ + const char* pFilePath; + const wchar_t* pFilePathW; + const ma_resource_manager_pipeline_notifications* pNotifications; + ma_uint64 initialSeekPointInPCMFrames; + ma_uint64 rangeBegInPCMFrames; + ma_uint64 rangeEndInPCMFrames; + ma_uint64 loopPointBegInPCMFrames; + ma_uint64 loopPointEndInPCMFrames; + ma_bool32 isLooping; + ma_uint32 flags; +} ma_resource_manager_data_source_config; + +MA_API ma_resource_manager_data_source_config ma_resource_manager_data_source_config_init(); + + +typedef enum +{ + ma_resource_manager_data_supply_type_unknown = 0, /* Used for determining whether or the data supply has been initialized. */ + ma_resource_manager_data_supply_type_encoded, /* Data supply is an encoded buffer. Connector is ma_decoder. */ + ma_resource_manager_data_supply_type_decoded, /* Data supply is a decoded buffer. Connector is ma_audio_buffer. */ + ma_resource_manager_data_supply_type_decoded_paged /* Data supply is a linked list of decoded buffers. Connector is ma_paged_audio_buffer. */ +} ma_resource_manager_data_supply_type; + +typedef struct +{ + MA_ATOMIC(4, ma_resource_manager_data_supply_type) type; /* Read and written from different threads so needs to be accessed atomically. */ + union + { + struct + { + const void* pData; + size_t sizeInBytes; + } encoded; + struct + { + const void* pData; + ma_uint64 totalFrameCount; + ma_uint64 decodedFrameCount; + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + } decoded; + struct + { + ma_paged_audio_buffer_data data; + ma_uint64 decodedFrameCount; + ma_uint32 sampleRate; + } decodedPaged; + } backend; +} ma_resource_manager_data_supply; + +struct ma_resource_manager_data_buffer_node +{ + ma_uint32 hashedName32; /* The hashed name. This is the key. */ + ma_uint32 refCount; + MA_ATOMIC(4, ma_result) result; /* Result from asynchronous loading. When loading set to MA_BUSY. When fully loaded set to MA_SUCCESS. When deleting set to MA_UNAVAILABLE. */ + MA_ATOMIC(4, ma_uint32) executionCounter; /* For allocating execution orders for jobs. */ + MA_ATOMIC(4, ma_uint32) executionPointer; /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ + ma_bool32 isDataOwnedByResourceManager; /* Set to true when the underlying data buffer was allocated the resource manager. Set to false if it is owned by the application (via ma_resource_manager_register_*()). */ + ma_resource_manager_data_supply data; + ma_resource_manager_data_buffer_node* pParent; + ma_resource_manager_data_buffer_node* pChildLo; + ma_resource_manager_data_buffer_node* pChildHi; +}; + +struct ma_resource_manager_data_buffer +{ + ma_data_source_base ds; /* Base data source. A data buffer is a data source. */ + ma_resource_manager* pResourceManager; /* A pointer to the resource manager that owns this buffer. */ + ma_resource_manager_data_buffer_node* pNode; /* The data node. This is reference counted and is what supplies the data. */ + ma_uint32 flags; /* The flags that were passed used to initialize the buffer. */ + MA_ATOMIC(4, ma_uint32) executionCounter; /* For allocating execution orders for jobs. */ + MA_ATOMIC(4, ma_uint32) executionPointer; /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ + ma_uint64 seekTargetInPCMFrames; /* Only updated by the public API. Never written nor read from the job thread. */ + ma_bool32 seekToCursorOnNextRead; /* On the next read we need to seek to the frame cursor. */ + MA_ATOMIC(4, ma_result) result; /* Keeps track of a result of decoding. Set to MA_BUSY while the buffer is still loading. Set to MA_SUCCESS when loading is finished successfully. Otherwise set to some other code. */ + MA_ATOMIC(4, ma_bool32) isLooping; /* Can be read and written by different threads at the same time. Must be used atomically. */ + ma_bool32 isConnectorInitialized; /* Used for asynchronous loading to ensure we don't try to initialize the connector multiple times while waiting for the node to fully load. */ + union + { + ma_decoder decoder; /* Supply type is ma_resource_manager_data_supply_type_encoded */ + ma_audio_buffer buffer; /* Supply type is ma_resource_manager_data_supply_type_decoded */ + ma_paged_audio_buffer pagedBuffer; /* Supply type is ma_resource_manager_data_supply_type_decoded_paged */ + } connector; /* Connects this object to the node's data supply. */ +}; + +struct ma_resource_manager_data_stream +{ + ma_data_source_base ds; /* Base data source. A data stream is a data source. */ + ma_resource_manager* pResourceManager; /* A pointer to the resource manager that owns this data stream. */ + ma_uint32 flags; /* The flags that were passed used to initialize the stream. */ + ma_decoder decoder; /* Used for filling pages with data. This is only ever accessed by the job thread. The public API should never touch this. */ + ma_bool32 isDecoderInitialized; /* Required for determining whether or not the decoder should be uninitialized in MA_RESOURCE_MANAGER_JOB_FREE_DATA_STREAM. */ + ma_uint64 totalLengthInPCMFrames; /* This is calculated when first loaded by the MA_RESOURCE_MANAGER_JOB_LOAD_DATA_STREAM. */ + ma_uint32 relativeCursor; /* The playback cursor, relative to the current page. Only ever accessed by the public API. Never accessed by the job thread. */ + MA_ATOMIC(8, ma_uint64) absoluteCursor; /* The playback cursor, in absolute position starting from the start of the file. */ + ma_uint32 currentPageIndex; /* Toggles between 0 and 1. Index 0 is the first half of pPageData. Index 1 is the second half. Only ever accessed by the public API. Never accessed by the job thread. */ + MA_ATOMIC(4, ma_uint32) executionCounter; /* For allocating execution orders for jobs. */ + MA_ATOMIC(4, ma_uint32) executionPointer; /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ + + /* Written by the public API, read by the job thread. */ + MA_ATOMIC(4, ma_bool32) isLooping; /* Whether or not the stream is looping. It's important to set the looping flag at the data stream level for smooth loop transitions. */ + + /* Written by the job thread, read by the public API. */ + void* pPageData; /* Buffer containing the decoded data of each page. Allocated once at initialization time. */ + MA_ATOMIC(4, ma_uint32) pageFrameCount[2]; /* The number of valid PCM frames in each page. Used to determine the last valid frame. */ + + /* Written and read by both the public API and the job thread. These must be atomic. */ + MA_ATOMIC(4, ma_result) result; /* Result from asynchronous loading. When loading set to MA_BUSY. When initialized set to MA_SUCCESS. When deleting set to MA_UNAVAILABLE. If an error occurs when loading, set to an error code. */ + MA_ATOMIC(4, ma_bool32) isDecoderAtEnd; /* Whether or not the decoder has reached the end. */ + MA_ATOMIC(4, ma_bool32) isPageValid[2]; /* Booleans to indicate whether or not a page is valid. Set to false by the public API, set to true by the job thread. Set to false as the pages are consumed, true when they are filled. */ + MA_ATOMIC(4, ma_bool32) seekCounter; /* When 0, no seeking is being performed. When > 0, a seek is being performed and reading should be delayed with MA_BUSY. */ +}; + +struct ma_resource_manager_data_source +{ + union + { + ma_resource_manager_data_buffer buffer; + ma_resource_manager_data_stream stream; + } backend; /* Must be the first item because we need the first item to be the data source callbacks for the buffer or stream. */ + + ma_uint32 flags; /* The flags that were passed in to ma_resource_manager_data_source_init(). */ + MA_ATOMIC(4, ma_uint32) executionCounter; /* For allocating execution orders for jobs. */ + MA_ATOMIC(4, ma_uint32) executionPointer; /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ +}; + +typedef struct +{ + ma_allocation_callbacks allocationCallbacks; + ma_log* pLog; + ma_format decodedFormat; /* The decoded format to use. Set to ma_format_unknown (default) to use the file's native format. */ + ma_uint32 decodedChannels; /* The decoded channel count to use. Set to 0 (default) to use the file's native channel count. */ + ma_uint32 decodedSampleRate; /* the decoded sample rate to use. Set to 0 (default) to use the file's native sample rate. */ + ma_uint32 jobThreadCount; /* Set to 0 if you want to self-manage your job threads. Defaults to 1. */ + ma_uint32 jobQueueCapacity; /* The maximum number of jobs that can fit in the queue at a time. Defaults to MA_RESOURCE_MANAGER_JOB_QUEUE_CAPACITY. Cannot be zero. */ + ma_uint32 flags; + ma_vfs* pVFS; /* Can be NULL in which case defaults will be used. */ + ma_decoding_backend_vtable** ppCustomDecodingBackendVTables; + ma_uint32 customDecodingBackendCount; + void* pCustomDecodingBackendUserData; +} ma_resource_manager_config; + +MA_API ma_resource_manager_config ma_resource_manager_config_init(void); + +struct ma_resource_manager +{ + ma_resource_manager_config config; + ma_resource_manager_data_buffer_node* pRootDataBufferNode; /* The root buffer in the binary tree. */ +#ifndef MA_NO_THREADING + ma_mutex dataBufferBSTLock; /* For synchronizing access to the data buffer binary tree. */ + ma_thread jobThreads[MA_RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT]; /* The threads for executing jobs. */ +#endif + ma_resource_manager_job_queue jobQueue; /* Multi-consumer, multi-producer job queue for managing jobs for asynchronous decoding and streaming. */ + ma_default_vfs defaultVFS; /* Only used if a custom VFS is not specified. */ + ma_log log; /* Only used if no log was specified in the config. */ +}; + +/* Init. */ +MA_API ma_result ma_resource_manager_init(const ma_resource_manager_config* pConfig, ma_resource_manager* pResourceManager); +MA_API void ma_resource_manager_uninit(ma_resource_manager* pResourceManager); +MA_API ma_log* ma_resource_manager_get_log(ma_resource_manager* pResourceManager); + +/* Registration. */ +MA_API ma_result ma_resource_manager_register_file(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags); +MA_API ma_result ma_resource_manager_register_file_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags); +MA_API ma_result ma_resource_manager_register_decoded_data(ma_resource_manager* pResourceManager, const char* pName, const void* pData, ma_uint64 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate); /* Does not copy. Increments the reference count if already exists and returns MA_SUCCESS. */ +MA_API ma_result ma_resource_manager_register_decoded_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName, const void* pData, ma_uint64 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate); +MA_API ma_result ma_resource_manager_register_encoded_data(ma_resource_manager* pResourceManager, const char* pName, const void* pData, size_t sizeInBytes); /* Does not copy. Increments the reference count if already exists and returns MA_SUCCESS. */ +MA_API ma_result ma_resource_manager_register_encoded_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName, const void* pData, size_t sizeInBytes); +MA_API ma_result ma_resource_manager_unregister_file(ma_resource_manager* pResourceManager, const char* pFilePath); +MA_API ma_result ma_resource_manager_unregister_file_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath); +MA_API ma_result ma_resource_manager_unregister_data(ma_resource_manager* pResourceManager, const char* pName); +MA_API ma_result ma_resource_manager_unregister_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName); + +/* Data Buffers. */ +MA_API ma_result ma_resource_manager_data_buffer_init_ex(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_buffer* pDataBuffer); +MA_API ma_result ma_resource_manager_data_buffer_init(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_buffer* pDataBuffer); +MA_API ma_result ma_resource_manager_data_buffer_init_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_buffer* pDataBuffer); +MA_API ma_result ma_resource_manager_data_buffer_init_copy(ma_resource_manager* pResourceManager, const ma_resource_manager_data_buffer* pExistingDataBuffer, ma_resource_manager_data_buffer* pDataBuffer); +MA_API ma_result ma_resource_manager_data_buffer_uninit(ma_resource_manager_data_buffer* pDataBuffer); +MA_API ma_result ma_resource_manager_data_buffer_read_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); +MA_API ma_result ma_resource_manager_data_buffer_seek_to_pcm_frame(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64 frameIndex); +MA_API ma_result ma_resource_manager_data_buffer_get_data_format(ma_resource_manager_data_buffer* pDataBuffer, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_resource_manager_data_buffer_get_cursor_in_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pCursor); +MA_API ma_result ma_resource_manager_data_buffer_get_length_in_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pLength); +MA_API ma_result ma_resource_manager_data_buffer_result(const ma_resource_manager_data_buffer* pDataBuffer); +MA_API ma_result ma_resource_manager_data_buffer_set_looping(ma_resource_manager_data_buffer* pDataBuffer, ma_bool32 isLooping); +MA_API ma_bool32 ma_resource_manager_data_buffer_is_looping(const ma_resource_manager_data_buffer* pDataBuffer); +MA_API ma_result ma_resource_manager_data_buffer_get_available_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pAvailableFrames); + +/* Data Streams. */ +MA_API ma_result ma_resource_manager_data_stream_init_ex(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_stream* pDataStream); +MA_API ma_result ma_resource_manager_data_stream_init(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_stream* pDataStream); +MA_API ma_result ma_resource_manager_data_stream_init_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_stream* pDataStream); +MA_API ma_result ma_resource_manager_data_stream_uninit(ma_resource_manager_data_stream* pDataStream); +MA_API ma_result ma_resource_manager_data_stream_read_pcm_frames(ma_resource_manager_data_stream* pDataStream, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); +MA_API ma_result ma_resource_manager_data_stream_seek_to_pcm_frame(ma_resource_manager_data_stream* pDataStream, ma_uint64 frameIndex); +MA_API ma_result ma_resource_manager_data_stream_get_data_format(ma_resource_manager_data_stream* pDataStream, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_resource_manager_data_stream_get_cursor_in_pcm_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pCursor); +MA_API ma_result ma_resource_manager_data_stream_get_length_in_pcm_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pLength); +MA_API ma_result ma_resource_manager_data_stream_result(const ma_resource_manager_data_stream* pDataStream); +MA_API ma_result ma_resource_manager_data_stream_set_looping(ma_resource_manager_data_stream* pDataStream, ma_bool32 isLooping); +MA_API ma_bool32 ma_resource_manager_data_stream_is_looping(const ma_resource_manager_data_stream* pDataStream); +MA_API ma_result ma_resource_manager_data_stream_get_available_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pAvailableFrames); + +/* Data Sources. */ +MA_API ma_result ma_resource_manager_data_source_init_ex(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_source* pDataSource); +MA_API ma_result ma_resource_manager_data_source_init(ma_resource_manager* pResourceManager, const char* pName, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_source* pDataSource); +MA_API ma_result ma_resource_manager_data_source_init_w(ma_resource_manager* pResourceManager, const wchar_t* pName, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_source* pDataSource); +MA_API ma_result ma_resource_manager_data_source_init_copy(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source* pExistingDataSource, ma_resource_manager_data_source* pDataSource); +MA_API ma_result ma_resource_manager_data_source_uninit(ma_resource_manager_data_source* pDataSource); +MA_API ma_result ma_resource_manager_data_source_read_pcm_frames(ma_resource_manager_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); +MA_API ma_result ma_resource_manager_data_source_seek_to_pcm_frame(ma_resource_manager_data_source* pDataSource, ma_uint64 frameIndex); +MA_API ma_result ma_resource_manager_data_source_get_data_format(ma_resource_manager_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_resource_manager_data_source_get_cursor_in_pcm_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pCursor); +MA_API ma_result ma_resource_manager_data_source_get_length_in_pcm_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pLength); +MA_API ma_result ma_resource_manager_data_source_result(const ma_resource_manager_data_source* pDataSource); +MA_API ma_result ma_resource_manager_data_source_set_looping(ma_resource_manager_data_source* pDataSource, ma_bool32 isLooping); +MA_API ma_bool32 ma_resource_manager_data_source_is_looping(const ma_resource_manager_data_source* pDataSource); +MA_API ma_result ma_resource_manager_data_source_get_available_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pAvailableFrames); + +/* Job management. */ +MA_API ma_result ma_resource_manager_post_job(ma_resource_manager* pResourceManager, const ma_resource_manager_job* pJob); +MA_API ma_result ma_resource_manager_post_job_quit(ma_resource_manager* pResourceManager); /* Helper for posting a quit job. */ +MA_API ma_result ma_resource_manager_next_job(ma_resource_manager* pResourceManager, ma_resource_manager_job* pJob); +MA_API ma_result ma_resource_manager_process_job(ma_resource_manager* pResourceManager, ma_resource_manager_job* pJob); +MA_API ma_result ma_resource_manager_process_next_job(ma_resource_manager* pResourceManager); /* Returns MA_CANCELLED if a MA_RESOURCE_MANAGER_JOB_QUIT job is found. In non-blocking mode, returns MA_NO_DATA_AVAILABLE if no jobs are available. */ +#endif /* MA_NO_RESOURCE_MANAGER */ + + + +/************************************************************************************************************************************************************ + +Node Graph + +************************************************************************************************************************************************************/ +#ifndef MA_NO_NODE_GRAPH +/* Must never exceed 254. */ +#ifndef MA_MAX_NODE_BUS_COUNT +#define MA_MAX_NODE_BUS_COUNT 254 +#endif + +/* Used internally by miniaudio for memory management. Must never exceed MA_MAX_NODE_BUS_COUNT. */ +#ifndef MA_MAX_NODE_LOCAL_BUS_COUNT +#define MA_MAX_NODE_LOCAL_BUS_COUNT 2 +#endif + +/* Use this when the bus count is determined by the node instance rather than the vtable. */ +#define MA_NODE_BUS_COUNT_UNKNOWN 255 + +typedef struct ma_node_graph ma_node_graph; +typedef void ma_node; + + +/* Node flags. */ +#define MA_NODE_FLAG_PASSTHROUGH 0x00000001 +#define MA_NODE_FLAG_CONTINUOUS_PROCESSING 0x00000002 +#define MA_NODE_FLAG_ALLOW_NULL_INPUT 0x00000004 +#define MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES 0x00000008 + + +/* The playback state of a node. Either started or stopped. */ +typedef enum +{ + ma_node_state_started = 0, + ma_node_state_stopped = 1 +} ma_node_state; + + +typedef struct +{ + /* + Extended processing callback. This callback is used for effects that process input and output + at different rates (i.e. they perform resampling). This is similar to the simple version, only + they take two sepate frame counts: one for input, and one for output. + + On input, `pFrameCountOut` is equal to the capacity of the output buffer for each bus, whereas + `pFrameCountIn` will be equal to the number of PCM frames in each of the buffers in `ppFramesIn`. + + On output, set `pFrameCountOut` to the number of PCM frames that were actually output and set + `pFrameCountIn` to the number of input frames that were consumed. + */ + void (* onProcess)(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut); + + /* + A callback for retrieving the number of a input frames that are required to output the + specified number of output frames. You would only want to implement this when the node performs + resampling. This is optional, even for nodes that perform resampling, but it does offer a + small reduction in latency as it allows miniaudio to calculate the exact number of input frames + to read at a time instead of having to estimate. + */ + ma_result (* onGetRequiredInputFrameCount)(ma_node* pNode, ma_uint32 outputFrameCount, ma_uint32* pInputFrameCount); + + /* + The number of input buses. This is how many sub-buffers will be contained in the `ppFramesIn` + parameters of the callbacks above. + */ + ma_uint8 inputBusCount; + + /* + The number of output buses. This is how many sub-buffers will be contained in the `ppFramesOut` + parameters of the callbacks above. + */ + ma_uint8 outputBusCount; + + /* + Flags describing characteristics of the node. This is currently just a placeholder for some + ideas for later on. + */ + ma_uint32 flags; +} ma_node_vtable; + +typedef struct +{ + const ma_node_vtable* vtable; /* Should never be null. Initialization of the node will fail if so. */ + ma_node_state initialState; /* Defaults to ma_node_state_started. */ + ma_uint32 inputBusCount; /* Only used if the vtable specifies an input bus count of `MA_NODE_BUS_COUNT_UNKNOWN`, otherwise must be set to `MA_NODE_BUS_COUNT_UNKNOWN` (default). */ + ma_uint32 outputBusCount; /* Only used if the vtable specifies an output bus count of `MA_NODE_BUS_COUNT_UNKNOWN`, otherwise be set to `MA_NODE_BUS_COUNT_UNKNOWN` (default). */ + const ma_uint32* pInputChannels; /* The number of elements are determined by the input bus count as determined by the vtable, or `inputBusCount` if the vtable specifies `MA_NODE_BUS_COUNT_UNKNOWN`. */ + const ma_uint32* pOutputChannels; /* The number of elements are determined by the output bus count as determined by the vtable, or `outputBusCount` if the vtable specifies `MA_NODE_BUS_COUNT_UNKNOWN`. */ +} ma_node_config; + +MA_API ma_node_config ma_node_config_init(void); + + +/* +A node has multiple output buses. An output bus is attached to an input bus as an item in a linked +list. Think of the input bus as a linked list, with the output bus being an item in that list. +*/ +#define MA_NODE_OUTPUT_BUS_FLAG_HAS_READ 0x01 /* Whether or not this bus ready to read more data. Only used on nodes with multiple output buses. */ + +typedef struct ma_node_output_bus ma_node_output_bus; +struct ma_node_output_bus +{ + /* Immutable. */ + ma_node* pNode; /* The node that owns this output bus. The input node. Will be null for dummy head and tail nodes. */ + ma_uint8 outputBusIndex; /* The index of the output bus on pNode that this output bus represents. */ + ma_uint8 channels; /* The number of channels in the audio stream for this bus. */ + + /* Mutable via multiple threads. Must be used atomically. The weird ordering here is for packing reasons. */ + MA_ATOMIC(1, ma_uint8) inputNodeInputBusIndex; /* The index of the input bus on the input. Required for detaching. */ + MA_ATOMIC(4, ma_uint32) flags; /* Some state flags for tracking the read state of the output buffer. */ + MA_ATOMIC(4, ma_uint32) refCount; /* Reference count for some thread-safety when detaching. */ + MA_ATOMIC(4, ma_bool32) isAttached; /* This is used to prevent iteration of nodes that are in the middle of being detached. Used for thread safety. */ + MA_ATOMIC(4, ma_spinlock) lock; /* Unfortunate lock, but significantly simplifies the implementation. Required for thread-safe attaching and detaching. */ + MA_ATOMIC(4, float) volume; /* Linear. */ + MA_ATOMIC(MA_SIZEOF_PTR, ma_node_output_bus*) pNext; /* If null, it's the tail node or detached. */ + MA_ATOMIC(MA_SIZEOF_PTR, ma_node_output_bus*) pPrev; /* If null, it's the head node or detached. */ + MA_ATOMIC(MA_SIZEOF_PTR, ma_node*) pInputNode; /* The node that this output bus is attached to. Required for detaching. */ +}; + +/* +A node has multiple input buses. The output buses of a node are connecting to the input busses of +another. An input bus is essentially just a linked list of output buses. +*/ +typedef struct ma_node_input_bus ma_node_input_bus; +struct ma_node_input_bus +{ + /* Mutable via multiple threads. */ + ma_node_output_bus head; /* Dummy head node for simplifying some lock-free thread-safety stuff. */ + MA_ATOMIC(4, ma_uint32) nextCounter; /* This is used to determine whether or not the input bus is finding the next node in the list. Used for thread safety when detaching output buses. */ + MA_ATOMIC(4, ma_spinlock) lock; /* Unfortunate lock, but significantly simplifies the implementation. Required for thread-safe attaching and detaching. */ + + /* Set once at startup. */ + ma_uint8 channels; /* The number of channels in the audio stream for this bus. */ +}; + + +typedef struct ma_node_base ma_node_base; +struct ma_node_base +{ + /* These variables are set once at startup. */ + ma_node_graph* pNodeGraph; /* The graph this node belongs to. */ + const ma_node_vtable* vtable; + float* pCachedData; /* Allocated on the heap. Fixed size. Needs to be stored on the heap because reading from output buses is done in separate function calls. */ + ma_uint16 cachedDataCapInFramesPerBus; /* The capacity of the input data cache in frames, per bus. */ + + /* These variables are read and written only from the audio thread. */ + ma_uint16 cachedFrameCountOut; + ma_uint16 cachedFrameCountIn; + ma_uint16 consumedFrameCountIn; + + /* These variables are read and written between different threads. */ + MA_ATOMIC(4, ma_node_state) state; /* When set to stopped, nothing will be read, regardless of the times in stateTimes. */ + MA_ATOMIC(8, ma_uint64) stateTimes[2]; /* Indexed by ma_node_state. Specifies the time based on the global clock that a node should be considered to be in the relevant state. */ + MA_ATOMIC(8, ma_uint64) localTime; /* The node's local clock. This is just a running sum of the number of output frames that have been processed. Can be modified by any thread with `ma_node_set_time()`. */ + ma_uint32 inputBusCount; + ma_uint32 outputBusCount; + ma_node_input_bus* pInputBuses; + ma_node_output_bus* pOutputBuses; + + /* Memory management. */ + ma_node_input_bus _inputBuses[MA_MAX_NODE_LOCAL_BUS_COUNT]; + ma_node_output_bus _outputBuses[MA_MAX_NODE_LOCAL_BUS_COUNT]; + void* _pHeap; /* A heap allocation for internal use only. pInputBuses and/or pOutputBuses will point to this if the bus count exceeds MA_MAX_NODE_LOCAL_BUS_COUNT. */ + ma_bool32 _ownsHeap; /* If set to true, the node owns the heap allocation and _pHeap will be freed in ma_node_uninit(). */ +}; + +MA_API ma_result ma_node_get_heap_size(const ma_node_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_node_init_preallocated(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, void* pHeap, ma_node* pNode); +MA_API ma_result ma_node_init(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node* pNode); +MA_API void ma_node_uninit(ma_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_node_graph* ma_node_get_node_graph(const ma_node* pNode); +MA_API ma_uint32 ma_node_get_input_bus_count(const ma_node* pNode); +MA_API ma_uint32 ma_node_get_output_bus_count(const ma_node* pNode); +MA_API ma_uint32 ma_node_get_input_channels(const ma_node* pNode, ma_uint32 inputBusIndex); +MA_API ma_uint32 ma_node_get_output_channels(const ma_node* pNode, ma_uint32 outputBusIndex); +MA_API ma_result ma_node_attach_output_bus(ma_node* pNode, ma_uint32 outputBusIndex, ma_node* pOtherNode, ma_uint32 otherNodeInputBusIndex); +MA_API ma_result ma_node_detach_output_bus(ma_node* pNode, ma_uint32 outputBusIndex); +MA_API ma_result ma_node_detach_all_output_buses(ma_node* pNode); +MA_API ma_result ma_node_set_output_bus_volume(ma_node* pNode, ma_uint32 outputBusIndex, float volume); +MA_API float ma_node_get_output_bus_volume(const ma_node* pNode, ma_uint32 outputBusIndex); +MA_API ma_result ma_node_set_state(ma_node* pNode, ma_node_state state); +MA_API ma_node_state ma_node_get_state(const ma_node* pNode); +MA_API ma_result ma_node_set_state_time(ma_node* pNode, ma_node_state state, ma_uint64 globalTime); +MA_API ma_uint64 ma_node_get_state_time(const ma_node* pNode, ma_node_state state); +MA_API ma_node_state ma_node_get_state_by_time(const ma_node* pNode, ma_uint64 globalTime); +MA_API ma_node_state ma_node_get_state_by_time_range(const ma_node* pNode, ma_uint64 globalTimeBeg, ma_uint64 globalTimeEnd); +MA_API ma_uint64 ma_node_get_time(const ma_node* pNode); +MA_API ma_result ma_node_set_time(ma_node* pNode, ma_uint64 localTime); + + +typedef struct +{ + ma_uint32 channels; +} ma_node_graph_config; + +MA_API ma_node_graph_config ma_node_graph_config_init(ma_uint32 channels); + + +struct ma_node_graph +{ + /* Immutable. */ + ma_node_base endpoint; /* Special node that all nodes eventually connect to. Data is read from this node in ma_node_graph_read_pcm_frames(). */ + + /* Read and written by multiple threads. */ + MA_ATOMIC(4, ma_bool32) isReading; +}; + +MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node_graph* pNodeGraph); +MA_API void ma_node_graph_uninit(ma_node_graph* pNodeGraph, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_node* ma_node_graph_get_endpoint(ma_node_graph* pNodeGraph); +MA_API ma_result ma_node_graph_read_pcm_frames(ma_node_graph* pNodeGraph, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); +MA_API ma_uint32 ma_node_graph_get_channels(const ma_node_graph* pNodeGraph); +MA_API ma_uint64 ma_node_graph_get_time(const ma_node_graph* pNodeGraph); +MA_API ma_result ma_node_graph_set_time(ma_node_graph* pNodeGraph, ma_uint64 globalTime); + + + +/* Data source node. 0 input buses, 1 output bus. Used for reading from a data source. */ +typedef struct +{ + ma_node_config nodeConfig; + ma_data_source* pDataSource; +} ma_data_source_node_config; + +MA_API ma_data_source_node_config ma_data_source_node_config_init(ma_data_source* pDataSource); + + +typedef struct +{ + ma_node_base base; + ma_data_source* pDataSource; +} ma_data_source_node; + +MA_API ma_result ma_data_source_node_init(ma_node_graph* pNodeGraph, const ma_data_source_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source_node* pDataSourceNode); +MA_API void ma_data_source_node_uninit(ma_data_source_node* pDataSourceNode, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_data_source_node_set_looping(ma_data_source_node* pDataSourceNode, ma_bool32 isLooping); +MA_API ma_bool32 ma_data_source_node_is_looping(ma_data_source_node* pDataSourceNode); + + +/* Splitter Node. 1 input, 2 outputs. Used for splitting/copying a stream so it can be as input into two separate output nodes. */ +typedef struct +{ + ma_node_config nodeConfig; + ma_uint32 channels; +} ma_splitter_node_config; + +MA_API ma_splitter_node_config ma_splitter_node_config_init(ma_uint32 channels); + + +typedef struct +{ + ma_node_base base; +} ma_splitter_node; + +MA_API ma_result ma_splitter_node_init(ma_node_graph* pNodeGraph, const ma_splitter_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_splitter_node* pSplitterNode); +MA_API void ma_splitter_node_uninit(ma_splitter_node* pSplitterNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +Biquad Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_biquad_config biquad; +} ma_biquad_node_config; + +MA_API ma_biquad_node_config ma_biquad_node_config_init(ma_uint32 channels, float b0, float b1, float b2, float a0, float a1, float a2); + + +typedef struct +{ + ma_node_base baseNode; + ma_biquad biquad; +} ma_biquad_node; + +MA_API ma_result ma_biquad_node_init(ma_node_graph* pNodeGraph, const ma_biquad_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_biquad_node* pNode); +MA_API ma_result ma_biquad_node_reinit(const ma_biquad_config* pConfig, ma_biquad_node* pNode); +MA_API void ma_biquad_node_uninit(ma_biquad_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +Low Pass Filter Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_lpf_config lpf; +} ma_lpf_node_config; + +MA_API ma_lpf_node_config ma_lpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order); + + +typedef struct +{ + ma_node_base baseNode; + ma_lpf lpf; +} ma_lpf_node; + +MA_API ma_result ma_lpf_node_init(ma_node_graph* pNodeGraph, const ma_lpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf_node* pNode); +MA_API ma_result ma_lpf_node_reinit(const ma_lpf_config* pConfig, ma_lpf_node* pNode); +MA_API void ma_lpf_node_uninit(ma_lpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +High Pass Filter Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_hpf_config hpf; +} ma_hpf_node_config; + +MA_API ma_hpf_node_config ma_hpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order); + + +typedef struct +{ + ma_node_base baseNode; + ma_hpf hpf; +} ma_hpf_node; + +MA_API ma_result ma_hpf_node_init(ma_node_graph* pNodeGraph, const ma_hpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf_node* pNode); +MA_API ma_result ma_hpf_node_reinit(const ma_hpf_config* pConfig, ma_hpf_node* pNode); +MA_API void ma_hpf_node_uninit(ma_hpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +Band Pass Filter Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_bpf_config bpf; +} ma_bpf_node_config; + +MA_API ma_bpf_node_config ma_bpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order); + + +typedef struct +{ + ma_node_base baseNode; + ma_bpf bpf; +} ma_bpf_node; + +MA_API ma_result ma_bpf_node_init(ma_node_graph* pNodeGraph, const ma_bpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_bpf_node* pNode); +MA_API ma_result ma_bpf_node_reinit(const ma_bpf_config* pConfig, ma_bpf_node* pNode); +MA_API void ma_bpf_node_uninit(ma_bpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +Notching Filter Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_notch_config notch; +} ma_notch_node_config; + +MA_API ma_notch_node_config ma_notch_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double q, double frequency); + + +typedef struct +{ + ma_node_base baseNode; + ma_notch2 notch; +} ma_notch_node; + +MA_API ma_result ma_notch_node_init(ma_node_graph* pNodeGraph, const ma_notch_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_notch_node* pNode); +MA_API ma_result ma_notch_node_reinit(const ma_notch_config* pConfig, ma_notch_node* pNode); +MA_API void ma_notch_node_uninit(ma_notch_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +Peaking Filter Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_peak_config peak; +} ma_peak_node_config; + +MA_API ma_peak_node_config ma_peak_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency); + + +typedef struct +{ + ma_node_base baseNode; + ma_peak2 peak; +} ma_peak_node; + +MA_API ma_result ma_peak_node_init(ma_node_graph* pNodeGraph, const ma_peak_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_peak_node* pNode); +MA_API ma_result ma_peak_node_reinit(const ma_peak_config* pConfig, ma_peak_node* pNode); +MA_API void ma_peak_node_uninit(ma_peak_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +Low Shelf Filter Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_loshelf_config loshelf; +} ma_loshelf_node_config; + +MA_API ma_loshelf_node_config ma_loshelf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency); + + +typedef struct +{ + ma_node_base baseNode; + ma_loshelf2 loshelf; +} ma_loshelf_node; + +MA_API ma_result ma_loshelf_node_init(ma_node_graph* pNodeGraph, const ma_loshelf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_loshelf_node* pNode); +MA_API ma_result ma_loshelf_node_reinit(const ma_loshelf_config* pConfig, ma_loshelf_node* pNode); +MA_API void ma_loshelf_node_uninit(ma_loshelf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +High Shelf Filter Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_hishelf_config hishelf; +} ma_hishelf_node_config; + +MA_API ma_hishelf_node_config ma_hishelf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency); + + +typedef struct +{ + ma_node_base baseNode; + ma_hishelf2 hishelf; +} ma_hishelf_node; + +MA_API ma_result ma_hishelf_node_init(ma_node_graph* pNodeGraph, const ma_hishelf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hishelf_node* pNode); +MA_API ma_result ma_hishelf_node_reinit(const ma_hishelf_config* pConfig, ma_hishelf_node* pNode); +MA_API void ma_hishelf_node_uninit(ma_hishelf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +typedef struct +{ + ma_node_config nodeConfig; + ma_delay_config delay; +} ma_delay_node_config; + +MA_API ma_delay_node_config ma_delay_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 delayInFrames, float decay); + + +typedef struct +{ + ma_node_base baseNode; + ma_delay delay; +} ma_delay_node; + +MA_API ma_result ma_delay_node_init(ma_node_graph* pNodeGraph, const ma_delay_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_delay_node* pDelayNode); +MA_API void ma_delay_node_uninit(ma_delay_node* pDelayNode, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API void ma_delay_node_set_wet(ma_delay_node* pDelayNode, float value); +MA_API float ma_delay_node_get_wet(const ma_delay_node* pDelayNode); +MA_API void ma_delay_node_set_dry(ma_delay_node* pDelayNode, float value); +MA_API float ma_delay_node_get_dry(const ma_delay_node* pDelayNode); +MA_API void ma_delay_node_set_decay(ma_delay_node* pDelayNode, float value); +MA_API float ma_delay_node_get_decay(const ma_delay_node* pDelayNode); +#endif /* MA_NO_NODE_GRAPH */ + + +/************************************************************************************************************************************************************ + +Engine + +************************************************************************************************************************************************************/ +#if !defined(MA_NO_ENGINE) && !defined(MA_NO_NODE_GRAPH) +typedef struct ma_engine ma_engine; +typedef struct ma_sound ma_sound; + + +/* Sound flags. */ +#define MA_SOUND_FLAG_STREAM MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM /* 0x00000001 */ +#define MA_SOUND_FLAG_DECODE MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE /* 0x00000002 */ +#define MA_SOUND_FLAG_ASYNC MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC /* 0x00000004 */ +#define MA_SOUND_FLAG_WAIT_INIT MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT /* 0x00000008 */ +#define MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT 0x00000010 /* Do not attach to the endpoint by default. Useful for when setting up nodes in a complex graph system. */ +#define MA_SOUND_FLAG_NO_PITCH 0x00000020 /* Disable pitch shifting with ma_sound_set_pitch() and ma_sound_group_set_pitch(). This is an optimization. */ +#define MA_SOUND_FLAG_NO_SPATIALIZATION 0x00000040 /* Disable spatialization. */ + +#ifndef MA_ENGINE_MAX_LISTENERS +#define MA_ENGINE_MAX_LISTENERS 4 +#endif + +#define MA_LISTENER_INDEX_CLOSEST ((ma_uint8)-1) + +typedef enum +{ + ma_engine_node_type_sound, + ma_engine_node_type_group +} ma_engine_node_type; + +typedef struct +{ + ma_engine* pEngine; + ma_engine_node_type type; + ma_uint32 channelsIn; + ma_uint32 channelsOut; + ma_uint32 sampleRate; /* Only used when the type is set to ma_engine_node_type_sound. */ + ma_bool8 isPitchDisabled; /* Pitching can be explicitly disable with MA_SOUND_FLAG_NO_PITCH to optimize processing. */ + ma_bool8 isSpatializationDisabled; /* Spatialization can be explicitly disabled with MA_SOUND_FLAG_NO_SPATIALIZATION. */ + ma_uint8 pinnedListenerIndex; /* The index of the listener this node should always use for spatialization. If set to MA_LISTENER_INDEX_CLOSEST the engine will use the closest listener. */ +} ma_engine_node_config; + +MA_API ma_engine_node_config ma_engine_node_config_init(ma_engine* pEngine, ma_engine_node_type type, ma_uint32 flags); + + +/* Base node object for both ma_sound and ma_sound_group. */ +typedef struct +{ + ma_node_base baseNode; /* Must be the first member for compatiblity with the ma_node API. */ + ma_engine* pEngine; /* A pointer to the engine. Set based on the value from the config. */ + ma_uint32 sampleRate; /* The sample rate of the input data. For sounds backed by a data source, this will be the data source's sample rate. Otherwise it'll be the engine's sample rate. */ + ma_fader fader; + ma_linear_resampler resampler; /* For pitch shift. */ + ma_spatializer spatializer; + ma_panner panner; + MA_ATOMIC(4, float) pitch; + float oldPitch; /* For determining whether or not the resampler needs to be updated to reflect the new pitch. The resampler will be updated on the mixing thread. */ + float oldDopplerPitch; /* For determining whether or not the resampler needs to be updated to take a new doppler pitch into account. */ + MA_ATOMIC(4, ma_bool32) isPitchDisabled; /* When set to true, pitching will be disabled which will allow the resampler to be bypassed to save some computation. */ + MA_ATOMIC(4, ma_bool32) isSpatializationDisabled; /* Set to false by default. When set to false, will not have spatialisation applied. */ + MA_ATOMIC(4, ma_uint32) pinnedListenerIndex; /* The index of the listener this node should always use for spatialization. If set to MA_LISTENER_INDEX_CLOSEST the engine will use the closest listener. */ + + /* Memory management. */ + ma_bool8 _ownsHeap; + void* _pHeap; +} ma_engine_node; + +MA_API ma_result ma_engine_node_get_heap_size(const ma_engine_node_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_engine_node_init_preallocated(const ma_engine_node_config* pConfig, void* pHeap, ma_engine_node* pEngineNode); +MA_API ma_result ma_engine_node_init(const ma_engine_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_engine_node* pEngineNode); +MA_API void ma_engine_node_uninit(ma_engine_node* pEngineNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +#define MA_SOUND_SOURCE_CHANNEL_COUNT 0xFFFFFFFF + +typedef struct +{ + const char* pFilePath; /* Set this to load from the resource manager. */ + const wchar_t* pFilePathW; /* Set this to load from the resource manager. */ + ma_data_source* pDataSource; /* Set this to load from an existing data source. */ + ma_node* pInitialAttachment; /* If set, the sound will be attached to an input of this node. This can be set to a ma_sound. If set to NULL, the sound will be attached directly to the endpoint unless MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT is set in `flags`. */ + ma_uint32 initialAttachmentInputBusIndex; /* The index of the input bus of pInitialAttachment to attach the sound to. */ + ma_uint32 channelsIn; /* Ignored if using a data source as input (the data source's channel count will be used always). Otherwise, setting to 0 will cause the engine's channel count to be used. */ + ma_uint32 channelsOut; /* Set this to 0 (default) to use the engine's channel count. Set to MA_SOUND_SOURCE_CHANNEL_COUNT to use the data source's channel count (only used if using a data source as input). */ + ma_uint32 flags; /* A combination of MA_SOUND_FLAG_* flags. */ + ma_uint64 initialSeekPointInPCMFrames; /* Initializes the sound such that it's seeked to this location by default. */ + ma_uint64 rangeBegInPCMFrames; + ma_uint64 rangeEndInPCMFrames; + ma_uint64 loopPointBegInPCMFrames; + ma_uint64 loopPointEndInPCMFrames; + ma_bool32 isLooping; + ma_fence* pDoneFence; /* Released when the resource manager has finished decoding the entire sound. Not used with streams. */ +} ma_sound_config; + +MA_API ma_sound_config ma_sound_config_init(void); + +struct ma_sound +{ + ma_engine_node engineNode; /* Must be the first member for compatibility with the ma_node API. */ + ma_data_source* pDataSource; + ma_uint64 seekTarget; /* The PCM frame index to seek to in the mixing thread. Set to (~(ma_uint64)0) to not perform any seeking. */ + MA_ATOMIC(4, ma_bool32) atEnd; + ma_bool8 ownsDataSource; + + /* + We're declaring a resource manager data source object here to save us a malloc when loading a + sound via the resource manager, which I *think* will be the most common scenario. + */ +#ifndef MA_NO_RESOURCE_MANAGER + ma_resource_manager_data_source* pResourceManagerDataSource; +#endif +}; + +/* Structure specifically for sounds played with ma_engine_play_sound(). Making this a separate structure to reduce overhead. */ +typedef struct ma_sound_inlined ma_sound_inlined; +struct ma_sound_inlined +{ + ma_sound sound; + ma_sound_inlined* pNext; + ma_sound_inlined* pPrev; +}; + +/* A sound group is just a sound. */ +typedef ma_sound_config ma_sound_group_config; +typedef ma_sound ma_sound_group; + +MA_API ma_sound_group_config ma_sound_group_config_init(void); + + +typedef struct +{ +#if !defined(MA_NO_RESOURCE_MANAGER) + ma_resource_manager* pResourceManager; /* Can be null in which case a resource manager will be created for you. */ +#endif +#if !defined(MA_NO_DEVICE_IO) + ma_context* pContext; + ma_device* pDevice; /* If set, the caller is responsible for calling ma_engine_data_callback() in the device's data callback. */ + ma_device_id* pPlaybackDeviceID; /* The ID of the playback device to use with the default listener. */ +#endif + ma_log* pLog; /* When set to NULL, will use the context's log. */ + ma_uint32 listenerCount; /* Must be between 1 and MA_ENGINE_MAX_LISTENERS. */ + ma_uint32 channels; /* The number of channels to use when mixing and spatializing. When set to 0, will use the native channel count of the device. */ + ma_uint32 sampleRate; /* The sample rate. When set to 0 will use the native channel count of the device. */ + ma_uint32 periodSizeInFrames; /* If set to something other than 0, updates will always be exactly this size. The underlying device may be a different size, but from the perspective of the mixer that won't matter.*/ + ma_uint32 periodSizeInMilliseconds; /* Used if periodSizeInFrames is unset. */ + ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. If set to 0, will use gainSmoothTimeInMilliseconds. */ + ma_uint32 gainSmoothTimeInMilliseconds; /* When set to 0, gainSmoothTimeInFrames will be used. If both are set to 0, a default value will be used. */ + ma_allocation_callbacks allocationCallbacks; + ma_bool32 noAutoStart; /* When set to true, requires an explicit call to ma_engine_start(). This is false by default, meaning the engine will be started automatically in ma_engine_init(). */ + ma_bool32 noDevice; /* When set to true, don't create a default device. ma_engine_read_pcm_frames() can be called manually to read data. */ + ma_mono_expansion_mode monoExpansionMode; /* Controls how the mono channel should be expanded to other channels when spatialization is disabled on a sound. */ + ma_vfs* pResourceManagerVFS; /* A pointer to a pre-allocated VFS object to use with the resource manager. This is ignored if pResourceManager is not NULL. */ +} ma_engine_config; + +MA_API ma_engine_config ma_engine_config_init(void); + + +struct ma_engine +{ + ma_node_graph nodeGraph; /* An engine is a node graph. It should be able to be plugged into any ma_node_graph API (with a cast) which means this must be the first member of this struct. */ +#if !defined(MA_NO_RESOURCE_MANAGER) + ma_resource_manager* pResourceManager; +#endif +#if !defined(MA_NO_DEVICE_IO) + ma_device* pDevice; /* Optionally set via the config, otherwise allocated by the engine in ma_engine_init(). */ +#endif + ma_log* pLog; + ma_uint32 sampleRate; + ma_uint32 listenerCount; + ma_spatializer_listener listeners[MA_ENGINE_MAX_LISTENERS]; + ma_allocation_callbacks allocationCallbacks; + ma_bool8 ownsResourceManager; + ma_bool8 ownsDevice; + ma_spinlock inlinedSoundLock; /* For synchronizing access so the inlined sound list. */ + ma_sound_inlined* pInlinedSoundHead; /* The first inlined sound. Inlined sounds are tracked in a linked list. */ + MA_ATOMIC(4, ma_uint32) inlinedSoundCount; /* The total number of allocated inlined sound objects. Used for debugging. */ + ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. */ + ma_mono_expansion_mode monoExpansionMode; +}; + +MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEngine); +MA_API void ma_engine_uninit(ma_engine* pEngine); +MA_API ma_result ma_engine_read_pcm_frames(ma_engine* pEngine, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); +MA_API ma_node_graph* ma_engine_get_node_graph(ma_engine* pEngine); +#if !defined(MA_NO_RESOURCE_MANAGER) +MA_API ma_resource_manager* ma_engine_get_resource_manager(ma_engine* pEngine); +#endif +MA_API ma_device* ma_engine_get_device(ma_engine* pEngine); +MA_API ma_log* ma_engine_get_log(ma_engine* pEngine); +MA_API ma_node* ma_engine_get_endpoint(ma_engine* pEngine); +MA_API ma_uint64 ma_engine_get_time(const ma_engine* pEngine); +MA_API ma_uint64 ma_engine_set_time(ma_engine* pEngine, ma_uint64 globalTime); +MA_API ma_uint32 ma_engine_get_channels(const ma_engine* pEngine); +MA_API ma_uint32 ma_engine_get_sample_rate(const ma_engine* pEngine); + +MA_API ma_result ma_engine_start(ma_engine* pEngine); +MA_API ma_result ma_engine_stop(ma_engine* pEngine); +MA_API ma_result ma_engine_set_volume(ma_engine* pEngine, float volume); +MA_API ma_result ma_engine_set_gain_db(ma_engine* pEngine, float gainDB); + +MA_API ma_uint32 ma_engine_get_listener_count(const ma_engine* pEngine); +MA_API ma_uint32 ma_engine_find_closest_listener(const ma_engine* pEngine, float absolutePosX, float absolutePosY, float absolutePosZ); +MA_API void ma_engine_listener_set_position(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z); +MA_API ma_vec3f ma_engine_listener_get_position(const ma_engine* pEngine, ma_uint32 listenerIndex); +MA_API void ma_engine_listener_set_direction(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z); +MA_API ma_vec3f ma_engine_listener_get_direction(const ma_engine* pEngine, ma_uint32 listenerIndex); +MA_API void ma_engine_listener_set_velocity(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z); +MA_API ma_vec3f ma_engine_listener_get_velocity(const ma_engine* pEngine, ma_uint32 listenerIndex); +MA_API void ma_engine_listener_set_cone(ma_engine* pEngine, ma_uint32 listenerIndex, float innerAngleInRadians, float outerAngleInRadians, float outerGain); +MA_API void ma_engine_listener_get_cone(const ma_engine* pEngine, ma_uint32 listenerIndex, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain); +MA_API void ma_engine_listener_set_world_up(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z); +MA_API ma_vec3f ma_engine_listener_get_world_up(const ma_engine* pEngine, ma_uint32 listenerIndex); +MA_API void ma_engine_listener_set_enabled(ma_engine* pEngine, ma_uint32 listenerIndex, ma_bool32 isEnabled); +MA_API ma_bool32 ma_engine_listener_is_enabled(const ma_engine* pEngine, ma_uint32 listenerIndex); + +#ifndef MA_NO_RESOURCE_MANAGER +MA_API ma_result ma_engine_play_sound(ma_engine* pEngine, const char* pFilePath, ma_sound_group* pGroup); /* Fire and forget. */ +#endif + +#ifndef MA_NO_RESOURCE_MANAGER +MA_API ma_result ma_sound_init_from_file(ma_engine* pEngine, const char* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, ma_fence* pDoneFence, ma_sound* pSound); +MA_API ma_result ma_sound_init_from_file_w(ma_engine* pEngine, const wchar_t* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, ma_fence* pDoneFence, ma_sound* pSound); +MA_API ma_result ma_sound_init_copy(ma_engine* pEngine, const ma_sound* pExistingSound, ma_uint32 flags, ma_sound_group* pGroup, ma_sound* pSound); +#endif +MA_API ma_result ma_sound_init_from_data_source(ma_engine* pEngine, ma_data_source* pDataSource, ma_uint32 flags, ma_sound_group* pGroup, ma_sound* pSound); +MA_API ma_result ma_sound_init_ex(ma_engine* pEngine, const ma_sound_config* pConfig, ma_sound* pSound); +MA_API void ma_sound_uninit(ma_sound* pSound); +MA_API ma_engine* ma_sound_get_engine(const ma_sound* pSound); +MA_API ma_data_source* ma_sound_get_data_source(const ma_sound* pSound); +MA_API ma_result ma_sound_start(ma_sound* pSound); +MA_API ma_result ma_sound_stop(ma_sound* pSound); +MA_API void ma_sound_set_volume(ma_sound* pSound, float volume); +MA_API float ma_sound_get_volume(const ma_sound* pSound); +MA_API void ma_sound_set_pan(ma_sound* pSound, float pan); +MA_API float ma_sound_get_pan(const ma_sound* pSound); +MA_API void ma_sound_set_pan_mode(ma_sound* pSound, ma_pan_mode panMode); +MA_API ma_pan_mode ma_sound_get_pan_mode(const ma_sound* pSound); +MA_API void ma_sound_set_pitch(ma_sound* pSound, float pitch); +MA_API float ma_sound_get_pitch(const ma_sound* pSound); +MA_API void ma_sound_set_spatialization_enabled(ma_sound* pSound, ma_bool32 enabled); +MA_API ma_bool32 ma_sound_is_spatialization_enabled(const ma_sound* pSound); +MA_API void ma_sound_set_pinned_listener_index(ma_sound* pSound, ma_uint32 listenerIndex); +MA_API ma_uint32 ma_sound_get_pinned_listener_index(const ma_sound* pSound); +MA_API void ma_sound_set_position(ma_sound* pSound, float x, float y, float z); +MA_API ma_vec3f ma_sound_get_position(const ma_sound* pSound); +MA_API void ma_sound_set_direction(ma_sound* pSound, float x, float y, float z); +MA_API ma_vec3f ma_sound_get_direction(const ma_sound* pSound); +MA_API void ma_sound_set_velocity(ma_sound* pSound, float x, float y, float z); +MA_API ma_vec3f ma_sound_get_velocity(const ma_sound* pSound); +MA_API void ma_sound_set_attenuation_model(ma_sound* pSound, ma_attenuation_model attenuationModel); +MA_API ma_attenuation_model ma_sound_get_attenuation_model(const ma_sound* pSound); +MA_API void ma_sound_set_positioning(ma_sound* pSound, ma_positioning positioning); +MA_API ma_positioning ma_sound_get_positioning(const ma_sound* pSound); +MA_API void ma_sound_set_rolloff(ma_sound* pSound, float rolloff); +MA_API float ma_sound_get_rolloff(const ma_sound* pSound); +MA_API void ma_sound_set_min_gain(ma_sound* pSound, float minGain); +MA_API float ma_sound_get_min_gain(const ma_sound* pSound); +MA_API void ma_sound_set_max_gain(ma_sound* pSound, float maxGain); +MA_API float ma_sound_get_max_gain(const ma_sound* pSound); +MA_API void ma_sound_set_min_distance(ma_sound* pSound, float minDistance); +MA_API float ma_sound_get_min_distance(const ma_sound* pSound); +MA_API void ma_sound_set_max_distance(ma_sound* pSound, float maxDistance); +MA_API float ma_sound_get_max_distance(const ma_sound* pSound); +MA_API void ma_sound_set_cone(ma_sound* pSound, float innerAngleInRadians, float outerAngleInRadians, float outerGain); +MA_API void ma_sound_get_cone(const ma_sound* pSound, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain); +MA_API void ma_sound_set_doppler_factor(ma_sound* pSound, float dopplerFactor); +MA_API float ma_sound_get_doppler_factor(const ma_sound* pSound); +MA_API void ma_sound_set_fade_in_pcm_frames(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames); +MA_API void ma_sound_set_fade_in_milliseconds(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds); +MA_API float ma_sound_get_current_fade_volume(ma_sound* pSound); +MA_API void ma_sound_set_start_time_in_pcm_frames(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInFrames); +MA_API void ma_sound_set_start_time_in_milliseconds(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInMilliseconds); +MA_API void ma_sound_set_stop_time_in_pcm_frames(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInFrames); +MA_API void ma_sound_set_stop_time_in_milliseconds(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInMilliseconds); +MA_API ma_bool32 ma_sound_is_playing(const ma_sound* pSound); +MA_API ma_uint64 ma_sound_get_time_in_pcm_frames(const ma_sound* pSound); +MA_API void ma_sound_set_looping(ma_sound* pSound, ma_bool32 isLooping); +MA_API ma_bool32 ma_sound_is_looping(const ma_sound* pSound); +MA_API ma_bool32 ma_sound_at_end(const ma_sound* pSound); +MA_API ma_result ma_sound_seek_to_pcm_frame(ma_sound* pSound, ma_uint64 frameIndex); /* Just a wrapper around ma_data_source_seek_to_pcm_frame(). */ +MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64* pCursor); +MA_API ma_result ma_sound_get_length_in_pcm_frames(ma_sound* pSound, ma_uint64* pLength); + +MA_API ma_result ma_sound_group_init(ma_engine* pEngine, ma_uint32 flags, ma_sound_group* pParentGroup, ma_sound_group* pGroup); +MA_API ma_result ma_sound_group_init_ex(ma_engine* pEngine, const ma_sound_group_config* pConfig, ma_sound_group* pGroup); +MA_API void ma_sound_group_uninit(ma_sound_group* pGroup); +MA_API ma_engine* ma_sound_group_get_engine(const ma_sound_group* pGroup); +MA_API ma_result ma_sound_group_start(ma_sound_group* pGroup); +MA_API ma_result ma_sound_group_stop(ma_sound_group* pGroup); +MA_API void ma_sound_group_set_volume(ma_sound_group* pGroup, float volume); +MA_API float ma_sound_group_get_volume(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_pan(ma_sound_group* pGroup, float pan); +MA_API float ma_sound_group_get_pan(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_pan_mode(ma_sound_group* pGroup, ma_pan_mode panMode); +MA_API ma_pan_mode ma_sound_group_get_pan_mode(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_pitch(ma_sound_group* pGroup, float pitch); +MA_API float ma_sound_group_get_pitch(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_spatialization_enabled(ma_sound_group* pGroup, ma_bool32 enabled); +MA_API ma_bool32 ma_sound_group_is_spatialization_enabled(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_pinned_listener_index(ma_sound_group* pGroup, ma_uint32 listenerIndex); +MA_API ma_uint32 ma_sound_group_get_pinned_listener_index(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_position(ma_sound_group* pGroup, float x, float y, float z); +MA_API ma_vec3f ma_sound_group_get_position(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_direction(ma_sound_group* pGroup, float x, float y, float z); +MA_API ma_vec3f ma_sound_group_get_direction(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_velocity(ma_sound_group* pGroup, float x, float y, float z); +MA_API ma_vec3f ma_sound_group_get_velocity(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_attenuation_model(ma_sound_group* pGroup, ma_attenuation_model attenuationModel); +MA_API ma_attenuation_model ma_sound_group_get_attenuation_model(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_positioning(ma_sound_group* pGroup, ma_positioning positioning); +MA_API ma_positioning ma_sound_group_get_positioning(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_rolloff(ma_sound_group* pGroup, float rolloff); +MA_API float ma_sound_group_get_rolloff(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_min_gain(ma_sound_group* pGroup, float minGain); +MA_API float ma_sound_group_get_min_gain(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_max_gain(ma_sound_group* pGroup, float maxGain); +MA_API float ma_sound_group_get_max_gain(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_min_distance(ma_sound_group* pGroup, float minDistance); +MA_API float ma_sound_group_get_min_distance(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_max_distance(ma_sound_group* pGroup, float maxDistance); +MA_API float ma_sound_group_get_max_distance(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_cone(ma_sound_group* pGroup, float innerAngleInRadians, float outerAngleInRadians, float outerGain); +MA_API void ma_sound_group_get_cone(const ma_sound_group* pGroup, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain); +MA_API void ma_sound_group_set_doppler_factor(ma_sound_group* pGroup, float dopplerFactor); +MA_API float ma_sound_group_get_doppler_factor(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_fade_in_pcm_frames(ma_sound_group* pGroup, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames); +MA_API void ma_sound_group_set_fade_in_milliseconds(ma_sound_group* pGroup, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds); +MA_API float ma_sound_group_get_current_fade_volume(ma_sound_group* pGroup); +MA_API void ma_sound_group_set_start_time_in_pcm_frames(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInFrames); +MA_API void ma_sound_group_set_start_time_in_milliseconds(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInMilliseconds); +MA_API void ma_sound_group_set_stop_time_in_pcm_frames(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInFrames); +MA_API void ma_sound_group_set_stop_time_in_milliseconds(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInMilliseconds); +MA_API ma_bool32 ma_sound_group_is_playing(const ma_sound_group* pGroup); +MA_API ma_uint64 ma_sound_group_get_time_in_pcm_frames(const ma_sound_group* pGroup); +#endif /* MA_NO_ENGINE */ + #ifdef __cplusplus } #endif diff --git a/miniaudio.h b/miniaudio.h index 970feef5..04246340 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -1,6 +1,6 @@ /* Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file. -miniaudio - v0.11.0 - TBD +miniaudio - v0.11.0 - 2021-12-18 David Reid - mackron@gmail.com @@ -88904,7 +88904,7 @@ issues with certain devices and configurations. These can be individually enable /* REVISION HISTORY ================ -v0.11.0 - TBD +v0.11.0 - 2021-12-18 - Add a node graph system for advanced mixing and effect processing. - Add a resource manager for loading and streaming sounds. - Add a high level engine API for sound management and mixing. This wraps around the node graph