From 7c9d4969011ab795eede3a8ea552636860dc24f7 Mon Sep 17 00:00:00 2001 From: Ravachol Date: Sun, 19 Nov 2023 01:08:15 +0100 Subject: [PATCH] uses miniaudio decoder for wav, flac and mp3. --- Makefile | 2 +- README.md | 7 +- include/miniaudio/miniaudio.h | 2816 ++++++++++++++++++++++----------- src/file.c | 2 +- src/kew.c | 16 +- src/mpris.c | 2 +- src/player.c | 10 +- src/playerops.c | 24 +- src/settings.c | 2 +- src/songloader.c | 14 +- src/songloader.h | 1 + src/soundbuiltin.c | 192 +++ src/soundbuiltin.h | 13 + src/soundcommon.c | 409 +++++ src/soundcommon.h | 189 +++ src/soundgapless.c | 430 +---- src/soundgapless.h | 71 +- src/soundpcm.c | 225 +++ src/soundpcm.h | 12 + src/visuals.c | 209 ++- 20 files changed, 3217 insertions(+), 1429 deletions(-) create mode 100644 src/soundbuiltin.c create mode 100644 src/soundbuiltin.h create mode 100644 src/soundcommon.c create mode 100644 src/soundcommon.h create mode 100644 src/soundpcm.c create mode 100644 src/soundpcm.h diff --git a/Makefile b/Makefile index f5359b5..8511d99 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ LIBS = -lpthread -lrt -pthread -lm -lfreeimage -lglib-2.0 $(shell $(PKG_CONFIG) OBJDIR = src/obj PREFIX = /usr -SRCS = src/mpris.c src/playerops.c src/volume.c src/cutils.c src/soundgapless.c src/songloader.c src/file.c src/chafafunc.c src/cache.c src/metadata.c src/playlist.c src/stringfunc.c src/term.c src/settings.c src/player.c src/albumart.c src/visuals.c src/kew.c +SRCS = src/soundcommon.c src/soundbuiltin.c src/soundpcm.c src/mpris.c src/playerops.c src/volume.c src/cutils.c src/soundgapless.c src/songloader.c src/file.c src/chafafunc.c src/cache.c src/metadata.c src/playlist.c src/stringfunc.c src/term.c src/settings.c src/player.c src/albumart.c src/visuals.c src/kew.c OBJS = $(SRCS:src/%.c=$(OBJDIR)/%.o) MAN_PAGE = kew.1 diff --git a/README.md b/README.md index 8417153..8b1a0f7 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,6 @@ [![Debian package](https://img.shields.io/debian/v/kew/sid?color=red&label=debian&logo=debian&style=for-the-badge)](https://packages.debian.org/sid/kew) [![GitHub license](https://img.shields.io/github/license/ravachol/kew?color=333333&style=for-the-badge)](https://github.com/ravachol/kew/blob/master/LICENSE) - - Listen to music in the terminal.
@@ -14,15 +12,14 @@ Listen to music in the terminal. kew (/kjuː/) is a command-line music player for Linux. - - ## Features * Search a music library with partial titles. * Creates a playlist based on a matched directory. * Display album covers as ASCII art or as a normal image. * Control the player with previous, next and pause. - * Has gapless playback and supports 24-bit audio. + * Has gapless playback (between files of the same format and type) and supports 24-bit audio. + * Does not yet work well with very long audio files that are not mp3, wav or flac. ## Name Change diff --git a/include/miniaudio/miniaudio.h b/include/miniaudio/miniaudio.h index ded0b31..47332e1 100644 --- a/include/miniaudio/miniaudio.h +++ b/include/miniaudio/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.17 - 2023-05-27 +miniaudio - v0.11.21 - 2023-11-15 David Reid - mackron@gmail.com @@ -87,7 +87,7 @@ device on the stack, but you could allocate it on the heap if that suits your si // Do something here. Probably your program's main loop. - ma_device_uninit(&device); // This will stop the device so no need to do that manually. + ma_device_uninit(&device); return 0; } ``` @@ -538,6 +538,20 @@ you'll need to disable run-time linking with `MA_NO_RUNTIME_LINKING` and link wi The Emscripten build emits Web Audio JavaScript directly and should compile cleanly out of the box. You cannot use `-std=c*` compiler flags, nor `-ansi`. +You can enable the use of AudioWorkets by defining `MA_ENABLE_AUDIO_WORKLETS` and then compiling +with the following options: + + -sAUDIO_WORKLET=1 -sWASM_WORKERS=1 -sASYNCIFY + +An example for compiling with AudioWorklet support might look like this: + + emcc program.c -o bin/program.html -DMA_ENABLE_AUDIO_WORKLETS -sAUDIO_WORKLET=1 -sWASM_WORKERS=1 -sASYNCIFY + +To run locally, you'll need to use emrun: + + emrun bin/program.html + + 2.7. Build Options ------------------ @@ -1661,7 +1675,7 @@ an example for initializing a data source: // ... - ma_resource_manager_data_source_uninit(pResourceManager, &dataSource); + ma_resource_manager_data_source_uninit(&dataSource); ``` The `flags` parameter specifies how you want to perform loading of the sound file. It can be a @@ -1898,10 +1912,10 @@ once after the other: ```c ma_resource_manager_data_source_init(pResourceManager, "my_file", ..., &myDataBuffer0); // Refcount = 1. Initial load. - ma_resource_manager_data_source_uninit(pResourceManager, &myDataBuffer0); // Refcount = 0. Unloaded. + ma_resource_manager_data_source_uninit(&myDataBuffer0); // Refcount = 0. Unloaded. ma_resource_manager_data_source_init(pResourceManager, "my_file", ..., &myDataBuffer1); // Refcount = 1. Reloaded because previous uninit() unloaded it. - ma_resource_manager_data_source_uninit(pResourceManager, &myDataBuffer1); // Refcount = 0. Unloaded. + ma_resource_manager_data_source_uninit(&myDataBuffer1); // Refcount = 0. Unloaded. ``` A binary search tree (BST) is used for storing data buffers as it has good balance between @@ -2661,9 +2675,16 @@ outputting any audio data. To output audio data, use `ma_encoder_write_pcm_frame example below: ```c - framesWritten = ma_encoder_write_pcm_frames(&encoder, pPCMFramesToWrite, framesToWrite); + ma_uint64 framesWritten; + result = ma_encoder_write_pcm_frames(&encoder, pPCMFramesToWrite, framesToWrite, &framesWritten); + if (result != MA_SUCCESS) { + ... handle error ... + } ``` +The `framesWritten` variable will contain the number of PCM frames that were actually written. This +is optionally and you can pass in `NULL` if you need this. + Encoders must be uninitialized with `ma_encoder_uninit()`. @@ -3395,7 +3416,7 @@ miniaudio supports reading from a buffer of raw audio data via the `ma_audio_buf read from memory that's managed by the application, but can also handle the memory management for you internally. Memory management is flexible and should support most use cases. -Audio buffers are initialised using the standard configuration system used everywhere in miniaudio: +Audio buffers are initialized using the standard configuration system used everywhere in miniaudio: ```c ma_audio_buffer_config config = ma_audio_buffer_config_init( @@ -3702,7 +3723,7 @@ extern "C" { #define MA_VERSION_MAJOR 0 #define MA_VERSION_MINOR 11 -#define MA_VERSION_REVISION 17 +#define MA_VERSION_REVISION 21 #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__) @@ -3930,36 +3951,46 @@ typedef ma_uint16 wchar_t; #define MA_NO_INLINE #endif -#if !defined(MA_API) - #if defined(MA_DLL) - #if defined(_WIN32) - #define MA_DLL_IMPORT __declspec(dllimport) - #define MA_DLL_EXPORT __declspec(dllexport) - #define MA_DLL_PRIVATE static +/* MA_DLL is not officially supported. You're on your own if you want to use this. */ +#if defined(MA_DLL) + #if defined(_WIN32) + #define MA_DLL_IMPORT __declspec(dllimport) + #define MA_DLL_EXPORT __declspec(dllexport) + #define MA_DLL_PRIVATE static + #else + #if defined(__GNUC__) && __GNUC__ >= 4 + #define MA_DLL_IMPORT __attribute__((visibility("default"))) + #define MA_DLL_EXPORT __attribute__((visibility("default"))) + #define MA_DLL_PRIVATE __attribute__((visibility("hidden"))) #else - #if defined(__GNUC__) && __GNUC__ >= 4 - #define MA_DLL_IMPORT __attribute__((visibility("default"))) - #define MA_DLL_EXPORT __attribute__((visibility("default"))) - #define MA_DLL_PRIVATE __attribute__((visibility("hidden"))) - #else - #define MA_DLL_IMPORT - #define MA_DLL_EXPORT - #define MA_DLL_PRIVATE static - #endif + #define MA_DLL_IMPORT + #define MA_DLL_EXPORT + #define MA_DLL_PRIVATE static #endif + #endif +#endif +#if !defined(MA_API) + #if defined(MA_DLL) #if defined(MINIAUDIO_IMPLEMENTATION) || defined(MA_IMPLEMENTATION) #define MA_API MA_DLL_EXPORT #else #define MA_API MA_DLL_IMPORT #endif - #define MA_PRIVATE MA_DLL_PRIVATE #else #define MA_API extern + #endif +#endif + +#if !defined(MA_STATIC) + #if defined(MA_DLL) + #define MA_PRIVATE MA_DLL_PRIVATE + #else #define MA_PRIVATE static #endif #endif + /* SIMD alignment in bytes. Currently set to 32 bytes in preparation for future AVX optimizations. */ #define MA_SIMD_ALIGNMENT 32 @@ -4243,7 +4274,7 @@ typedef enum ma_standard_sample_rate_192000 = 192000, ma_standard_sample_rate_16000 = 16000, /* Extreme lows */ - ma_standard_sample_rate_11025 = 11250, + ma_standard_sample_rate_11025 = 11025, ma_standard_sample_rate_8000 = 8000, ma_standard_sample_rate_352800 = 352800, /* Extreme highs */ @@ -5033,13 +5064,14 @@ typedef struct 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_int64 cursorInFrames; /* The current time in frames. Incremented by ma_fader_process_pcm_frames(). Signed because it'll be offset by startOffsetInFrames in set_fade_ex(). */ } 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 void ma_fader_set_fade_ex(ma_fader* pFader, float volumeBeg, float volumeEnd, ma_uint64 lengthInFrames, ma_int64 startOffsetInFrames); MA_API float ma_fader_get_current_volume(const ma_fader* pFader); @@ -5365,7 +5397,7 @@ MA_API void ma_resampler_uninit(ma_resampler* pResampler, const ma_allocation_ca /* Converts the given input data. -Both the input and output frames must be in the format specified in the config when the resampler was initilized. +Both the input and output frames must be in the format specified in the config when the resampler was initialized. On input, [pFrameCountOut] contains the number of output frames to process. On output it contains the number of output frames that were actually processed, which may be less than the requested amount which will happen if there's not enough input data. You can use @@ -6684,7 +6716,8 @@ typedef enum ma_device_notification_type_stopped, ma_device_notification_type_rerouted, ma_device_notification_type_interruption_began, - ma_device_notification_type_interruption_ended + ma_device_notification_type_interruption_ended, + ma_device_notification_type_unlocked } ma_device_notification_type; typedef struct @@ -7010,7 +7043,7 @@ struct ma_device_config ma_uint32 periods; ma_performance_profile performanceProfile; 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_bool8 noClip; /* When set to true, the contents of the output buffer passed into the data callback will not be clipped after returning. Only applies when the playback sample format is f32. */ ma_bool8 noDisableDenormals; /* Do not disable denormals when firing the data callback. */ ma_bool8 noFixedSizedCallback; /* Disables strict fixed-sized data callbacks. Setting this to true will result in the period size being treated only as a hint to the backend. This is an optimization for those who don't need fixed sized callbacks. */ ma_device_data_proc dataCallback; @@ -7084,7 +7117,7 @@ struct ma_device_config /* -The callback for handling device enumeration. This is fired from `ma_context_enumerated_devices()`. +The callback for handling device enumeration. This is fired from `ma_context_enumerate_devices()`. Parameters @@ -7658,6 +7691,8 @@ struct ma_context ma_proc RegOpenKeyExA; ma_proc RegCloseKey; ma_proc RegQueryValueExA; + + /*HRESULT*/ long CoInitializeResult; } win32; #endif #ifdef MA_POSIX @@ -7937,21 +7972,12 @@ struct ma_device struct { /* AudioWorklets path. */ - /* EMSCRIPTEN_WEBAUDIO_T */ int audioContextPlayback; - /* EMSCRIPTEN_WEBAUDIO_T */ int audioContextCapture; - /* EMSCRIPTEN_AUDIO_WORKLET_NODE_T */ int workletNodePlayback; - /* EMSCRIPTEN_AUDIO_WORKLET_NODE_T */ int workletNodeCapture; - size_t intermediaryBufferSizeInFramesPlayback; - size_t intermediaryBufferSizeInFramesCapture; - float* pIntermediaryBufferPlayback; - float* pIntermediaryBufferCapture; - void* pStackBufferPlayback; - void* pStackBufferCapture; - ma_bool32 isInitialized; - - /* ScriptProcessorNode path. */ - int indexPlayback; /* We use a factory on the JavaScript side to manage devices and use an index for JS/C interop. */ - int indexCapture; + /* EMSCRIPTEN_WEBAUDIO_T */ int audioContext; + /* EMSCRIPTEN_WEBAUDIO_T */ int audioWorklet; + float* pIntermediaryBuffer; + void* pStackBuffer; + ma_result initResult; /* Set to MA_BUSY while initialization is in progress. */ + int deviceIndex; /* We store the device in a list on the JavaScript side. This is used to map our C object to the JS object. */ } webaudio; #endif #ifdef MA_SUPPORT_NULL @@ -8601,8 +8627,8 @@ then be set directly on the structure. Below are the members of the `ma_device_c callback will write to every sample in the output buffer, or if you are doing your own clearing. noClip - When set to true, the contents of the output buffer passed into the data callback will be clipped after returning. When set to false (default), the - 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 + When set to true, the 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 to clip. When set to false (default), the contents of the output buffer passed into the data callback will be clipped after returning. This only applies when the playback sample format is f32. noDisableDenormals @@ -9115,8 +9141,6 @@ speakers or received from the microphone which can in turn result in de-syncs. Do not call this in any callback. -This will be called implicitly by `ma_device_uninit()`. - See Also -------- @@ -10153,7 +10177,7 @@ MA_API ma_noise_config ma_noise_config_init(ma_format format, ma_uint32 channels typedef struct { - ma_data_source_vtable ds; + ma_data_source_base ds; ma_noise_config config; ma_lcg lcg; union @@ -10551,7 +10575,7 @@ 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 seperate frame counts: one for input, and one for output. + they take two separate 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`. @@ -11056,6 +11080,15 @@ typedef struct 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. */ + /* When setting a fade, it's not done immediately in ma_sound_set_fade(). It's deferred to the audio thread which means we need to store the settings here. */ + struct + { + ma_atomic_float volumeBeg; + ma_atomic_float volumeEnd; + ma_atomic_uint64 fadeLengthInFrames; /* <-- Defaults to (~(ma_uint64)0) which is used to indicate that no fade should be applied. */ + ma_atomic_uint64 absoluteGlobalTimeInFrames; /* <-- The time to start the fade. */ + } fadeSettings; + /* Memory management. */ ma_bool8 _ownsHeap; void* _pHeap; @@ -11136,6 +11169,8 @@ typedef ma_sound ma_sound_group; MA_API ma_sound_group_config ma_sound_group_config_init(void); /* Deprecated. Will be removed in version 0.12. Use ma_sound_config_2() instead. */ MA_API ma_sound_group_config ma_sound_group_config_init_2(ma_engine* pEngine); /* Will be renamed to ma_sound_config_init() in version 0.12. */ +typedef void (* ma_engine_process_proc)(void* pUserData, float* pFramesOut, ma_uint64 frameCount); + typedef struct { #if !defined(MA_NO_RESOURCE_MANAGER) @@ -11145,6 +11180,7 @@ typedef struct 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. */ + ma_device_data_proc dataCallback; /* Can be null. Can be used to provide a custom device data callback. */ ma_device_notification_proc notificationCallback; #endif ma_log* pLog; /* When set to NULL, will use the context's log. */ @@ -11161,6 +11197,8 @@ typedef struct 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_process_proc onProcess; /* Fired at the end of each call to ma_engine_read_pcm_frames(). For engine's that manage their own internal device (the default configuration), this will be fired from the audio thread, and you do not need to call ma_engine_read_pcm_frames() manually in order to trigger this. */ + void* pProcessUserData; /* User data that's passed into onProcess. */ } ma_engine_config; MA_API ma_engine_config ma_engine_config_init(void); @@ -11188,6 +11226,8 @@ struct ma_engine ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. */ ma_uint32 defaultVolumeSmoothTimeInPCMFrames; ma_mono_expansion_mode monoExpansionMode; + ma_engine_process_proc onProcess; + void* pProcessUserData; }; MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEngine); @@ -11212,7 +11252,9 @@ 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 float ma_engine_get_volume(ma_engine* pEngine); MA_API ma_result ma_engine_set_gain_db(ma_engine* pEngine, float gainDB); +MA_API float ma_engine_get_gain_db(ma_engine* pEngine); 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); @@ -11246,6 +11288,8 @@ 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 ma_result ma_sound_stop_with_fade_in_pcm_frames(ma_sound* pSound, ma_uint64 fadeLengthInFrames); /* Will overwrite any scheduled stop and fade. */ +MA_API ma_result ma_sound_stop_with_fade_in_milliseconds(ma_sound* pSound, ma_uint64 fadeLengthInFrames); /* Will overwrite any scheduled stop and fade. */ 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); @@ -11288,13 +11332,18 @@ MA_API void ma_sound_set_directional_attenuation_factor(ma_sound* pSound, float MA_API float ma_sound_get_directional_attenuation_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 void ma_sound_set_fade_start_in_pcm_frames(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames, ma_uint64 absoluteGlobalTimeInFrames); +MA_API void ma_sound_set_fade_start_in_milliseconds(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds, ma_uint64 absoluteGlobalTimeInMilliseconds); MA_API float ma_sound_get_current_fade_volume(const 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 void ma_sound_set_stop_time_with_fade_in_pcm_frames(ma_sound* pSound, ma_uint64 stopAbsoluteGlobalTimeInFrames, ma_uint64 fadeLengthInFrames); +MA_API void ma_sound_set_stop_time_with_fade_in_milliseconds(ma_sound* pSound, ma_uint64 stopAbsoluteGlobalTimeInMilliseconds, ma_uint64 fadeLengthInMilliseconds); 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 ma_uint64 ma_sound_get_time_in_milliseconds(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); @@ -12195,7 +12244,7 @@ static MA_INLINE void ma_zero_memory_default(void* p, size_t sz) #define ma_abs(x) (((x) > 0) ? (x) : -(x)) #define ma_clamp(x, lo, hi) (ma_max(lo, ma_min(x, hi))) #define ma_offset_ptr(p, offset) (((ma_uint8*)(p)) + (offset)) -#define ma_align(x, a) ((x + (a-1)) & ~(a-1)) +#define ma_align(x, a) (((x) + ((a)-1)) & ~((a)-1)) #define ma_align_64(x) ma_align(x, 8) #define ma_buffer_frame_capacity(buffer, channels, format) (sizeof(buffer) / ma_get_bytes_per_sample(format) / (channels)) @@ -13596,7 +13645,7 @@ MA_API ma_result ma_log_postv(ma_log* pLog, ma_uint32 level, const char* pFormat /* First try formatting into our fixed sized stack allocated buffer. If this is too small we'll fallback to a heap allocation. */ length = vsnprintf(pFormattedMessageStack, sizeof(pFormattedMessageStack), pFormat, args); if (length < 0) { - return MA_INVALID_OPERATION; /* An error occured when trying to convert the buffer. */ + return MA_INVALID_OPERATION; /* An error occurred when trying to convert the buffer. */ } if ((size_t)length < sizeof(pFormattedMessageStack)) { @@ -16137,7 +16186,15 @@ static void ma_thread_wait__posix(ma_thread* pThread) static ma_result ma_mutex_init__posix(ma_mutex* pMutex) { - int result = pthread_mutex_init((pthread_mutex_t*)pMutex, NULL); + int result; + + if (pMutex == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pMutex); + + result = pthread_mutex_init((pthread_mutex_t*)pMutex, NULL); if (result != 0) { return ma_result_from_errno(result); } @@ -17764,7 +17821,7 @@ MA_API ma_handle ma_dlopen(ma_log* pLog, const char* filename) #ifdef MA_WIN32 /* From MSDN: Desktop applications cannot use LoadPackagedLibrary; if a desktop application calls this function it fails with APPMODEL_ERROR_NO_PACKAGE.*/ - #if !defined(MA_WIN32_UWP) + #if !defined(MA_WIN32_UWP) || !(defined(WINAPI_FAMILY) && ((defined(WINAPI_FAMILY_PHONE_APP) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP))) handle = (ma_handle)LoadLibraryA(filename); #else /* *sigh* It appears there is no ANSI version of LoadPackagedLibrary()... */ @@ -18409,7 +18466,7 @@ Timing *******************************************************************************/ #if defined(MA_WIN32) && !defined(MA_POSIX) static LARGE_INTEGER g_ma_TimerFrequency; /* <-- Initialized to zero since it's static. */ - void ma_timer_init(ma_timer* pTimer) + static void ma_timer_init(ma_timer* pTimer) { LARGE_INTEGER counter; @@ -18421,7 +18478,7 @@ Timing pTimer->counter = counter.QuadPart; } - double ma_timer_get_time_in_seconds(ma_timer* pTimer) + static double ma_timer_get_time_in_seconds(ma_timer* pTimer) { LARGE_INTEGER counter; if (!QueryPerformanceCounter(&counter)) { @@ -18594,30 +18651,31 @@ static void ma_device__on_notification(ma_device_notification notification) } } -void ma_device__on_notification_started(ma_device* pDevice) +static void ma_device__on_notification_started(ma_device* pDevice) { ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_started)); } -void ma_device__on_notification_stopped(ma_device* pDevice) +static void ma_device__on_notification_stopped(ma_device* pDevice) { ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_stopped)); } -void ma_device__on_notification_rerouted(ma_device* pDevice) +/* Not all platforms support reroute notifications. */ +#if !defined(MA_EMSCRIPTEN) +static void ma_device__on_notification_rerouted(ma_device* pDevice) { ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_rerouted)); } +#endif -void ma_device__on_notification_interruption_began(ma_device* pDevice) -{ - ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_interruption_began)); -} - -void ma_device__on_notification_interruption_ended(ma_device* pDevice) +#if defined(MA_EMSCRIPTEN) +EMSCRIPTEN_KEEPALIVE +void ma_device__on_notification_unlocked(ma_device* pDevice) { - ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_interruption_ended)); + ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_unlocked)); } +#endif static void ma_device__on_data_inner(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) @@ -19072,10 +19130,10 @@ static MA_INLINE void ma_device__set_state(ma_device* pDevice, ma_device_state n #if defined(MA_WIN32) - GUID MA_GUID_KSDATAFORMAT_SUBTYPE_PCM = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; - GUID MA_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {0x00000003, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; - /*GUID MA_GUID_KSDATAFORMAT_SUBTYPE_ALAW = {0x00000006, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};*/ - /*GUID MA_GUID_KSDATAFORMAT_SUBTYPE_MULAW = {0x00000007, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};*/ + static GUID MA_GUID_KSDATAFORMAT_SUBTYPE_PCM = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; + static GUID MA_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {0x00000003, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; + /*static GUID MA_GUID_KSDATAFORMAT_SUBTYPE_ALAW = {0x00000006, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};*/ + /*static GUID MA_GUID_KSDATAFORMAT_SUBTYPE_MULAW = {0x00000007, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};*/ #endif @@ -22119,7 +22177,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device MA_COPY_MEMORY(&wf, pNativeFormat, cbSize); } - + result = MA_SUCCESS; } @@ -23227,7 +23285,7 @@ static ma_result ma_device_read__wasapi(ma_device* pDevice, void* pFrames, ma_ui /* At this point we should be able to loop back to the start of the loop and try retrieving a data buffer again. */ } else { - /* An error occured and we need to abort. */ + /* An error occurred and we need to abort. */ ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for reading from the device. HRESULT = %d. Stopping device.\n", (int)hr); result = ma_result_from_HRESULT(hr); break; @@ -23449,6 +23507,39 @@ static ma_result ma_context_init__wasapi(ma_context* pContext, const ma_context_ MA_ZERO_OBJECT(&pContext->wasapi); + + #if defined(MA_WIN32_UWP) + { + /* Link to mmdevapi so we can get access to ActivateAudioInterfaceAsync(). */ + pContext->wasapi.hMMDevapi = ma_dlopen(ma_context_get_log(pContext), "mmdevapi.dll"); + if (pContext->wasapi.hMMDevapi) { + pContext->wasapi.ActivateAudioInterfaceAsync = ma_dlsym(ma_context_get_log(pContext), pContext->wasapi.hMMDevapi, "ActivateAudioInterfaceAsync"); + if (pContext->wasapi.ActivateAudioInterfaceAsync == NULL) { + ma_dlclose(ma_context_get_log(pContext), pContext->wasapi.hMMDevapi); + return MA_NO_BACKEND; /* ActivateAudioInterfaceAsync() could not be loaded. */ + } + } else { + return MA_NO_BACKEND; /* Failed to load mmdevapi.dll which is required for ActivateAudioInterfaceAsync() */ + } + } + #endif + + /* Optionally use the Avrt API to specify the audio thread's latency sensitivity requirements */ + pContext->wasapi.hAvrt = ma_dlopen(ma_context_get_log(pContext), "avrt.dll"); + if (pContext->wasapi.hAvrt) { + pContext->wasapi.AvSetMmThreadCharacteristicsA = ma_dlsym(ma_context_get_log(pContext), pContext->wasapi.hAvrt, "AvSetMmThreadCharacteristicsA"); + pContext->wasapi.AvRevertMmThreadcharacteristics = ma_dlsym(ma_context_get_log(pContext), pContext->wasapi.hAvrt, "AvRevertMmThreadCharacteristics"); + + /* If either function could not be found, disable use of avrt entirely. */ + if (!pContext->wasapi.AvSetMmThreadCharacteristicsA || !pContext->wasapi.AvRevertMmThreadcharacteristics) { + pContext->wasapi.AvSetMmThreadCharacteristicsA = NULL; + pContext->wasapi.AvRevertMmThreadcharacteristics = NULL; + ma_dlclose(ma_context_get_log(pContext), pContext->wasapi.hAvrt); + pContext->wasapi.hAvrt = NULL; + } + } + + /* Annoyingly, WASAPI does not allow you to release an IAudioClient object from a different thread than the one that retrieved it with GetService(). This can result in a deadlock in two @@ -23492,41 +23583,6 @@ static ma_result ma_context_init__wasapi(ma_context* pContext, const ma_context_ ma_mutex_uninit(&pContext->wasapi.commandLock); return result; } - - #if defined(MA_WIN32_UWP) - { - /* Link to mmdevapi so we can get access to ActivateAudioInterfaceAsync(). */ - pContext->wasapi.hMMDevapi = ma_dlopen(ma_context_get_log(pContext), "mmdevapi.dll"); - if (pContext->wasapi.hMMDevapi) { - pContext->wasapi.ActivateAudioInterfaceAsync = ma_dlsym(ma_context_get_log(pContext), pContext->wasapi.hMMDevapi, "ActivateAudioInterfaceAsync"); - if (pContext->wasapi.ActivateAudioInterfaceAsync == NULL) { - ma_semaphore_uninit(&pContext->wasapi.commandSem); - ma_mutex_uninit(&pContext->wasapi.commandLock); - ma_dlclose(ma_context_get_log(pContext), pContext->wasapi.hMMDevapi); - return MA_NO_BACKEND; /* ActivateAudioInterfaceAsync() could not be loaded. */ - } - } else { - ma_semaphore_uninit(&pContext->wasapi.commandSem); - ma_mutex_uninit(&pContext->wasapi.commandLock); - return MA_NO_BACKEND; /* Failed to load mmdevapi.dll which is required for ActivateAudioInterfaceAsync() */ - } - } - #endif - - /* Optionally use the Avrt API to specify the audio thread's latency sensitivity requirements */ - pContext->wasapi.hAvrt = ma_dlopen(ma_context_get_log(pContext), "avrt.dll"); - if (pContext->wasapi.hAvrt) { - pContext->wasapi.AvSetMmThreadCharacteristicsA = ma_dlsym(ma_context_get_log(pContext), pContext->wasapi.hAvrt, "AvSetMmThreadCharacteristicsA"); - pContext->wasapi.AvRevertMmThreadcharacteristics = ma_dlsym(ma_context_get_log(pContext), pContext->wasapi.hAvrt, "AvRevertMmThreadCharacteristics"); - - /* If either function could not be found, disable use of avrt entirely. */ - if (!pContext->wasapi.AvSetMmThreadCharacteristicsA || !pContext->wasapi.AvRevertMmThreadcharacteristics) { - pContext->wasapi.AvSetMmThreadCharacteristicsA = NULL; - pContext->wasapi.AvRevertMmThreadcharacteristics = NULL; - ma_dlclose(ma_context_get_log(pContext), pContext->wasapi.hAvrt); - pContext->wasapi.hAvrt = NULL; - } - } } @@ -28002,6 +28058,12 @@ static ma_result ma_device_start__alsa(ma_device* pDevice) static ma_result ma_device_stop__alsa(ma_device* pDevice) { + /* + The stop callback will get called on the worker thread after read/write__alsa() has returned. At this point there is + a small chance that our wakeupfd has not been cleared. We'll clear that out now if applicable. + */ + int resultPoll; + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping capture device...\n"); ((ma_snd_pcm_drop_proc)pDevice->pContext->alsa.snd_pcm_drop)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture); @@ -28014,6 +28076,13 @@ static ma_result ma_device_stop__alsa(ma_device* pDevice) } else { ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing capture device successful.\n"); } + + /* Clear the wakeupfd. */ + resultPoll = poll((struct pollfd*)pDevice->alsa.pPollDescriptorsCapture, 1, 0); + if (resultPoll > 0) { + ma_uint64 t; + read(((struct pollfd*)pDevice->alsa.pPollDescriptorsCapture)[0].fd, &t, sizeof(t)); + } } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { @@ -28028,6 +28097,14 @@ static ma_result ma_device_stop__alsa(ma_device* pDevice) } else { ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing playback device successful.\n"); } + + /* Clear the wakeupfd. */ + resultPoll = poll((struct pollfd*)pDevice->alsa.pPollDescriptorsPlayback, 1, 0); + if (resultPoll > 0) { + ma_uint64 t; + read(((struct pollfd*)pDevice->alsa.pPollDescriptorsPlayback)[0].fd, &t, sizeof(t)); + } + } return MA_SUCCESS; @@ -28040,7 +28117,7 @@ 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) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] poll() failed."); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] poll() failed.\n"); return ma_result_from_errno(errno); } @@ -28053,7 +28130,7 @@ static ma_result ma_device_wait__alsa(ma_device* pDevice, ma_snd_pcm_t* pPCM, st ma_uint64 t; 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."); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] read() failed.\n"); return ma_result_from_errno(errno); } @@ -28067,13 +28144,17 @@ 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) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_poll_descriptors_revents() failed."); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_poll_descriptors_revents() failed.\n"); return ma_result_from_errno(-resultALSA); } if ((revents & POLLERR) != 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] POLLERR detected."); - return ma_result_from_errno(errno); + ma_snd_pcm_state_t state = ((ma_snd_pcm_state_proc)pDevice->pContext->alsa.snd_pcm_state)(pPCM); + if (state == MA_SND_PCM_STATE_XRUN) { + /* The PCM is in a xrun state. This will be recovered from at a higher level. We can disregard this. */ + } else { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[ALSA] POLLERR detected. status = %d\n", ((ma_snd_pcm_state_proc)pDevice->pContext->alsa.snd_pcm_state)(pPCM)); + } } if ((revents & requiredEvent) == requiredEvent) { @@ -30199,11 +30280,6 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi /* Notes for PulseAudio: - - We're always using native format/channels/rate regardless of whether or not PulseAudio - supports the format directly through their own data conversion system. I'm doing this to - reduce as much variability from the PulseAudio side as possible because it's seems to be - extremely unreliable at everything it does. - - When both the period size in frames and milliseconds are 0, we default to miniaudio's default buffer sizes rather than leaving it up to PulseAudio because I don't trust PulseAudio to give us any kind of reasonable latency by default. @@ -30292,6 +30368,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi if (pDescriptorCapture->sampleRate != 0) { ss.rate = pDescriptorCapture->sampleRate; } + streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY; if (ma_format_from_pulse(ss.format) == ma_format_unknown) { if (ma_is_little_endian()) { @@ -30299,14 +30376,17 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi } else { ss.format = MA_PA_SAMPLE_FLOAT32BE; } + streamFlags |= MA_PA_STREAM_FIX_FORMAT; ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.format not supported by miniaudio. Defaulting to PA_SAMPLE_FLOAT32.\n"); } if (ss.rate == 0) { ss.rate = MA_DEFAULT_SAMPLE_RATE; + streamFlags |= MA_PA_STREAM_FIX_RATE; ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.rate = 0. Defaulting to %d.\n", ss.rate); } if (ss.channels == 0) { ss.channels = MA_DEFAULT_CHANNELS; + streamFlags |= MA_PA_STREAM_FIX_CHANNELS; ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.channels = 0. Defaulting to %d.\n", ss.channels); } @@ -30335,7 +30415,6 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi /* Connect after we've got all of our internal state set up. */ - streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; if (devCapture != NULL) { streamFlags |= MA_PA_STREAM_DONT_MOVE; } @@ -30438,20 +30517,24 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi ss.rate = pDescriptorPlayback->sampleRate; } + streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY; if (ma_format_from_pulse(ss.format) == ma_format_unknown) { if (ma_is_little_endian()) { ss.format = MA_PA_SAMPLE_FLOAT32LE; } else { ss.format = MA_PA_SAMPLE_FLOAT32BE; } + streamFlags |= MA_PA_STREAM_FIX_FORMAT; ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.format not supported by miniaudio. Defaulting to PA_SAMPLE_FLOAT32.\n"); } if (ss.rate == 0) { ss.rate = MA_DEFAULT_SAMPLE_RATE; + streamFlags |= MA_PA_STREAM_FIX_RATE; ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.rate = 0. Defaulting to %d.\n", ss.rate); } if (ss.channels == 0) { ss.channels = MA_DEFAULT_CHANNELS; + streamFlags |= MA_PA_STREAM_FIX_CHANNELS; ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.channels = 0. Defaulting to %d.\n", ss.channels); } @@ -30484,7 +30567,6 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi /* Connect after we've got all of our internal state set up. */ - streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; if (devPlayback != NULL) { streamFlags |= MA_PA_STREAM_DONT_MOVE; } @@ -31796,6 +31878,18 @@ size, allocate a block of memory of that size and then call AudioObjectGetProper AudioDeviceID's so just do "dataSize/sizeof(AudioDeviceID)" to know the device count. */ +#if defined(MA_APPLE_MOBILE) +static void ma_device__on_notification_interruption_began(ma_device* pDevice) +{ + ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_interruption_began)); +} + +static void ma_device__on_notification_interruption_ended(ma_device* pDevice) +{ + ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_interruption_ended)); +} +#endif + static ma_result ma_result_from_OSStatus(OSStatus status) { switch (status) @@ -32712,9 +32806,9 @@ static ma_result ma_find_best_format__coreaudio(ma_context* pContext, AudioObjec hasSupportedFormat = MA_FALSE; for (iFormat = 0; iFormat < deviceFormatDescriptionCount; ++iFormat) { - ma_format format; - ma_result formatResult = ma_format_from_AudioStreamBasicDescription(&pDeviceFormatDescriptions[iFormat].mFormat, &format); - if (formatResult == MA_SUCCESS && format != ma_format_unknown) { + ma_format formatFromDescription; + ma_result formatResult = ma_format_from_AudioStreamBasicDescription(&pDeviceFormatDescriptions[iFormat].mFormat, &formatFromDescription); + if (formatResult == MA_SUCCESS && formatFromDescription != ma_format_unknown) { hasSupportedFormat = MA_TRUE; bestDeviceFormatSoFar = pDeviceFormatDescriptions[iFormat].mFormat; break; @@ -34765,7 +34859,7 @@ static ma_result ma_context_init__coreaudio(ma_context* pContext, const ma_conte #endif #if !defined(MA_NO_RUNTIME_LINKING) && !defined(MA_APPLE_MOBILE) - pContext->coreaudio.hCoreFoundation = ma_dlopen(ma_context_get_log(pContext), "CoreFoundation.framework/CoreFoundation"); + pContext->coreaudio.hCoreFoundation = ma_dlopen(ma_context_get_log(pContext), "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"); if (pContext->coreaudio.hCoreFoundation == NULL) { return MA_API_NOT_FOUND; } @@ -34774,7 +34868,7 @@ static ma_result ma_context_init__coreaudio(ma_context* pContext, const ma_conte pContext->coreaudio.CFRelease = ma_dlsym(ma_context_get_log(pContext), pContext->coreaudio.hCoreFoundation, "CFRelease"); - pContext->coreaudio.hCoreAudio = ma_dlopen(ma_context_get_log(pContext), "CoreAudio.framework/CoreAudio"); + pContext->coreaudio.hCoreAudio = ma_dlopen(ma_context_get_log(pContext), "/System/Library/Frameworks/CoreAudio.framework/CoreAudio"); if (pContext->coreaudio.hCoreAudio == NULL) { ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hCoreFoundation); return MA_API_NOT_FOUND; @@ -34792,7 +34886,7 @@ static ma_result ma_context_init__coreaudio(ma_context* pContext, const ma_conte The way it'll work is that it'll first try AudioUnit, and if the required symbols are not present there we'll fall back to AudioToolbox. */ - pContext->coreaudio.hAudioUnit = ma_dlopen(ma_context_get_log(pContext), "AudioUnit.framework/AudioUnit"); + pContext->coreaudio.hAudioUnit = ma_dlopen(ma_context_get_log(pContext), "/System/Library/Frameworks/AudioUnit.framework/AudioUnit"); if (pContext->coreaudio.hAudioUnit == NULL) { ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hCoreAudio); ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hCoreFoundation); @@ -34802,7 +34896,7 @@ static ma_result ma_context_init__coreaudio(ma_context* pContext, const ma_conte if (ma_dlsym(ma_context_get_log(pContext), pContext->coreaudio.hAudioUnit, "AudioComponentFindNext") == NULL) { /* Couldn't find the required symbols in AudioUnit, so fall back to AudioToolbox. */ ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hAudioUnit); - pContext->coreaudio.hAudioUnit = ma_dlopen(ma_context_get_log(pContext), "AudioToolbox.framework/AudioToolbox"); + pContext->coreaudio.hAudioUnit = ma_dlopen(ma_context_get_log(pContext), "/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox"); if (pContext->coreaudio.hAudioUnit == NULL) { ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hCoreAudio); ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hCoreFoundation); @@ -37585,7 +37679,7 @@ static ma_result ma_create_and_configure_AAudioStreamBuilder__aaudio(ma_context* anything from Android 11 and earlier. Suggestions welcome on how we might be able to make this more targetted. */ - if (pConfig->aaudio.enableCompatibilityWorkarounds && ma_android_sdk_version() > 30) { + if (!pConfig->aaudio.enableCompatibilityWorkarounds || ma_android_sdk_version() > 30) { /* AAudio is annoying when it comes to it's buffer calculation stuff because it doesn't let you retrieve the actual sample rate until after you've opened the stream. But you need to configure @@ -39252,7 +39346,7 @@ static ma_result ma_device_start__opensl(ma_device* pDevice) 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. */ + /* In playback mode (no duplex) we need to load some initial buffers. In duplex mode we need to enqueue silent buffers. */ if (pDevice->type == ma_device_type_duplex) { MA_ZERO_MEMORY(pDevice->opensl.pBufferPlayback, pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); } else { @@ -39673,101 +39767,76 @@ static ma_result ma_context_get_device_info__webaudio(ma_context* pContext, ma_d return MA_SUCCESS; } -#if !defined(MA_USE_AUDIO_WORKLETS) -static void ma_device_uninit_by_index__webaudio(ma_device* pDevice, ma_device_type deviceType, int deviceIndex) +static ma_result ma_device_uninit__webaudio(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); - EM_ASM({ - var device = miniaudio.get_device_by_index($0); - var pAllocationCallbacks = $3; - - /* Make sure all nodes are disconnected and marked for collection. */ - if (device.scriptNode !== undefined) { - device.scriptNode.onaudioprocess = function(e) {}; /* We want to reset the callback to ensure it doesn't get called after AudioContext.close() has returned. Shouldn't happen since we're disconnecting, but just to be safe... */ - device.scriptNode.disconnect(); - device.scriptNode = undefined; - } - if (device.streamNode !== undefined) { - device.streamNode.disconnect(); - device.streamNode = undefined; - } + #if defined(MA_USE_AUDIO_WORKLETS) + { + EM_ASM({ + var device = miniaudio.get_device_by_index($0); - /* - Stop the device. I think there is a chance the callback could get fired after calling this, hence why we want - to clear the callback before closing. - */ - device.webaudio.close(); - device.webaudio = undefined; + if (device.streamNode !== undefined) { + device.streamNode.disconnect(); + device.streamNode = undefined; + } + }, pDevice->webaudio.deviceIndex); - /* Can't forget to free the intermediary buffer. This is the buffer that's shared between JavaScript and C. */ - if (device.intermediaryBuffer !== undefined) { - _ma_free_emscripten(device.intermediaryBuffer, pAllocationCallbacks); - device.intermediaryBuffer = undefined; - device.intermediaryBufferView = undefined; - device.intermediaryBufferSizeInBytes = undefined; - } + emscripten_destroy_web_audio_node(pDevice->webaudio.audioWorklet); + emscripten_destroy_audio_context(pDevice->webaudio.audioContext); + ma_free(pDevice->webaudio.pStackBuffer, &pDevice->pContext->allocationCallbacks); + } + #else + { + EM_ASM({ + var device = miniaudio.get_device_by_index($0); - /* Make sure the device is untracked so the slot can be reused later. */ - miniaudio.untrack_device_by_index($0); - }, deviceIndex, deviceType, &pDevice->pContext->allocationCallbacks); -} -#endif + /* Make sure all nodes are disconnected and marked for collection. */ + if (device.scriptNode !== undefined) { + device.scriptNode.onaudioprocess = function(e) {}; /* We want to reset the callback to ensure it doesn't get called after AudioContext.close() has returned. Shouldn't happen since we're disconnecting, but just to be safe... */ + device.scriptNode.disconnect(); + device.scriptNode = undefined; + } -static void ma_device_uninit_by_type__webaudio(ma_device* pDevice, ma_device_type deviceType) -{ - MA_ASSERT(pDevice != NULL); - MA_ASSERT(deviceType == ma_device_type_capture || deviceType == ma_device_type_playback); + if (device.streamNode !== undefined) { + device.streamNode.disconnect(); + device.streamNode = undefined; + } -#if defined(MA_USE_AUDIO_WORKLETS) - if (deviceType == ma_device_type_capture) { - ma_free(pDevice->webaudio.pIntermediaryBufferCapture, &pDevice->pContext->allocationCallbacks); - ma_free(pDevice->webaudio.pStackBufferCapture, &pDevice->pContext->allocationCallbacks); - emscripten_destroy_audio_context(pDevice->webaudio.audioContextCapture); - } else { - ma_free(pDevice->webaudio.pIntermediaryBufferPlayback, &pDevice->pContext->allocationCallbacks); - ma_free(pDevice->webaudio.pStackBufferPlayback, &pDevice->pContext->allocationCallbacks); - emscripten_destroy_audio_context(pDevice->webaudio.audioContextPlayback); - } -#else - if (deviceType == ma_device_type_capture) { - ma_device_uninit_by_index__webaudio(pDevice, ma_device_type_capture, pDevice->webaudio.indexCapture); - } else { - ma_device_uninit_by_index__webaudio(pDevice, ma_device_type_playback, pDevice->webaudio.indexPlayback); + /* + Stop the device. I think there is a chance the callback could get fired after calling this, hence why we want + to clear the callback before closing. + */ + device.webaudio.close(); + device.webaudio = undefined; + device.pDevice = undefined; + }, pDevice->webaudio.deviceIndex); } -#endif -} - -static ma_result ma_device_uninit__webaudio(ma_device* pDevice) -{ - MA_ASSERT(pDevice != NULL); + #endif - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ma_device_uninit_by_type__webaudio(pDevice, ma_device_type_capture); - } + /* Clean up the device on the JS side. */ + EM_ASM({ + miniaudio.untrack_device_by_index($0); + }, pDevice->webaudio.deviceIndex); - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ma_device_uninit_by_type__webaudio(pDevice, ma_device_type_playback); - } + ma_free(pDevice->webaudio.pIntermediaryBuffer, &pDevice->pContext->allocationCallbacks); return MA_SUCCESS; } +#if !defined(MA_USE_AUDIO_WORKLETS) static ma_uint32 ma_calculate_period_size_in_frames_from_descriptor__webaudio(const ma_device_descriptor* pDescriptor, ma_uint32 nativeSampleRate, ma_performance_profile performanceProfile) { -#if defined(MA_USE_AUDIO_WORKLETS) - (void)pDescriptor; - (void)nativeSampleRate; - (void)performanceProfile; - - return 256; -#else /* There have been reports of the default buffer size being too small on some browsers. If we're using the default buffer size, we'll make sure the period size is bigger than our standard defaults. */ ma_uint32 periodSizeInFrames; + if (nativeSampleRate == 0) { + nativeSampleRate = MA_DEFAULT_SAMPLE_RATE; + } + if (pDescriptor->periodSizeInFrames == 0) { if (pDescriptor->periodSizeInMilliseconds == 0) { if (performanceProfile == ma_performance_profile_low_latency) { @@ -39792,8 +39861,8 @@ static ma_uint32 ma_calculate_period_size_in_frames_from_descriptor__webaudio(co } return periodSizeInFrames; -#endif } +#endif #if defined(MA_USE_AUDIO_WORKLETS) @@ -39801,20 +39870,22 @@ typedef struct { ma_device* pDevice; const ma_device_config* pConfig; - ma_device_descriptor* pDescriptor; - ma_device_type deviceType; - ma_uint32 channels; + ma_device_descriptor* pDescriptorPlayback; + ma_device_descriptor* pDescriptorCapture; } ma_audio_worklet_thread_initialized_data; static EM_BOOL ma_audio_worklet_process_callback__webaudio(int inputCount, const AudioSampleFrame* pInputs, int outputCount, AudioSampleFrame* pOutputs, int paramCount, const AudioParamFrame* pParams, void* pUserData) { ma_device* pDevice = (ma_device*)pUserData; ma_uint32 frameCount; - ma_uint32 framesProcessed; (void)paramCount; (void)pParams; + if (ma_device_get_state(pDevice) != ma_device_state_started) { + return EM_TRUE; + } + /* The Emscripten documentation says that it'll always be 128 frames being passed in. Hard coding it like that feels like a very bad idea to me. Even if it's hard coded in the backend, the API and documentation should always refer @@ -39825,38 +39896,31 @@ static EM_BOOL ma_audio_worklet_process_callback__webaudio(int inputCount, const */ frameCount = 128; - /* Run the conversion logic in a loop for robustness. */ - framesProcessed = 0; - while (framesProcessed < frameCount) { - ma_uint32 framesToProcessThisIteration = frameCount - framesProcessed; - - if (inputCount > 0) { - if (framesToProcessThisIteration > pDevice->webaudio.intermediaryBufferSizeInFramesPlayback) { - framesToProcessThisIteration = pDevice->webaudio.intermediaryBufferSizeInFramesPlayback; + if (inputCount > 0) { + /* Input data needs to be interleaved before we hand it to the client. */ + for (ma_uint32 iChannel = 0; iChannel < pDevice->capture.internalChannels; iChannel += 1) { + for (ma_uint32 iFrame = 0; iFrame < frameCount; iFrame += 1) { + pDevice->webaudio.pIntermediaryBuffer[iFrame*pDevice->capture.internalChannels + iChannel] = pInputs[0].data[frameCount*iChannel + iFrame]; } - - /* Input data needs to be interleaved before we hand it to the client. */ - for (ma_uint32 iFrame = 0; iFrame < framesToProcessThisIteration; iFrame += 1) { - for (ma_uint32 iChannel = 0; iChannel < pDevice->capture.internalChannels; iChannel += 1) { - pDevice->webaudio.pIntermediaryBufferCapture[iFrame*pDevice->capture.internalChannels + iChannel] = pInputs[0].data[frameCount*iChannel + framesProcessed + iFrame]; - } - } - - ma_device_process_pcm_frames_capture__webaudio(pDevice, framesToProcessThisIteration, pDevice->webaudio.pIntermediaryBufferCapture); } - if (outputCount > 0) { - ma_device_process_pcm_frames_playback__webaudio(pDevice, framesToProcessThisIteration, pDevice->webaudio.pIntermediaryBufferPlayback); + ma_device_process_pcm_frames_capture__webaudio(pDevice, frameCount, pDevice->webaudio.pIntermediaryBuffer); + } + + if (outputCount > 0) { + /* If it's a capture-only device, we'll need to output silence. */ + if (pDevice->type == ma_device_type_capture) { + MA_ZERO_MEMORY(pOutputs[0].data, frameCount * pDevice->playback.internalChannels * sizeof(float)); + } else { + ma_device_process_pcm_frames_playback__webaudio(pDevice, frameCount, pDevice->webaudio.pIntermediaryBuffer); /* We've read the data from the client. Now we need to deinterleave the buffer and output to the output buffer. */ - for (ma_uint32 iFrame = 0; iFrame < framesToProcessThisIteration; iFrame += 1) { - for (ma_uint32 iChannel = 0; iChannel < pDevice->playback.internalChannels; iChannel += 1) { - pOutputs[0].data[frameCount*iChannel + framesProcessed + iFrame] = pDevice->webaudio.pIntermediaryBufferPlayback[iFrame*pDevice->playback.internalChannels + iChannel]; + for (ma_uint32 iChannel = 0; iChannel < pDevice->playback.internalChannels; iChannel += 1) { + for (ma_uint32 iFrame = 0; iFrame < frameCount; iFrame += 1) { + pOutputs[0].data[frameCount*iChannel + iFrame] = pDevice->webaudio.pIntermediaryBuffer[iFrame*pDevice->playback.internalChannels + iChannel]; } } } - - framesProcessed += framesToProcessThisIteration; } return EM_TRUE; @@ -39866,76 +39930,135 @@ static EM_BOOL ma_audio_worklet_process_callback__webaudio(int inputCount, const static void ma_audio_worklet_processor_created__webaudio(EMSCRIPTEN_WEBAUDIO_T audioContext, EM_BOOL success, void* pUserData) { ma_audio_worklet_thread_initialized_data* pParameters = (ma_audio_worklet_thread_initialized_data*)pUserData; - EmscriptenAudioWorkletNodeCreateOptions workletNodeOptions; - EMSCRIPTEN_AUDIO_WORKLET_NODE_T workletNode; - int outputChannelCount = 0; + EmscriptenAudioWorkletNodeCreateOptions audioWorkletOptions; + int channels = 0; + size_t intermediaryBufferSizeInFrames; + int sampleRate; if (success == EM_FALSE) { - pParameters->pDevice->webaudio.isInitialized = MA_TRUE; + pParameters->pDevice->webaudio.initResult = MA_ERROR; + ma_free(pParameters, &pParameters->pDevice->pContext->allocationCallbacks); return; } - MA_ZERO_OBJECT(&workletNodeOptions); + /* The next step is to initialize the audio worklet node. */ + MA_ZERO_OBJECT(&audioWorkletOptions); - if (pParameters->deviceType == ma_device_type_capture) { - workletNodeOptions.numberOfInputs = 1; + /* + The way channel counts work with Web Audio is confusing. As far as I can tell, there's no way to know the channel + count from MediaStreamAudioSourceNode (what we use for capture)? The only way to have control is to configure an + output channel count on the capture side. This is slightly confusing for capture mode because intuitively you + wouldn't actually connect an output to an input-only node, but this is what we'll have to do in order to have + proper control over the channel count. In the capture case, we'll have to output silence to it's output node. + */ + if (pParameters->pConfig->deviceType == ma_device_type_capture) { + channels = (int)((pParameters->pDescriptorCapture->channels > 0) ? pParameters->pDescriptorCapture->channels : MA_DEFAULT_CHANNELS); + audioWorkletOptions.numberOfInputs = 1; } else { - outputChannelCount = (int)pParameters->channels; /* Safe cast. */ + channels = (int)((pParameters->pDescriptorPlayback->channels > 0) ? pParameters->pDescriptorPlayback->channels : MA_DEFAULT_CHANNELS); - workletNodeOptions.numberOfOutputs = 1; - workletNodeOptions.outputChannelCounts = &outputChannelCount; + if (pParameters->pConfig->deviceType == ma_device_type_duplex) { + audioWorkletOptions.numberOfInputs = 1; + } else { + audioWorkletOptions.numberOfInputs = 0; + } } - /* Here is where we create the node that will do our processing. */ - workletNode = emscripten_create_wasm_audio_worklet_node(audioContext, "miniaudio", &workletNodeOptions, &ma_audio_worklet_process_callback__webaudio, pParameters->pDevice); + audioWorkletOptions.numberOfOutputs = 1; + audioWorkletOptions.outputChannelCounts = &channels; - if (pParameters->deviceType == ma_device_type_capture) { - pParameters->pDevice->webaudio.workletNodeCapture = workletNode; - } else { - pParameters->pDevice->webaudio.workletNodePlayback = workletNode; - } /* - With the worklet node created we can now attach it to the graph. This is done differently depending on whether or not - it's capture or playback mode. + Now that we know the channel count to use we can allocate the intermediary buffer. The + intermediary buffer is used for interleaving and deinterleaving. */ - if (pParameters->deviceType == ma_device_type_capture) { - EM_ASM({ - var workletNode = emscriptenGetAudioObject($0); + intermediaryBufferSizeInFrames = 128; + + pParameters->pDevice->webaudio.pIntermediaryBuffer = (float*)ma_malloc(intermediaryBufferSizeInFrames * (ma_uint32)channels * sizeof(float), &pParameters->pDevice->pContext->allocationCallbacks); + if (pParameters->pDevice->webaudio.pIntermediaryBuffer == NULL) { + pParameters->pDevice->webaudio.initResult = MA_OUT_OF_MEMORY; + ma_free(pParameters, &pParameters->pDevice->pContext->allocationCallbacks); + return; + } + + + pParameters->pDevice->webaudio.audioWorklet = emscripten_create_wasm_audio_worklet_node(audioContext, "miniaudio", &audioWorkletOptions, &ma_audio_worklet_process_callback__webaudio, pParameters->pDevice); + + /* With the audio worklet initialized we can now attach it to the graph. */ + if (pParameters->pConfig->deviceType == ma_device_type_capture || pParameters->pConfig->deviceType == ma_device_type_duplex) { + ma_result attachmentResult = (ma_result)EM_ASM_INT({ + var getUserMediaResult = 0; + var audioWorklet = emscriptenGetAudioObject($0); var audioContext = emscriptenGetAudioObject($1); navigator.mediaDevices.getUserMedia({audio:true, video:false}) .then(function(stream) { audioContext.streamNode = audioContext.createMediaStreamSource(stream); - audioContext.streamNode.connect(workletNode); - - /* - Now that the worklet node has been connected, do we need to inspect workletNode.channelCount - to check the actual channel count, or is it safe to assume it's always 2? - */ + audioContext.streamNode.connect(audioWorklet); + audioWorklet.connect(audioContext.destination); + getUserMediaResult = 0; /* 0 = MA_SUCCESS */ }) .catch(function(error) { - + console.log("navigator.mediaDevices.getUserMedia Failed: " + error); + getUserMediaResult = -1; /* -1 = MA_ERROR */ }); - }, workletNode, audioContext); - } else { - EM_ASM({ - var workletNode = emscriptenGetAudioObject($0); + + return getUserMediaResult; + }, pParameters->pDevice->webaudio.audioWorklet, audioContext); + + if (attachmentResult != MA_SUCCESS) { + ma_log_postf(ma_device_get_log(pParameters->pDevice), MA_LOG_LEVEL_ERROR, "Web Audio: Failed to connect capture node."); + emscripten_destroy_web_audio_node(pParameters->pDevice->webaudio.audioWorklet); + pParameters->pDevice->webaudio.initResult = attachmentResult; + ma_free(pParameters, &pParameters->pDevice->pContext->allocationCallbacks); + return; + } + } + + /* If it's playback only we can now attach the worklet node to the graph. This has already been done for the duplex case. */ + if (pParameters->pConfig->deviceType == ma_device_type_playback) { + ma_result attachmentResult = (ma_result)EM_ASM_INT({ + var audioWorklet = emscriptenGetAudioObject($0); var audioContext = emscriptenGetAudioObject($1); - workletNode.connect(audioContext.destination); - }, workletNode, audioContext); + audioWorklet.connect(audioContext.destination); + return 0; /* 0 = MA_SUCCESS */ + }, pParameters->pDevice->webaudio.audioWorklet, audioContext); + + if (attachmentResult != MA_SUCCESS) { + ma_log_postf(ma_device_get_log(pParameters->pDevice), MA_LOG_LEVEL_ERROR, "Web Audio: Failed to connect playback node."); + pParameters->pDevice->webaudio.initResult = attachmentResult; + ma_free(pParameters, &pParameters->pDevice->pContext->allocationCallbacks); + return; + } } - pParameters->pDevice->webaudio.isInitialized = MA_TRUE; + /* We need to update the descriptors so that they reflect the internal data format. Both capture and playback should be the same. */ + sampleRate = EM_ASM_INT({ return emscriptenGetAudioObject($0).sampleRate; }, audioContext); + + if (pParameters->pDescriptorCapture != NULL) { + pParameters->pDescriptorCapture->format = ma_format_f32; + pParameters->pDescriptorCapture->channels = (ma_uint32)channels; + pParameters->pDescriptorCapture->sampleRate = (ma_uint32)sampleRate; + ma_channel_map_init_standard(ma_standard_channel_map_webaudio, pParameters->pDescriptorCapture->channelMap, ma_countof(pParameters->pDescriptorCapture->channelMap), pParameters->pDescriptorCapture->channels); + pParameters->pDescriptorCapture->periodSizeInFrames = intermediaryBufferSizeInFrames; + pParameters->pDescriptorCapture->periodCount = 1; + } - ma_log_postf(ma_device_get_log(pParameters->pDevice), MA_LOG_LEVEL_DEBUG, "AudioWorklets: Created worklet node: %d\n", workletNode); + if (pParameters->pDescriptorPlayback != NULL) { + pParameters->pDescriptorPlayback->format = ma_format_f32; + pParameters->pDescriptorPlayback->channels = (ma_uint32)channels; + pParameters->pDescriptorPlayback->sampleRate = (ma_uint32)sampleRate; + ma_channel_map_init_standard(ma_standard_channel_map_webaudio, pParameters->pDescriptorPlayback->channelMap, ma_countof(pParameters->pDescriptorPlayback->channelMap), pParameters->pDescriptorPlayback->channels); + pParameters->pDescriptorPlayback->periodSizeInFrames = intermediaryBufferSizeInFrames; + pParameters->pDescriptorPlayback->periodCount = 1; + } - /* Our parameter data is no longer needed. */ + /* At this point we're done and we can return. */ + ma_log_postf(ma_device_get_log(pParameters->pDevice), MA_LOG_LEVEL_DEBUG, "AudioWorklets: Created worklet node: %d\n", pParameters->pDevice->webaudio.audioWorklet); + pParameters->pDevice->webaudio.initResult = MA_SUCCESS; ma_free(pParameters, &pParameters->pDevice->pContext->allocationCallbacks); } - - static void ma_audio_worklet_thread_initialized__webaudio(EMSCRIPTEN_WEBAUDIO_T audioContext, EM_BOOL success, void* pUserData) { ma_audio_worklet_thread_initialized_data* pParameters = (ma_audio_worklet_thread_initialized_data*)pUserData; @@ -39944,7 +40067,7 @@ static void ma_audio_worklet_thread_initialized__webaudio(EMSCRIPTEN_WEBAUDIO_T MA_ASSERT(pParameters != NULL); if (success == EM_FALSE) { - pParameters->pDevice->webaudio.isInitialized = MA_TRUE; + pParameters->pDevice->webaudio.initResult = MA_ERROR; return; } @@ -39955,45 +40078,54 @@ static void ma_audio_worklet_thread_initialized__webaudio(EMSCRIPTEN_WEBAUDIO_T } #endif -static ma_result ma_device_init_by_type__webaudio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptor, ma_device_type deviceType) +static ma_result ma_device_init__webaudio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) { -#if defined(MA_USE_AUDIO_WORKLETS) - EMSCRIPTEN_WEBAUDIO_T audioContext; - void* pStackBuffer; - size_t intermediaryBufferSizeInFrames; - float* pIntermediaryBuffer; -#endif - ma_uint32 channels; - ma_uint32 sampleRate; - ma_uint32 periodSizeInFrames; - - MA_ASSERT(pDevice != NULL); - MA_ASSERT(pConfig != NULL); - MA_ASSERT(deviceType != ma_device_type_duplex); - - if (deviceType == ma_device_type_capture && !ma_is_capture_supported__webaudio()) { - return MA_NO_DEVICE; + if (pConfig->deviceType == ma_device_type_loopback) { + return MA_DEVICE_TYPE_NOT_SUPPORTED; } - /* We're going to calculate some stuff in C just to simplify the JS code. */ - channels = (pDescriptor->channels > 0) ? pDescriptor->channels : MA_DEFAULT_CHANNELS; - sampleRate = (pDescriptor->sampleRate > 0) ? pDescriptor->sampleRate : MA_DEFAULT_SAMPLE_RATE; - periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__webaudio(pDescriptor, sampleRate, pConfig->performanceProfile); - - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "periodSizeInFrames = %d\n", (int)periodSizeInFrames); + /* No exclusive mode with Web Audio. */ + 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)) { + return MA_SHARE_MODE_NOT_SUPPORTED; + } -#if defined(MA_USE_AUDIO_WORKLETS) + /* + With AudioWorklets we'll have just a single AudioContext. I'm not sure why I'm not doing this for ScriptProcessorNode so + it might be worthwhile to look into that as well. + */ + #if defined(MA_USE_AUDIO_WORKLETS) { - ma_audio_worklet_thread_initialized_data* pInitParameters; EmscriptenWebAudioCreateAttributes audioContextAttributes; + ma_audio_worklet_thread_initialized_data* pInitParameters; + void* pStackBuffer; - audioContextAttributes.latencyHint = MA_WEBAUDIO_LATENCY_HINT_INTERACTIVE; - audioContextAttributes.sampleRate = sampleRate; + if (pConfig->performanceProfile == ma_performance_profile_conservative) { + audioContextAttributes.latencyHint = MA_WEBAUDIO_LATENCY_HINT_PLAYBACK; + } else { + audioContextAttributes.latencyHint = MA_WEBAUDIO_LATENCY_HINT_INTERACTIVE; + } + + /* + In my testing, Firefox does not seem to capture audio data properly if the sample rate is set + to anything other than 48K. This does not seem to be the case for other browsers. For this reason, + if the device type is anything other than playback, we'll leave the sample rate as-is and let the + browser pick the appropriate rate for us. + */ + if (pConfig->deviceType == ma_device_type_playback) { + audioContextAttributes.sampleRate = pDescriptorPlayback->sampleRate; + } else { + audioContextAttributes.sampleRate = 0; + } /* It's not clear if this can return an error. None of the tests in the Emscripten repository check for this, so neither am I for now. */ - audioContext = emscripten_create_audio_context(&audioContextAttributes); + pDevice->webaudio.audioContext = emscripten_create_audio_context(&audioContextAttributes); + - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "TRACE: AUDIO CONTEXT CREATED\n"); + /* + With the context created we can now create the worklet. We can only have a single worklet per audio + context which means we'll need to craft this appropriately to handle duplex devices correctly. + */ /* We now need to create a worker thread. This is a bit weird because we need to allocate our @@ -40002,339 +40134,227 @@ static ma_result ma_device_init_by_type__webaudio(ma_device* pDevice, const ma_d */ pStackBuffer = ma_aligned_malloc(MA_AUDIO_WORKLETS_THREAD_STACK_SIZE, 16, &pDevice->pContext->allocationCallbacks); if (pStackBuffer == NULL) { - emscripten_destroy_audio_context(audioContext); - return MA_OUT_OF_MEMORY; - } - - /* - We need an intermediary buffer for data conversion. WebAudio reports data in uninterleaved - format whereas we require it to be interleaved. We'll do this in chunks of 128 frames. - */ - intermediaryBufferSizeInFrames = 128; - pIntermediaryBuffer = ma_malloc(intermediaryBufferSizeInFrames * channels * sizeof(float), &pDevice->pContext->allocationCallbacks); - if (pIntermediaryBuffer == NULL) { - ma_free(pStackBuffer, &pDevice->pContext->allocationCallbacks); - emscripten_destroy_audio_context(audioContext); + emscripten_destroy_audio_context(pDevice->webaudio.audioContext); return MA_OUT_OF_MEMORY; } - pInitParameters = ma_malloc(sizeof(*pInitParameters), &pDevice->pContext->allocationCallbacks); + /* Our thread initialization parameters need to be allocated on the heap so they don't go out of scope. */ + pInitParameters = (ma_audio_worklet_thread_initialized_data*)ma_malloc(sizeof(*pInitParameters), &pDevice->pContext->allocationCallbacks); if (pInitParameters == NULL) { - ma_free(pIntermediaryBuffer, &pDevice->pContext->allocationCallbacks); ma_free(pStackBuffer, &pDevice->pContext->allocationCallbacks); - emscripten_destroy_audio_context(audioContext); + emscripten_destroy_audio_context(pDevice->webaudio.audioContext); return MA_OUT_OF_MEMORY; } - pInitParameters->pDevice = pDevice; - pInitParameters->pConfig = pConfig; - pInitParameters->pDescriptor = pDescriptor; - pInitParameters->deviceType = deviceType; - pInitParameters->channels = channels; + pInitParameters->pDevice = pDevice; + pInitParameters->pConfig = pConfig; + pInitParameters->pDescriptorPlayback = pDescriptorPlayback; + pInitParameters->pDescriptorCapture = pDescriptorCapture; /* We need to flag the device as not yet initialized so we can wait on it later. Unfortunately all of the Emscripten WebAudio stuff is asynchronous. */ - pDevice->webaudio.isInitialized = MA_FALSE; + pDevice->webaudio.initResult = MA_BUSY; + { + emscripten_start_wasm_audio_worklet_thread_async(pDevice->webaudio.audioContext, pStackBuffer, MA_AUDIO_WORKLETS_THREAD_STACK_SIZE, ma_audio_worklet_thread_initialized__webaudio, pInitParameters); + } + while (pDevice->webaudio.initResult == MA_BUSY) { emscripten_sleep(1); } /* We must wait for initialization to complete. We're just spinning here. The emscripten_sleep() call is why we need to build with `-sASYNCIFY`. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "TRACE: CREATING WORKLET\n"); + /* Initialization is now complete. Descriptors were updated when the worklet was initialized. */ + if (pDevice->webaudio.initResult != MA_SUCCESS) { + ma_free(pStackBuffer, &pDevice->pContext->allocationCallbacks); + emscripten_destroy_audio_context(pDevice->webaudio.audioContext); + return pDevice->webaudio.initResult; + } - emscripten_start_wasm_audio_worklet_thread_async(audioContext, pStackBuffer, MA_AUDIO_WORKLETS_THREAD_STACK_SIZE, ma_audio_worklet_thread_initialized__webaudio, pInitParameters); + /* We need to add an entry to the miniaudio.devices list on the JS side so we can do some JS/C interop. */ + pDevice->webaudio.deviceIndex = EM_ASM_INT({ + return miniaudio.track_device({ + webaudio: emscriptenGetAudioObject($0), + state: 1 /* 1 = ma_device_state_stopped */ + }); + }, pDevice->webaudio.audioContext); + + return MA_SUCCESS; + } + #else + { + /* ScriptProcessorNode. This path requires us to do almost everything in JS, but we'll do as much as we can in C. */ + ma_uint32 deviceIndex; + ma_uint32 channels; + ma_uint32 sampleRate; + ma_uint32 periodSizeInFrames; - /* We must wait for initialization to complete. We're just spinning here. The emscripten_sleep() call is why we need to build with `-sASYNCIFY`. */ - while (pDevice->webaudio.isInitialized == MA_FALSE) { - emscripten_sleep(1); + /* The channel count will depend on the device type. If it's a capture, use it's, otherwise use the playback side. */ + if (pConfig->deviceType == ma_device_type_capture) { + channels = (pDescriptorCapture->channels > 0) ? pDescriptorCapture->channels : MA_DEFAULT_CHANNELS; + } else { + channels = (pDescriptorPlayback->channels > 0) ? pDescriptorPlayback->channels : MA_DEFAULT_CHANNELS; } /* - Now that initialization is finished we can go ahead and extract our channel count so that - miniaudio can set up a data converter at a higher level. + When testing in Firefox, I've seen it where capture mode fails if the sample rate is changed to anything other than it's + native rate. For this reason we're leaving the sample rate untouched for capture devices. */ - if (deviceType == ma_device_type_capture) { - /* - For capture we won't actually know what the channel count is. Everything I've seen seems - to indicate that the default channel count is 2, so I'm sticking with that. - */ - channels = 2; + if (pConfig->deviceType == ma_device_type_playback) { + sampleRate = pDescriptorPlayback->sampleRate; } else { - /* Get the channel count from the audio context. */ - channels = (ma_uint32)EM_ASM_INT({ - return emscriptenGetAudioObject($0).destination.channelCount; - }, audioContext); + sampleRate = 0; /* Let the browser decide when capturing. */ } - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "TRACE: INITIALIZED. channels = %u\n", channels); - } -#else - /* We create the device on the JavaScript side and reference it using an index. We use this to make it possible to reference the device between JavaScript and C. */ - int deviceIndex = EM_ASM_INT({ - var channels = $0; - var sampleRate = $1; - var bufferSize = $2; /* In PCM frames. */ - var isCapture = $3; - var pDevice = $4; - var pAllocationCallbacks = $5; + /* The period size needs to be a power of 2. */ + if (pConfig->deviceType == ma_device_type_capture) { + periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__webaudio(pDescriptorCapture, sampleRate, pConfig->performanceProfile); + } else { + periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__webaudio(pDescriptorPlayback, sampleRate, pConfig->performanceProfile); + } - if (typeof(window.miniaudio) === 'undefined') { - return -1; /* Context not initialized. */ + /* We need an intermediary buffer for doing interleaving and deinterleaving. */ + pDevice->webaudio.pIntermediaryBuffer = (float*)ma_malloc(periodSizeInFrames * channels * sizeof(float), &pDevice->pContext->allocationCallbacks); + if (pDevice->webaudio.pIntermediaryBuffer == NULL) { + return MA_OUT_OF_MEMORY; } - var device = {}; + deviceIndex = EM_ASM_INT({ + var deviceType = $0; + var channels = $1; + var sampleRate = $2; + var bufferSize = $3; + var pIntermediaryBuffer = $4; + var pDevice = $5; - /* 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_device_state_stopped */ + if (typeof(window.miniaudio) === 'undefined') { + return -1; /* Context not initialized. */ + } - /* We need an intermediary buffer which we use for JavaScript and C interop. This buffer stores interleaved f32 PCM data. */ - device.intermediaryBufferSizeInBytes = channels * bufferSize * 4; - device.intermediaryBuffer = _ma_malloc_emscripten(device.intermediaryBufferSizeInBytes, pAllocationCallbacks); - device.intermediaryBufferView = new Float32Array(Module.HEAPF32.buffer, device.intermediaryBuffer, device.intermediaryBufferSizeInBytes); + var device = {}; - /* - Both playback and capture devices use a ScriptProcessorNode for performing per-sample operations. - - ScriptProcessorNode is actually deprecated so this is likely to be temporary. The way this works for playback is very simple. You just set a callback - that's periodically fired, just like a normal audio callback function. But apparently this design is "flawed" and is now deprecated in favour of - something called AudioWorklets which _forces_ you to load a _separate_ .js file at run time... nice... Hopefully ScriptProcessorNode will continue to - work for years to come, but this may need to change to use AudioSourceBufferNode instead, which I think is what Emscripten uses for it's built-in SDL - implementation. I'll be avoiding that insane AudioWorklet API like the plague... - - For capture it is a bit unintuitive. We use the ScriptProccessorNode _only_ to get the raw PCM data. It is connected to an AudioContext just like the - playback case, however we just output silence to the AudioContext instead of passing any real data. It would make more sense to me to use the - MediaRecorder API, but unfortunately you need to specify a MIME time (Opus, Vorbis, etc.) for the binary blob that's returned to the client, but I've - been unable to figure out how to get this as raw PCM. The closest I can think is to use the MIME type for WAV files and just parse it, but I don't know - how well this would work. Although ScriptProccessorNode is deprecated, in practice it seems to have pretty good browser support so I'm leaving it like - this for now. If anyone knows how I could get raw PCM data using the MediaRecorder API please let me know! - */ - device.scriptNode = device.webaudio.createScriptProcessor(bufferSize, (isCapture) ? channels : 0, (isCapture) ? 0 : channels); + /* First thing we need is an AudioContext. */ + var audioContextOptions = {}; + if (deviceType == window.miniaudio.device_type.playback && sampleRate != 0) { + audioContextOptions.sampleRate = sampleRate; + } - if (isCapture) { - device.scriptNode.onaudioprocess = function(e) { - if (device.intermediaryBuffer === undefined) { - return; /* This means the device has been uninitialized. */ - } + device.webaudio = new (window.AudioContext || window.webkitAudioContext)(audioContextOptions); + device.webaudio.suspend(); /* The AudioContext must be created in a suspended state. */ + device.state = window.miniaudio.device_state.stopped; - if (device.intermediaryBufferView.length == 0) { - /* Recreate intermediaryBufferView when losing reference to the underlying buffer, probably due to emscripten resizing heap. */ - device.intermediaryBufferView = new Float32Array(Module.HEAPF32.buffer, device.intermediaryBuffer, device.intermediaryBufferSizeInBytes); - } + /* + We need to create a ScriptProcessorNode. The channel situation is the same as the AudioWorklet path in that we + need to specify an output and configure the channel count there. + */ + var channelCountIn = 0; + var channelCountOut = channels; + if (deviceType != window.miniaudio.device_type.playback) { + channelCountIn = channels; + } - /* Make sure silence it output to the AudioContext destination. Not doing this will cause sound to come out of the speakers! */ - for (var iChannel = 0; iChannel < e.outputBuffer.numberOfChannels; ++iChannel) { - e.outputBuffer.getChannelData(iChannel).fill(0.0); - } + device.scriptNode = device.webaudio.createScriptProcessor(bufferSize, channelCountIn, channelCountOut); - /* There are some situations where we may want to send silence to the client. */ - var sendSilence = false; - if (device.streamNode === undefined) { - sendSilence = true; - } - - /* Sanity check. This will never happen, right? */ - if (e.inputBuffer.numberOfChannels != channels) { - console.log("Capture: Channel count mismatch. " + e.inputBufer.numberOfChannels + " != " + channels + ". Sending silence."); - sendSilence = true; + /* The node processing callback. */ + device.scriptNode.onaudioprocess = function(e) { + if (device.intermediaryBufferView == null || device.intermediaryBufferView.length == 0) { + device.intermediaryBufferView = new Float32Array(Module.HEAPF32.buffer, pIntermediaryBuffer, bufferSize * channels); } - /* This looped design guards against the situation where e.inputBuffer is a different size to the original buffer size. Should never happen in practice. */ - var totalFramesProcessed = 0; - while (totalFramesProcessed < e.inputBuffer.length) { - var framesRemaining = e.inputBuffer.length - totalFramesProcessed; - var framesToProcess = framesRemaining; - if (framesToProcess > (device.intermediaryBufferSizeInBytes/channels/4)) { - framesToProcess = (device.intermediaryBufferSizeInBytes/channels/4); - } + /* Do the capture side first. */ + if (deviceType == miniaudio.device_type.capture || deviceType == miniaudio.device_type.duplex) { + /* The data must be interleaved before being processed miniaudio. */ + for (var iChannel = 0; iChannel < channels; iChannel += 1) { + var inputBuffer = e.inputBuffer.getChannelData(iChannel); + var intermediaryBuffer = device.intermediaryBufferView; - /* We need to do the reverse of the playback case. We need to interleave the input data and copy it into the intermediary buffer. Then we send it to the client. */ - if (sendSilence) { - device.intermediaryBufferView.fill(0.0); - } else { - for (var iFrame = 0; iFrame < framesToProcess; ++iFrame) { - for (var iChannel = 0; iChannel < e.inputBuffer.numberOfChannels; ++iChannel) { - device.intermediaryBufferView[iFrame*channels + iChannel] = e.inputBuffer.getChannelData(iChannel)[totalFramesProcessed + iFrame]; - } + for (var iFrame = 0; iFrame < bufferSize; iFrame += 1) { + intermediaryBuffer[iFrame*channels + iChannel] = inputBuffer[iFrame]; } } - /* Send data to the client from our intermediary buffer. */ - _ma_device_process_pcm_frames_capture__webaudio(pDevice, framesToProcess, device.intermediaryBuffer); - - totalFramesProcessed += framesToProcess; + _ma_device_process_pcm_frames_capture__webaudio(pDevice, bufferSize, pIntermediaryBuffer); } - }; - navigator.mediaDevices.getUserMedia({audio:true, video:false}) - .then(function(stream) { - device.streamNode = device.webaudio.createMediaStreamSource(stream); - device.streamNode.connect(device.scriptNode); - device.scriptNode.connect(device.webaudio.destination); - }) - .catch(function(error) { - /* I think this should output silence... */ - device.scriptNode.connect(device.webaudio.destination); - }); - } else { - device.scriptNode.onaudioprocess = function(e) { - if (device.intermediaryBuffer === undefined) { - return; /* This means the device has been uninitialized. */ - } - - if(device.intermediaryBufferView.length == 0) { - /* Recreate intermediaryBufferView when losing reference to the underlying buffer, probably due to emscripten resizing heap. */ - device.intermediaryBufferView = new Float32Array(Module.HEAPF32.buffer, device.intermediaryBuffer, device.intermediaryBufferSizeInBytes); - } + if (deviceType == miniaudio.device_type.playback || deviceType == miniaudio.device_type.duplex) { + _ma_device_process_pcm_frames_playback__webaudio(pDevice, bufferSize, pIntermediaryBuffer); - var outputSilence = false; + for (var iChannel = 0; iChannel < e.outputBuffer.numberOfChannels; ++iChannel) { + var outputBuffer = e.outputBuffer.getChannelData(iChannel); + var intermediaryBuffer = device.intermediaryBufferView; - /* Sanity check. This will never happen, right? */ - if (e.outputBuffer.numberOfChannels != channels) { - console.log("Playback: Channel count mismatch. " + e.outputBufer.numberOfChannels + " != " + channels + ". Outputting silence."); - outputSilence = true; - return; - } - - /* This looped design guards against the situation where e.outputBuffer is a different size to the original buffer size. Should never happen in practice. */ - var totalFramesProcessed = 0; - while (totalFramesProcessed < e.outputBuffer.length) { - var framesRemaining = e.outputBuffer.length - totalFramesProcessed; - var framesToProcess = framesRemaining; - if (framesToProcess > (device.intermediaryBufferSizeInBytes/channels/4)) { - framesToProcess = (device.intermediaryBufferSizeInBytes/channels/4); - } - - /* Read data from the client into our intermediary buffer. */ - _ma_device_process_pcm_frames_playback__webaudio(pDevice, framesToProcess, device.intermediaryBuffer); - - /* At this point we'll have data in our intermediary buffer which we now need to deinterleave and copy over to the output buffers. */ - if (outputSilence) { - for (var iChannel = 0; iChannel < e.outputBuffer.numberOfChannels; ++iChannel) { - e.outputBuffer.getChannelData(iChannel).fill(0.0); - } - } else { - for (var iChannel = 0; iChannel < e.outputBuffer.numberOfChannels; ++iChannel) { - var outputBuffer = e.outputBuffer.getChannelData(iChannel); - var intermediaryBuffer = device.intermediaryBufferView; - for (var iFrame = 0; iFrame < framesToProcess; ++iFrame) { - outputBuffer[totalFramesProcessed + iFrame] = intermediaryBuffer[iFrame*channels + iChannel]; - } + for (var iFrame = 0; iFrame < bufferSize; iFrame += 1) { + outputBuffer[iFrame] = intermediaryBuffer[iFrame*channels + iChannel]; } } - - totalFramesProcessed += framesToProcess; + } else { + /* It's a capture-only device. Make sure the output is silenced. */ + for (var iChannel = 0; iChannel < e.outputBuffer.numberOfChannels; ++iChannel) { + e.outputBuffer.getChannelData(iChannel).fill(0.0); + } } }; - device.scriptNode.connect(device.webaudio.destination); - } - - return miniaudio.track_device(device); - }, channels, sampleRate, periodSizeInFrames, deviceType == ma_device_type_capture, pDevice, &pDevice->pContext->allocationCallbacks); - - if (deviceIndex < 0) { - return MA_FAILED_TO_OPEN_BACKEND_DEVICE; - } -#endif - -#if defined(MA_USE_AUDIO_WORKLETS) - if (deviceType == ma_device_type_capture) { - pDevice->webaudio.audioContextCapture = audioContext; - pDevice->webaudio.pStackBufferCapture = pStackBuffer; - pDevice->webaudio.intermediaryBufferSizeInFramesCapture = intermediaryBufferSizeInFrames; - pDevice->webaudio.pIntermediaryBufferCapture = pIntermediaryBuffer; - } else { - pDevice->webaudio.audioContextPlayback = audioContext; - pDevice->webaudio.pStackBufferPlayback = pStackBuffer; - pDevice->webaudio.intermediaryBufferSizeInFramesPlayback = intermediaryBufferSizeInFrames; - pDevice->webaudio.pIntermediaryBufferPlayback = pIntermediaryBuffer; - } -#else - if (deviceType == ma_device_type_capture) { - pDevice->webaudio.indexCapture = deviceIndex; - } else { - pDevice->webaudio.indexPlayback = deviceIndex; - } -#endif + /* Now we need to connect our node to the graph. */ + if (deviceType == miniaudio.device_type.capture || deviceType == miniaudio.device_type.duplex) { + navigator.mediaDevices.getUserMedia({audio:true, video:false}) + .then(function(stream) { + device.streamNode = device.webaudio.createMediaStreamSource(stream); + device.streamNode.connect(device.scriptNode); + device.scriptNode.connect(device.webaudio.destination); + }) + .catch(function(error) { + console.log("Failed to get user media: " + error); + }); + } - 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->periodSizeInFrames = periodSizeInFrames; - pDescriptor->periodCount = 1; + if (deviceType == miniaudio.device_type.playback) { + device.scriptNode.connect(device.webaudio.destination); + } -#if defined(MA_USE_AUDIO_WORKLETS) - pDescriptor->sampleRate = sampleRate; /* Is this good enough to be used in the general case? */ -#else - pDescriptor->sampleRate = EM_ASM_INT({ return miniaudio.get_device_by_index($0).webaudio.sampleRate; }, deviceIndex); -#endif + device.pDevice = pDevice; - return MA_SUCCESS; -} + return miniaudio.track_device(device); + }, pConfig->deviceType, channels, sampleRate, periodSizeInFrames, pDevice->webaudio.pIntermediaryBuffer, pDevice); -static ma_result ma_device_init__webaudio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) -{ - ma_result result; + if (deviceIndex < 0) { + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + } - if (pConfig->deviceType == ma_device_type_loopback) { - return MA_DEVICE_TYPE_NOT_SUPPORTED; - } + pDevice->webaudio.deviceIndex = deviceIndex; - /* No exclusive mode with Web Audio. */ - 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)) { - return MA_SHARE_MODE_NOT_SUPPORTED; - } + /* Grab the sample rate from the audio context directly. */ + sampleRate = (ma_uint32)EM_ASM_INT({ return miniaudio.get_device_by_index($0).webaudio.sampleRate; }, deviceIndex); - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - result = ma_device_init_by_type__webaudio(pDevice, pConfig, pDescriptorCapture, ma_device_type_capture); - if (result != MA_SUCCESS) { - return result; + if (pDescriptorCapture != NULL) { + pDescriptorCapture->format = ma_format_f32; + pDescriptorCapture->channels = channels; + pDescriptorCapture->sampleRate = sampleRate; + ma_channel_map_init_standard(ma_standard_channel_map_webaudio, pDescriptorCapture->channelMap, ma_countof(pDescriptorCapture->channelMap), pDescriptorCapture->channels); + pDescriptorCapture->periodSizeInFrames = periodSizeInFrames; + pDescriptorCapture->periodCount = 1; } - } - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - result = ma_device_init_by_type__webaudio(pDevice, pConfig, pDescriptorPlayback, ma_device_type_playback); - if (result != MA_SUCCESS) { - if (pConfig->deviceType == ma_device_type_duplex) { - ma_device_uninit_by_type__webaudio(pDevice, ma_device_type_capture); - } - return result; + if (pDescriptorPlayback != NULL) { + pDescriptorPlayback->format = ma_format_f32; + pDescriptorPlayback->channels = channels; + pDescriptorPlayback->sampleRate = sampleRate; + ma_channel_map_init_standard(ma_standard_channel_map_webaudio, pDescriptorPlayback->channelMap, ma_countof(pDescriptorPlayback->channelMap), pDescriptorPlayback->channels); + pDescriptorPlayback->periodSizeInFrames = periodSizeInFrames; + pDescriptorPlayback->periodCount = 1; } - } - return MA_SUCCESS; + return MA_SUCCESS; + } + #endif } static ma_result ma_device_start__webaudio(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); -#if defined(MA_USE_AUDIO_WORKLETS) - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - emscripten_resume_audio_context_sync(pDevice->webaudio.audioContextCapture); - } - - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - emscripten_resume_audio_context_sync(pDevice->webaudio.audioContextPlayback); - } -#else - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - EM_ASM({ - var device = miniaudio.get_device_by_index($0); - device.webaudio.resume(); - device.state = 2; /* ma_device_state_started */ - }, pDevice->webaudio.indexCapture); - } - - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - EM_ASM({ - var device = miniaudio.get_device_by_index($0); - device.webaudio.resume(); - device.state = 2; /* ma_device_state_started */ - }, pDevice->webaudio.indexPlayback); - } -#endif + EM_ASM({ + var device = miniaudio.get_device_by_index($0); + device.webaudio.resume(); + device.state = miniaudio.device_state.started; + }, pDevice->webaudio.deviceIndex); return MA_SUCCESS; } @@ -40352,37 +40372,11 @@ static ma_result ma_device_stop__webaudio(ma_device* pDevice) I read this to mean that "any current context processing blocks" are processed by suspend() - i.e. They they are drained. We therefore shouldn't need to do any kind of explicit draining. */ - -#if defined(MA_USE_AUDIO_WORKLETS) - /* I can't seem to find a way to suspend an AudioContext via the C Emscripten API. Is this an oversight? */ - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - EM_ASM({ - emscriptenGetAudioObject($0).suspend(); - }, pDevice->webaudio.audioContextCapture); - } - - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - EM_ASM({ - emscriptenGetAudioObject($0).suspend(); - }, pDevice->webaudio.audioContextPlayback); - } -#else - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - EM_ASM({ - var device = miniaudio.get_device_by_index($0); - device.webaudio.suspend(); - device.state = 1; /* ma_device_state_stopped */ - }, pDevice->webaudio.indexCapture); - } - - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - EM_ASM({ - var device = miniaudio.get_device_by_index($0); - device.webaudio.suspend(); - device.state = 1; /* ma_device_state_stopped */ - }, pDevice->webaudio.indexPlayback); - } -#endif + EM_ASM({ + var device = miniaudio.get_device_by_index($0); + device.webaudio.suspend(); + device.state = miniaudio.device_state.stopped; + }, pDevice->webaudio.deviceIndex); ma_device__on_notification_stopped(pDevice); @@ -40399,7 +40393,7 @@ static ma_result ma_context_uninit__webaudio(ma_context* pContext) /* Remove the global miniaudio object from window if there are no more references to it. */ EM_ASM({ if (typeof(window.miniaudio) !== 'undefined') { - window.miniaudio.referenceCount--; + window.miniaudio.referenceCount -= 1; if (window.miniaudio.referenceCount === 0) { delete window.miniaudio; } @@ -40427,7 +40421,20 @@ static ma_result ma_context_init__webaudio(ma_context* pContext, const ma_contex window.miniaudio = { referenceCount: 0 }; - miniaudio.devices = []; /* Device cache for mapping devices to indexes for JavaScript/C interop. */ + + /* Device types. */ + window.miniaudio.device_type = {}; + window.miniaudio.device_type.playback = $0; + window.miniaudio.device_type.capture = $1; + window.miniaudio.device_type.duplex = $2; + + /* Device states. */ + window.miniaudio.device_state = {}; + window.miniaudio.device_state.stopped = $3; + window.miniaudio.device_state.started = $4; + + /* Device cache for mapping devices to indexes for JavaScript/C interop. */ + miniaudio.devices = []; miniaudio.track_device = function(device) { /* Try inserting into a free slot first. */ @@ -40470,14 +40477,21 @@ static ma_result ma_context_init__webaudio(ma_context* pContext, const ma_contex }; miniaudio.unlock_event_types = (function(){ - return ['touchstart', 'touchend', 'click']; + return ['touchend', 'click']; })(); 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_device_state_started */) { - device.webaudio.resume(); + if (device != null && + device.webaudio != null && + device.state === window.miniaudio.device_state.started) { + + device.webaudio.resume().then(() => { + Module._ma_device__on_notification_unlocked(device.pDevice); + }, + (error) => {console.error("Failed to resume audiocontext", error); + }); } } miniaudio.unlock_event_types.map(function(event_type) { @@ -40490,10 +40504,10 @@ static ma_result ma_context_init__webaudio(ma_context* pContext, const ma_contex }); } - window.miniaudio.referenceCount++; + window.miniaudio.referenceCount += 1; return 1; - }, 0); /* Must pass in a dummy argument for C99 compatibility. */ + }, ma_device_type_playback, ma_device_type_capture, ma_device_type_duplex, ma_device_state_stopped, ma_device_state_started); if (resultFromJS != 1) { return MA_FAILED_TO_INIT_BACKEND; @@ -40828,10 +40842,14 @@ MA_API ma_result ma_device_post_init(ma_device* pDevice, ma_device_type deviceTy static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) { ma_device* pDevice = (ma_device*)pData; +#ifdef MA_WIN32 + HRESULT CoInitializeResult; +#endif + MA_ASSERT(pDevice != NULL); #ifdef MA_WIN32 - ma_CoInitializeEx(pDevice->pContext, NULL, MA_COINIT_VALUE); + CoInitializeResult = ma_CoInitializeEx(pDevice->pContext, NULL, MA_COINIT_VALUE); #endif /* @@ -40911,13 +40929,20 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) ma_device__on_notification_stopped(pDevice); } + /* If we stopped because the device has been uninitialized, abort now. */ + if (ma_device_get_state(pDevice) == ma_device_state_uninitialized) { + break; + } + /* 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_device_state_stopped); ma_event_signal(&pDevice->stopEvent); } #ifdef MA_WIN32 - ma_CoUninitialize(pDevice->pContext); + if (CoInitializeResult == S_OK) { + ma_CoUninitialize(pDevice->pContext); + } #endif return (ma_thread_result)0; @@ -40940,7 +40965,9 @@ 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. */ #if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) - ma_CoUninitialize(pContext); + if (pContext->win32.CoInitializeResult == S_OK) { + ma_CoUninitialize(pContext); + } #if defined(MA_WIN32_DESKTOP) ma_dlclose(ma_context_get_log(pContext), pContext->win32.hUser32DLL); @@ -40997,7 +41024,7 @@ static ma_result ma_context_init_backend_apis__win32(ma_context* pContext) (void)pContext; /* Unused. */ #endif - ma_CoInitializeEx(pContext, NULL, MA_COINIT_VALUE); + pContext->win32.CoInitializeResult = ma_CoInitializeEx(pContext, NULL, MA_COINIT_VALUE); return MA_SUCCESS; } #else @@ -41872,7 +41899,6 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC } - /* If we're using fixed sized callbacks we'll need to make use of an intermediary buffer. Needs to be done after post_init_setup() because we'll need access to the sample rate. @@ -42118,10 +42144,23 @@ MA_API void ma_device_uninit(ma_device* pDevice) return; } - /* Make sure the device is stopped first. The backends will probably handle this naturally, but I like to do it explicitly for my own sanity. */ - if (ma_device_is_started(pDevice)) { - ma_device_stop(pDevice); + /* + It's possible for the miniaudio side of the device and the backend to not be in sync due to + system-level situations such as the computer being put into sleep mode and the backend not + notifying miniaudio of the fact the device has stopped. It's possible for this to result in a + deadlock due to miniaudio thinking the device is in a running state, when in fact it's not + running at all. For this reason I am no longer explicitly stopping the device. I don't think + this should affect anyone in practice since uninitializing the backend will naturally stop the + device anyway. + */ + #if 0 + { + /* Make sure the device is stopped first. The backends will probably handle this naturally, but I like to do it explicitly for my own sanity. */ + if (ma_device_is_started(pDevice)) { + ma_device_stop(pDevice); + } } + #endif /* Putting the device into an uninitialized state will make the worker thread return. */ ma_device__set_state(pDevice, ma_device_state_uninitialized); @@ -44653,13 +44692,14 @@ static MA_INLINE void ma_pcm_f32_to_s16__neon(void* dst, const void* src, ma_uin d1 = vmovq_n_f32(0); } else if (ditherMode == ma_dither_mode_rectangle) { float d0v[4]; + float d1v[4]; + d0v[0] = ma_dither_f32_rectangle(ditherMin, ditherMax); d0v[1] = ma_dither_f32_rectangle(ditherMin, ditherMax); d0v[2] = ma_dither_f32_rectangle(ditherMin, ditherMax); d0v[3] = ma_dither_f32_rectangle(ditherMin, ditherMax); d0 = vld1q_f32(d0v); - float d1v[4]; d1v[0] = ma_dither_f32_rectangle(ditherMin, ditherMax); d1v[1] = ma_dither_f32_rectangle(ditherMin, ditherMax); d1v[2] = ma_dither_f32_rectangle(ditherMin, ditherMax); @@ -44667,13 +44707,14 @@ static MA_INLINE void ma_pcm_f32_to_s16__neon(void* dst, const void* src, ma_uin d1 = vld1q_f32(d1v); } else { float d0v[4]; + float d1v[4]; + d0v[0] = ma_dither_f32_triangle(ditherMin, ditherMax); d0v[1] = ma_dither_f32_triangle(ditherMin, ditherMax); d0v[2] = ma_dither_f32_triangle(ditherMin, ditherMax); d0v[3] = ma_dither_f32_triangle(ditherMin, ditherMax); d0 = vld1q_f32(d0v); - float d1v[4]; d1v[0] = ma_dither_f32_triangle(ditherMin, ditherMax); d1v[1] = ma_dither_f32_triangle(ditherMin, ditherMax); d1v[2] = ma_dither_f32_triangle(ditherMin, ditherMax); @@ -49286,48 +49327,65 @@ MA_API ma_result ma_fader_process_pcm_frames(ma_fader* pFader, void* pFramesOut, 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; + /* If the cursor is still negative we need to just copy the absolute number of those frames, but no more than frameCount. */ + if (pFader->cursorInFrames < 0) { + ma_uint64 absCursorInFrames = (ma_uint64)0 - pFader->cursorInFrames; + if (absCursorInFrames > frameCount) { + absCursorInFrames = frameCount; + } + + ma_copy_pcm_frames(pFramesOut, pFramesIn, absCursorInFrames, pFader->config.format, pFader->config.channels); + + pFader->cursorInFrames += absCursorInFrames; + frameCount -= absCursorInFrames; + pFramesOut = ma_offset_ptr(pFramesOut, ma_get_bytes_per_frame(pFader->config.format, pFader->config.channels)*absCursorInFrames); + pFramesIn = ma_offset_ptr(pFramesIn, ma_get_bytes_per_frame(pFader->config.format, pFader->config.channels)*absCursorInFrames); } - /* 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); + if (pFader->cursorInFrames >= 0) { + /* + 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; } - } 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); + + /* 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->volumeBeg); + } } else { - /* Slow path. This is where we do the actual fading. */ - ma_uint64 iFrame; - ma_uint32 iChannel; + /* Slower path. Volumes are different, so may need to do an interpolation. */ + if ((ma_uint64)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 now we only support f32. Support for other formats might 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 (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; + 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; } - } else { - return MA_NOT_IMPLEMENTED; } } } @@ -49357,6 +49415,11 @@ MA_API void ma_fader_get_data_format(const ma_fader* pFader, ma_format* pFormat, } MA_API void ma_fader_set_fade(ma_fader* pFader, float volumeBeg, float volumeEnd, ma_uint64 lengthInFrames) +{ + ma_fader_set_fade_ex(pFader, volumeBeg, volumeEnd, lengthInFrames, 0); +} + +MA_API void ma_fader_set_fade_ex(ma_fader* pFader, float volumeBeg, float volumeEnd, ma_uint64 lengthInFrames, ma_int64 startOffsetInFrames) { if (pFader == NULL) { return; @@ -49375,10 +49438,15 @@ MA_API void ma_fader_set_fade(ma_fader* pFader, float volumeBeg, float volumeEnd lengthInFrames = UINT_MAX; } + /* The start offset needs to be clamped to ensure it doesn't overflow a signed number. */ + if (startOffsetInFrames > INT_MAX) { + startOffsetInFrames = INT_MAX; + } + pFader->volumeBeg = volumeBeg; pFader->volumeEnd = volumeEnd; pFader->lengthInFrames = lengthInFrames; - pFader->cursorInFrames = 0; /* Reset cursor. */ + pFader->cursorInFrames = -startOffsetInFrames; } MA_API float ma_fader_get_current_volume(const ma_fader* pFader) @@ -49387,10 +49455,15 @@ MA_API float ma_fader_get_current_volume(const ma_fader* pFader) return 0.0f; } + /* Any frames prior to the start of the fade period will be at unfaded volume. */ + if (pFader->cursorInFrames < 0) { + return 1.0f; + } + /* The current volume depends on the position of the cursor. */ if (pFader->cursorInFrames == 0) { return pFader->volumeBeg; - } else if (pFader->cursorInFrames >= pFader->lengthInFrames) { + } else if ((ma_uint64)pFader->cursorInFrames >= pFader->lengthInFrames) { /* Safe case because the < 0 case was checked above. */ 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. */ @@ -51314,8 +51387,10 @@ static ma_result ma_linear_resampler_process_pcm_frames_s16_downsample(ma_linear } } - /* Filter. */ - ma_lpf_process_pcm_frame_s16(&pResampler->lpf, pResampler->x1.s16, pResampler->x1.s16); + /* Filter. Do not apply filtering if sample rates are the same or else you'll get dangerous glitching. */ + if (pResampler->config.sampleRateIn != pResampler->config.sampleRateOut) { + ma_lpf_process_pcm_frame_s16(&pResampler->lpf, pResampler->x1.s16, pResampler->x1.s16); + } framesProcessedIn += 1; pResampler->inTimeInt -= 1; @@ -51401,8 +51476,10 @@ static ma_result ma_linear_resampler_process_pcm_frames_s16_upsample(ma_linear_r MA_ASSERT(pResampler->inTimeInt == 0); ma_linear_resampler_interpolate_frame_s16(pResampler, pFramesOutS16); - /* Filter. */ - ma_lpf_process_pcm_frame_s16(&pResampler->lpf, pFramesOutS16, pFramesOutS16); + /* Filter. Do not apply filtering if sample rates are the same or else you'll get dangerous glitching. */ + if (pResampler->config.sampleRateIn != pResampler->config.sampleRateOut) { + ma_lpf_process_pcm_frame_s16(&pResampler->lpf, pFramesOutS16, pFramesOutS16); + } pFramesOutS16 += pResampler->config.channels; } @@ -51474,8 +51551,10 @@ static ma_result ma_linear_resampler_process_pcm_frames_f32_downsample(ma_linear } } - /* Filter. */ - ma_lpf_process_pcm_frame_f32(&pResampler->lpf, pResampler->x1.f32, pResampler->x1.f32); + /* Filter. Do not apply filtering if sample rates are the same or else you'll get dangerous glitching. */ + if (pResampler->config.sampleRateIn != pResampler->config.sampleRateOut) { + ma_lpf_process_pcm_frame_f32(&pResampler->lpf, pResampler->x1.f32, pResampler->x1.f32); + } framesProcessedIn += 1; pResampler->inTimeInt -= 1; @@ -51561,8 +51640,10 @@ static ma_result ma_linear_resampler_process_pcm_frames_f32_upsample(ma_linear_r MA_ASSERT(pResampler->inTimeInt == 0); ma_linear_resampler_interpolate_frame_f32(pResampler, pFramesOutF32); - /* Filter. */ - ma_lpf_process_pcm_frame_f32(&pResampler->lpf, pFramesOutF32, pFramesOutF32); + /* Filter. Do not apply filtering if sample rates are the same or else you'll get dangerous glitching. */ + if (pResampler->config.sampleRateIn != pResampler->config.sampleRateOut) { + ma_lpf_process_pcm_frame_f32(&pResampler->lpf, pFramesOutF32, pFramesOutF32); + } pFramesOutF32 += pResampler->config.channels; } @@ -51632,7 +51713,7 @@ MA_API ma_result ma_linear_resampler_set_rate_ratio(ma_linear_resampler* pResamp return MA_INVALID_ARGS; } - d = 1000; + d = 1000000; n = (ma_uint32)(ratioInOut * d); if (n == 0) { @@ -52811,7 +52892,7 @@ static ma_result ma_channel_map_apply_mono_in_f32(float* MA_RESTRICT pFramesOut, for (iFrame = 0; iFrame < unrolledFrameCount; iFrame += 1) { __m128 in0 = _mm_set1_ps(pFramesIn[iFrame*2 + 0]); __m128 in1 = _mm_set1_ps(pFramesIn[iFrame*2 + 1]); - _mm_storeu_ps(&pFramesOut[iFrame*4 + 0], _mm_shuffle_ps(in1, in0, _MM_SHUFFLE(0, 0, 0, 0))); + _mm_storeu_ps(&pFramesOut[iFrame*4 + 0], _mm_shuffle_ps(in0, in1, _MM_SHUFFLE(0, 0, 0, 0))); } /* Tail. */ @@ -52837,7 +52918,7 @@ static ma_result ma_channel_map_apply_mono_in_f32(float* MA_RESTRICT pFramesOut, __m128 in1 = _mm_set1_ps(pFramesIn[iFrame*2 + 1]); _mm_storeu_ps(&pFramesOut[iFrame*12 + 0], in0); - _mm_storeu_ps(&pFramesOut[iFrame*12 + 4], _mm_shuffle_ps(in1, in0, _MM_SHUFFLE(0, 0, 0, 0))); + _mm_storeu_ps(&pFramesOut[iFrame*12 + 4], _mm_shuffle_ps(in0, in1, _MM_SHUFFLE(0, 0, 0, 0))); _mm_storeu_ps(&pFramesOut[iFrame*12 + 8], in1); } @@ -56500,7 +56581,7 @@ static ma_result ma_pcm_rb_data_source__on_read(ma_data_source* pDataSource, voi if (framesToRead > 0xFFFFFFFF) { framesToRead = 0xFFFFFFFF; } - + mappedFrameCount = (ma_uint32)framesToRead; result = ma_pcm_rb_acquire_read(pRB, &mappedFrameCount, &pMappedBuffer); if (result != MA_SUCCESS) { @@ -56550,7 +56631,7 @@ static ma_result ma_pcm_rb_data_source__on_get_data_format(ma_data_source* pData return MA_SUCCESS; } -static ma_data_source_vtable ma_gRBDataSourceVTable = +static ma_data_source_vtable ma_gRBDataSourceVTable = { ma_pcm_rb_data_source__on_read, NULL, /* onSeek */ @@ -57614,7 +57695,7 @@ MA_API ma_result ma_data_source_set_range_in_pcm_frames(ma_data_source* pDataSou pDataSourceBase->loopBegInFrames = 0; pDataSourceBase->loopEndInFrames = ~((ma_uint64)0); - + /* Seek to within range. Note that our seek positions here are relative to the new range. We don't want do do this if we failed to retrieve the cursor earlier on because it probably means the data source @@ -58858,80 +58939,6 @@ 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_result result; - ma_vfs_file file; - ma_file_info info; - void* pData; - size_t bytesRead; - - if (ppData != NULL) { - *ppData = NULL; - } - if (pSize != NULL) { - *pSize = 0; - } - - if (ppData == NULL) { - return MA_INVALID_ARGS; - } - - if (pFilePath != NULL) { - result = ma_vfs_open(pVFS, pFilePath, MA_OPEN_MODE_READ, &file); - } else { - result = ma_vfs_open_w(pVFS, pFilePathW, MA_OPEN_MODE_READ, &file); - } - if (result != MA_SUCCESS) { - return result; - } - - result = ma_vfs_info(pVFS, file, &info); - if (result != MA_SUCCESS) { - ma_vfs_close(pVFS, file); - return result; - } - - if (info.sizeInBytes > MA_SIZE_MAX) { - ma_vfs_close(pVFS, file); - return MA_TOO_BIG; - } - - pData = ma_malloc((size_t)info.sizeInBytes, pAllocationCallbacks); /* Safe cast. */ - if (pData == NULL) { - ma_vfs_close(pVFS, file); - return result; - } - - result = ma_vfs_read(pVFS, file, pData, (size_t)info.sizeInBytes, &bytesRead); /* Safe cast. */ - ma_vfs_close(pVFS, file); - - if (result != MA_SUCCESS) { - ma_free(pData, pAllocationCallbacks); - return result; - } - - if (pSize != NULL) { - *pSize = bytesRead; - } - - MA_ASSERT(ppData != NULL); - *ppData = pData; - - return MA_SUCCESS; -} - -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); -} - -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); -} - - #if !defined(MA_USE_WIN32_FILEIO) && (defined(MA_WIN32) && defined(MA_WIN32_DESKTOP) && !defined(MA_NO_WIN32_FILEIO) && !defined(MA_POSIX)) #define MA_USE_WIN32_FILEIO #endif @@ -59660,6 +59667,81 @@ MA_API ma_result ma_vfs_or_default_info(ma_vfs* pVFS, ma_vfs_file file, ma_file_ +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; + ma_file_info info; + void* pData; + size_t bytesRead; + + if (ppData != NULL) { + *ppData = NULL; + } + if (pSize != NULL) { + *pSize = 0; + } + + if (ppData == NULL) { + return MA_INVALID_ARGS; + } + + if (pFilePath != NULL) { + result = ma_vfs_or_default_open(pVFS, pFilePath, MA_OPEN_MODE_READ, &file); + } else { + result = ma_vfs_or_default_open_w(pVFS, pFilePathW, MA_OPEN_MODE_READ, &file); + } + if (result != MA_SUCCESS) { + return result; + } + + result = ma_vfs_or_default_info(pVFS, file, &info); + if (result != MA_SUCCESS) { + ma_vfs_or_default_close(pVFS, file); + return result; + } + + if (info.sizeInBytes > MA_SIZE_MAX) { + ma_vfs_or_default_close(pVFS, file); + return MA_TOO_BIG; + } + + pData = ma_malloc((size_t)info.sizeInBytes, pAllocationCallbacks); /* Safe cast. */ + if (pData == NULL) { + ma_vfs_or_default_close(pVFS, file); + return result; + } + + result = ma_vfs_or_default_read(pVFS, file, pData, (size_t)info.sizeInBytes, &bytesRead); /* Safe cast. */ + ma_vfs_or_default_close(pVFS, file); + + if (result != MA_SUCCESS) { + ma_free(pData, pAllocationCallbacks); + return result; + } + + if (pSize != NULL) { + *pSize = bytesRead; + } + + MA_ASSERT(ppData != NULL); + *ppData = pData; + + return MA_SUCCESS; +} + +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); +} + +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); +} + + + /************************************************************************************************************************************************************** Decoding and Encoding Headers. These are auto-generated from a tool. @@ -59676,7 +59758,7 @@ extern "C" { #define MA_DR_WAV_XSTRINGIFY(x) MA_DR_WAV_STRINGIFY(x) #define MA_DR_WAV_VERSION_MAJOR 0 #define MA_DR_WAV_VERSION_MINOR 13 -#define MA_DR_WAV_VERSION_REVISION 9 +#define MA_DR_WAV_VERSION_REVISION 13 #define MA_DR_WAV_VERSION_STRING MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_MAJOR) "." MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_MINOR) "." MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_REVISION) #include #define MA_DR_WAVE_FORMAT_PCM 0x1 @@ -59976,6 +60058,7 @@ typedef struct struct { ma_bool8 isLE; + ma_bool8 isUnsigned; } aiff; } ma_dr_wav; MA_API ma_bool32 ma_dr_wav_init(ma_dr_wav* pWav, ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks); @@ -60095,7 +60178,7 @@ extern "C" { #define MA_DR_FLAC_XSTRINGIFY(x) MA_DR_FLAC_STRINGIFY(x) #define MA_DR_FLAC_VERSION_MAJOR 0 #define MA_DR_FLAC_VERSION_MINOR 12 -#define MA_DR_FLAC_VERSION_REVISION 40 +#define MA_DR_FLAC_VERSION_REVISION 42 #define MA_DR_FLAC_VERSION_STRING MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_MAJOR) "." MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_MINOR) "." MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_REVISION) #include #if defined(_MSC_VER) && _MSC_VER >= 1700 @@ -60382,7 +60465,7 @@ extern "C" { #define MA_DR_MP3_XSTRINGIFY(x) MA_DR_MP3_STRINGIFY(x) #define MA_DR_MP3_VERSION_MAJOR 0 #define MA_DR_MP3_VERSION_MINOR 6 -#define MA_DR_MP3_VERSION_REVISION 35 +#define MA_DR_MP3_VERSION_REVISION 38 #define MA_DR_MP3_VERSION_STRING MA_DR_MP3_XSTRINGIFY(MA_DR_MP3_VERSION_MAJOR) "." MA_DR_MP3_XSTRINGIFY(MA_DR_MP3_VERSION_MINOR) "." MA_DR_MP3_XSTRINGIFY(MA_DR_MP3_VERSION_REVISION) #include #define MA_DR_MP3_MAX_PCM_FRAMES_PER_MP3_FRAME 1152 @@ -60689,7 +60772,7 @@ static ma_result ma_decoder_internal_on_tell__custom(void* pUserData, ma_int64* } -static ma_result ma_decoder_init_from_vtable(const ma_decoding_backend_vtable* pVTable, void* pVTableUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +static ma_result ma_decoder_init_from_vtable__internal(const ma_decoding_backend_vtable* pVTable, void* pVTableUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) { ma_result result; ma_decoding_backend_config backendConfig; @@ -60718,6 +60801,93 @@ static ma_result ma_decoder_init_from_vtable(const ma_decoding_backend_vtable* p return MA_SUCCESS; } +static ma_result ma_decoder_init_from_file__internal(const ma_decoding_backend_vtable* pVTable, void* pVTableUserData, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result; + ma_decoding_backend_config backendConfig; + ma_data_source* pBackend; + + MA_ASSERT(pVTable != NULL); + MA_ASSERT(pConfig != NULL); + MA_ASSERT(pDecoder != NULL); + + if (pVTable->onInitFile == NULL) { + return MA_NOT_IMPLEMENTED; + } + + backendConfig = ma_decoding_backend_config_init(pConfig->format, pConfig->seekPointCount); + + result = pVTable->onInitFile(pVTableUserData, pFilePath, &backendConfig, &pDecoder->allocationCallbacks, &pBackend); + if (result != MA_SUCCESS) { + return result; /* Failed to initialize the backend from this vtable. */ + } + + /* Getting here means we were able to initialize the backend so we can now initialize the decoder. */ + pDecoder->pBackend = pBackend; + pDecoder->pBackendVTable = pVTable; + pDecoder->pBackendUserData = pConfig->pCustomBackendUserData; + + return MA_SUCCESS; +} + +static ma_result ma_decoder_init_from_file_w__internal(const ma_decoding_backend_vtable* pVTable, void* pVTableUserData, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result; + ma_decoding_backend_config backendConfig; + ma_data_source* pBackend; + + MA_ASSERT(pVTable != NULL); + MA_ASSERT(pConfig != NULL); + MA_ASSERT(pDecoder != NULL); + + if (pVTable->onInitFileW == NULL) { + return MA_NOT_IMPLEMENTED; + } + + backendConfig = ma_decoding_backend_config_init(pConfig->format, pConfig->seekPointCount); + + result = pVTable->onInitFileW(pVTableUserData, pFilePath, &backendConfig, &pDecoder->allocationCallbacks, &pBackend); + if (result != MA_SUCCESS) { + return result; /* Failed to initialize the backend from this vtable. */ + } + + /* Getting here means we were able to initialize the backend so we can now initialize the decoder. */ + pDecoder->pBackend = pBackend; + pDecoder->pBackendVTable = pVTable; + pDecoder->pBackendUserData = pConfig->pCustomBackendUserData; + + return MA_SUCCESS; +} + +static ma_result ma_decoder_init_from_memory__internal(const ma_decoding_backend_vtable* pVTable, void* pVTableUserData, const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result; + ma_decoding_backend_config backendConfig; + ma_data_source* pBackend; + + MA_ASSERT(pVTable != NULL); + MA_ASSERT(pConfig != NULL); + MA_ASSERT(pDecoder != NULL); + + if (pVTable->onInitMemory == NULL) { + return MA_NOT_IMPLEMENTED; + } + + backendConfig = ma_decoding_backend_config_init(pConfig->format, pConfig->seekPointCount); + + result = pVTable->onInitMemory(pVTableUserData, pData, dataSize, &backendConfig, &pDecoder->allocationCallbacks, &pBackend); + if (result != MA_SUCCESS) { + return result; /* Failed to initialize the backend from this vtable. */ + } + + /* Getting here means we were able to initialize the backend so we can now initialize the decoder. */ + pDecoder->pBackend = pBackend; + pDecoder->pBackendVTable = pVTable; + pDecoder->pBackendUserData = pConfig->pCustomBackendUserData; + + return MA_SUCCESS; +} + static ma_result ma_decoder_init_custom__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) @@ -60735,8 +60905,8 @@ static ma_result ma_decoder_init_custom__internal(const ma_decoder_config* pConf /* The order each backend is listed is what defines the priority. */ for (ivtable = 0; ivtable < pConfig->customBackendCount; ivtable += 1) { const ma_decoding_backend_vtable* pVTable = pConfig->ppCustomBackendVTables[ivtable]; - if (pVTable != NULL && pVTable->onInit != NULL) { - result = ma_decoder_init_from_vtable(pVTable, pConfig->pCustomBackendUserData, pConfig, pDecoder); + if (pVTable != NULL) { + result = ma_decoder_init_from_vtable__internal(pVTable, pConfig->pCustomBackendUserData, pConfig, pDecoder); if (result == MA_SUCCESS) { return MA_SUCCESS; } else { @@ -60755,6 +60925,93 @@ static ma_result ma_decoder_init_custom__internal(const ma_decoder_config* pConf return MA_NO_BACKEND; } +static ma_result ma_decoder_init_custom_from_file__internal(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result = MA_NO_BACKEND; + size_t ivtable; + + MA_ASSERT(pConfig != NULL); + MA_ASSERT(pDecoder != NULL); + + if (pConfig->ppCustomBackendVTables == NULL) { + return MA_NO_BACKEND; + } + + /* The order each backend is listed is what defines the priority. */ + for (ivtable = 0; ivtable < pConfig->customBackendCount; ivtable += 1) { + const ma_decoding_backend_vtable* pVTable = pConfig->ppCustomBackendVTables[ivtable]; + if (pVTable != NULL) { + result = ma_decoder_init_from_file__internal(pVTable, pConfig->pCustomBackendUserData, pFilePath, pConfig, pDecoder); + if (result == MA_SUCCESS) { + return MA_SUCCESS; + } + } else { + /* No vtable. */ + } + } + + /* Getting here means we couldn't find a backend. */ + return MA_NO_BACKEND; +} + +static ma_result ma_decoder_init_custom_from_file_w__internal(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result = MA_NO_BACKEND; + size_t ivtable; + + MA_ASSERT(pConfig != NULL); + MA_ASSERT(pDecoder != NULL); + + if (pConfig->ppCustomBackendVTables == NULL) { + return MA_NO_BACKEND; + } + + /* The order each backend is listed is what defines the priority. */ + for (ivtable = 0; ivtable < pConfig->customBackendCount; ivtable += 1) { + const ma_decoding_backend_vtable* pVTable = pConfig->ppCustomBackendVTables[ivtable]; + if (pVTable != NULL) { + result = ma_decoder_init_from_file_w__internal(pVTable, pConfig->pCustomBackendUserData, pFilePath, pConfig, pDecoder); + if (result == MA_SUCCESS) { + return MA_SUCCESS; + } + } else { + /* No vtable. */ + } + } + + /* Getting here means we couldn't find a backend. */ + return MA_NO_BACKEND; +} + +static ma_result ma_decoder_init_custom_from_memory__internal(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result = MA_NO_BACKEND; + size_t ivtable; + + MA_ASSERT(pConfig != NULL); + MA_ASSERT(pDecoder != NULL); + + if (pConfig->ppCustomBackendVTables == NULL) { + return MA_NO_BACKEND; + } + + /* The order each backend is listed is what defines the priority. */ + for (ivtable = 0; ivtable < pConfig->customBackendCount; ivtable += 1) { + const ma_decoding_backend_vtable* pVTable = pConfig->ppCustomBackendVTables[ivtable]; + if (pVTable != NULL) { + result = ma_decoder_init_from_memory__internal(pVTable, pConfig->pCustomBackendUserData, pData, dataSize, pConfig, pDecoder); + if (result == MA_SUCCESS) { + return MA_SUCCESS; + } + } else { + /* No vtable. */ + } + } + + /* Getting here means we couldn't find a backend. */ + return MA_NO_BACKEND; +} + /* WAV */ #ifdef ma_dr_wav_h @@ -60888,6 +61145,47 @@ static ma_result ma_wav_init_internal(const ma_decoding_backend_config* pConfig, return MA_SUCCESS; } +static ma_result ma_wav_post_init(ma_wav* pWav) +{ + /* + If an explicit format was not specified, try picking the closest match based on the internal + format. The format needs to be supported by miniaudio. + */ + if (pWav->format == ma_format_unknown) { + switch (pWav->dr.translatedFormatTag) + { + case MA_DR_WAVE_FORMAT_PCM: + { + if (pWav->dr.bitsPerSample == 8) { + pWav->format = ma_format_u8; + } else if (pWav->dr.bitsPerSample == 16) { + pWav->format = ma_format_s16; + } else if (pWav->dr.bitsPerSample == 24) { + pWav->format = ma_format_s24; + } else if (pWav->dr.bitsPerSample == 32) { + pWav->format = ma_format_s32; + } + } break; + + case MA_DR_WAVE_FORMAT_IEEE_FLOAT: + { + if (pWav->dr.bitsPerSample == 32) { + pWav->format = ma_format_f32; + } + } break; + + default: break; + } + + /* Fall back to f32 if we couldn't find anything. */ + if (pWav->format == ma_format_unknown) { + pWav->format = ma_format_f32; + } + } + + return MA_SUCCESS; +} + MA_API ma_result ma_wav_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_wav* pWav) { ma_result result; @@ -60915,41 +61213,7 @@ MA_API ma_result ma_wav_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_p return MA_INVALID_FILE; } - /* - If an explicit format was not specified, try picking the closest match based on the internal - format. The format needs to be supported by miniaudio. - */ - if (pWav->format == ma_format_unknown) { - switch (pWav->dr.translatedFormatTag) - { - case MA_DR_WAVE_FORMAT_PCM: - { - if (pWav->dr.bitsPerSample == 8) { - pWav->format = ma_format_u8; - } else if (pWav->dr.bitsPerSample == 16) { - pWav->format = ma_format_s16; - } else if (pWav->dr.bitsPerSample == 24) { - pWav->format = ma_format_s24; - } else if (pWav->dr.bitsPerSample == 32) { - pWav->format = ma_format_s32; - } - } break; - - case MA_DR_WAVE_FORMAT_IEEE_FLOAT: - { - if (pWav->dr.bitsPerSample == 32) { - pWav->format = ma_format_f32; - } - } break; - - default: break; - } - - /* Fall back to f32 if we couldn't find anything. */ - if (pWav->format == ma_format_unknown) { - pWav->format = ma_format_f32; - } - } + ma_wav_post_init(pWav); return MA_SUCCESS; } @@ -60980,6 +61244,8 @@ MA_API ma_result ma_wav_init_file(const char* pFilePath, const ma_decoding_backe return MA_INVALID_FILE; } + ma_wav_post_init(pWav); + return MA_SUCCESS; } #else @@ -61010,6 +61276,8 @@ MA_API ma_result ma_wav_init_file_w(const wchar_t* pFilePath, const ma_decoding_ return MA_INVALID_FILE; } + ma_wav_post_init(pWav); + return MA_SUCCESS; } #else @@ -61040,6 +61308,8 @@ MA_API ma_result ma_wav_init_memory(const void* pData, size_t dataSize, const ma return MA_INVALID_FILE; } + ma_wav_post_init(pWav); + return MA_SUCCESS; } #else @@ -61408,7 +61678,22 @@ static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_wav = static ma_result ma_decoder_init_wav__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) { - return ma_decoder_init_from_vtable(&g_ma_decoding_backend_vtable_wav, NULL, pConfig, pDecoder); + return ma_decoder_init_from_vtable__internal(&g_ma_decoding_backend_vtable_wav, NULL, pConfig, pDecoder); +} + +static ma_result ma_decoder_init_wav_from_file__internal(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + return ma_decoder_init_from_file__internal(&g_ma_decoding_backend_vtable_wav, NULL, pFilePath, pConfig, pDecoder); +} + +static ma_result ma_decoder_init_wav_from_file_w__internal(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + return ma_decoder_init_from_file_w__internal(&g_ma_decoding_backend_vtable_wav, NULL, pFilePath, pConfig, pDecoder); +} + +static ma_result ma_decoder_init_wav_from_memory__internal(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + return ma_decoder_init_from_memory__internal(&g_ma_decoding_backend_vtable_wav, NULL, pData, dataSize, pConfig, pDecoder); } #endif /* ma_dr_wav_h */ @@ -62015,7 +62300,22 @@ static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_flac = static ma_result ma_decoder_init_flac__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) { - return ma_decoder_init_from_vtable(&g_ma_decoding_backend_vtable_flac, NULL, pConfig, pDecoder); + return ma_decoder_init_from_vtable__internal(&g_ma_decoding_backend_vtable_flac, NULL, pConfig, pDecoder); +} + +static ma_result ma_decoder_init_flac_from_file__internal(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + return ma_decoder_init_from_file__internal(&g_ma_decoding_backend_vtable_flac, NULL, pFilePath, pConfig, pDecoder); +} + +static ma_result ma_decoder_init_flac_from_file_w__internal(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + return ma_decoder_init_from_file_w__internal(&g_ma_decoding_backend_vtable_flac, NULL, pFilePath, pConfig, pDecoder); +} + +static ma_result ma_decoder_init_flac_from_memory__internal(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + return ma_decoder_init_from_memory__internal(&g_ma_decoding_backend_vtable_flac, NULL, pData, dataSize, pConfig, pDecoder); } #endif /* ma_dr_flac_h */ @@ -62188,6 +62488,18 @@ static ma_result ma_mp3_generate_seek_table(ma_mp3* pMP3, const ma_decoding_back return MA_SUCCESS; } +static ma_result ma_mp3_post_init(ma_mp3* pMP3, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_result result; + + result = ma_mp3_generate_seek_table(pMP3, pConfig, pAllocationCallbacks); + if (result != MA_SUCCESS) { + return result; + } + + 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; @@ -62215,7 +62527,7 @@ 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); + ma_mp3_post_init(pMP3, pConfig, pAllocationCallbacks); return MA_SUCCESS; } @@ -62246,7 +62558,7 @@ 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); + ma_mp3_post_init(pMP3, pConfig, pAllocationCallbacks); return MA_SUCCESS; } @@ -62278,7 +62590,7 @@ 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); + ma_mp3_post_init(pMP3, pConfig, pAllocationCallbacks); return MA_SUCCESS; } @@ -62310,7 +62622,7 @@ 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); + ma_mp3_post_init(pMP3, pConfig, pAllocationCallbacks); return MA_SUCCESS; } @@ -62668,7 +62980,22 @@ static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_mp3 = static ma_result ma_decoder_init_mp3__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) { - return ma_decoder_init_from_vtable(&g_ma_decoding_backend_vtable_mp3, NULL, pConfig, pDecoder); + return ma_decoder_init_from_vtable__internal(&g_ma_decoding_backend_vtable_mp3, NULL, pConfig, pDecoder); +} + +static ma_result ma_decoder_init_mp3_from_file__internal(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + return ma_decoder_init_from_file__internal(&g_ma_decoding_backend_vtable_mp3, NULL, pFilePath, pConfig, pDecoder); +} + +static ma_result ma_decoder_init_mp3_from_file_w__internal(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + return ma_decoder_init_from_file_w__internal(&g_ma_decoding_backend_vtable_mp3, NULL, pFilePath, pConfig, pDecoder); +} + +static ma_result ma_decoder_init_mp3_from_memory__internal(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + return ma_decoder_init_from_memory__internal(&g_ma_decoding_backend_vtable_mp3, NULL, pData, dataSize, pConfig, pDecoder); } #endif /* ma_dr_mp3_h */ @@ -63250,8 +63577,6 @@ MA_API ma_result ma_stbvorbis_seek_to_pcm_frame(ma_stbvorbis* pVorbis, ma_uint64 } result = ma_stbvorbis_read_pcm_frames(pVorbis, buffer, framesToRead, &framesRead); - pVorbis->cursor += framesRead; - if (result != MA_SUCCESS) { return result; } @@ -63487,7 +63812,22 @@ static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_stbvorbis = static ma_result ma_decoder_init_vorbis__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) { - return ma_decoder_init_from_vtable(&g_ma_decoding_backend_vtable_stbvorbis, NULL, pConfig, pDecoder); + return ma_decoder_init_from_vtable__internal(&g_ma_decoding_backend_vtable_stbvorbis, NULL, pConfig, pDecoder); +} + +static ma_result ma_decoder_init_vorbis_from_file__internal(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + return ma_decoder_init_from_file__internal(&g_ma_decoding_backend_vtable_stbvorbis, NULL, pFilePath, pConfig, pDecoder); +} + +static ma_result ma_decoder_init_vorbis_from_file_w__internal(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + return ma_decoder_init_from_file_w__internal(&g_ma_decoding_backend_vtable_stbvorbis, NULL, pFilePath, pConfig, pDecoder); +} + +static ma_result ma_decoder_init_vorbis_from_memory__internal(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + return ma_decoder_init_from_memory__internal(&g_ma_decoding_backend_vtable_stbvorbis, NULL, pData, dataSize, pConfig, pDecoder); } #endif /* STB_VORBIS_INCLUDE_STB_VORBIS_H */ @@ -63554,10 +63894,6 @@ static ma_result ma_decoder__preinit(ma_decoder_read_proc onRead, ma_decoder_see MA_ZERO_OBJECT(pDecoder); - if (onRead == NULL || onSeek == NULL) { - return MA_INVALID_ARGS; - } - dataSourceConfig = ma_data_source_config_init(); dataSourceConfig.vtable = &g_ma_decoder_data_source_vtable; @@ -63801,7 +64137,7 @@ static ma_result ma_decoder__on_tell_memory(ma_decoder* pDecoder, ma_int64* pCur return MA_SUCCESS; } -static ma_result ma_decoder__preinit_memory(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +static ma_result ma_decoder__preinit_memory_wrapper(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) { ma_result result = ma_decoder__preinit(ma_decoder__on_read_memory, ma_decoder__on_seek_memory, ma_decoder__on_tell_memory, NULL, pConfig, pDecoder); if (result != MA_SUCCESS) { @@ -63822,17 +64158,121 @@ static ma_result ma_decoder__preinit_memory(const void* pData, size_t dataSize, MA_API ma_result ma_decoder_init_memory(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) { - ma_decoder_config config; ma_result result; + ma_decoder_config config; - config = ma_decoder_config_init_copy(pConfig); /* Make sure the config is not NULL. */ + config = ma_decoder_config_init_copy(pConfig); - result = ma_decoder__preinit_memory(pData, dataSize, &config, pDecoder); + result = ma_decoder__preinit(NULL, NULL, NULL, NULL, &config, pDecoder); if (result != MA_SUCCESS) { return result; } - return ma_decoder_init__internal(ma_decoder__on_read_memory, ma_decoder__on_seek_memory, NULL, &config, pDecoder); + if (pData == NULL || dataSize == 0) { + return MA_INVALID_ARGS; + } + + /* If the backend has support for loading from a file path we'll want to use that. If that all fails we'll fall back to the VFS path. */ + result = MA_NO_BACKEND; + + if (config.encodingFormat != ma_encoding_format_unknown) { + #ifdef MA_HAS_WAV + if (config.encodingFormat == ma_encoding_format_wav) { + result = ma_decoder_init_wav_from_memory__internal(pData, dataSize, &config, pDecoder); + } + #endif + #ifdef MA_HAS_FLAC + if (config.encodingFormat == ma_encoding_format_flac) { + result = ma_decoder_init_flac_from_memory__internal(pData, dataSize, &config, pDecoder); + } + #endif + #ifdef MA_HAS_MP3 + if (config.encodingFormat == ma_encoding_format_mp3) { + result = ma_decoder_init_mp3_from_memory__internal(pData, dataSize, &config, pDecoder); + } + #endif + #ifdef MA_HAS_VORBIS + if (config.encodingFormat == ma_encoding_format_vorbis) { + result = ma_decoder_init_vorbis_from_memory__internal(pData, dataSize, &config, pDecoder); + } + #endif + } + + if (result != MA_SUCCESS) { + /* Getting here means we weren't able to initialize a decoder of a specific encoding format. */ + + /* + We use trial and error to open a decoder. We prioritize custom decoders so that if they + implement the same encoding format they take priority over the built-in decoders. + */ + result = ma_decoder_init_custom_from_memory__internal(pData, dataSize, &config, pDecoder); + + /* + If we get to this point and we still haven't found a decoder, and the caller has requested a + specific encoding format, there's no hope for it. Abort. + */ + if (result != MA_SUCCESS && config.encodingFormat != ma_encoding_format_unknown) { + return MA_NO_BACKEND; + } + + /* Use trial and error for stock decoders. */ + if (result != MA_SUCCESS) { + #ifdef MA_HAS_WAV + if (result != MA_SUCCESS) { + result = ma_decoder_init_wav_from_memory__internal(pData, dataSize, &config, pDecoder); + } + #endif + #ifdef MA_HAS_FLAC + if (result != MA_SUCCESS) { + result = ma_decoder_init_flac_from_memory__internal(pData, dataSize, &config, pDecoder); + } + #endif + #ifdef MA_HAS_MP3 + if (result != MA_SUCCESS) { + result = ma_decoder_init_mp3_from_memory__internal(pData, dataSize, &config, pDecoder); + } + #endif + #ifdef MA_HAS_VORBIS + if (result != MA_SUCCESS) { + result = ma_decoder_init_vorbis_from_memory__internal(pData, dataSize, &config, pDecoder); + } + #endif + } + } + + /* + If at this point we still haven't successfully initialized the decoder it most likely means + the backend doesn't have an implementation for loading from a file path. We'll try using + miniaudio's built-in file IO for loading file. + */ + if (result == MA_SUCCESS) { + /* Initialization was successful. Finish up. */ + result = ma_decoder__postinit(&config, pDecoder); + if (result != MA_SUCCESS) { + /* + The backend was initialized successfully, but for some reason post-initialization failed. This is most likely + due to an out of memory error. We're going to abort with an error here and not try to recover. + */ + if (pDecoder->pBackendVTable != NULL && pDecoder->pBackendVTable->onUninit != NULL) { + pDecoder->pBackendVTable->onUninit(pDecoder->pBackendUserData, &pDecoder->pBackend, &pDecoder->allocationCallbacks); + } + + return result; + } + } else { + /* Probably no implementation for loading from a block of memory. Use miniaudio's abstraction instead. */ + result = ma_decoder__preinit_memory_wrapper(pData, dataSize, &config, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + + result = ma_decoder_init__internal(ma_decoder__on_read_memory, ma_decoder__on_seek_memory, NULL, &config, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + } + + return MA_SUCCESS; } @@ -64126,7 +64566,140 @@ MA_API ma_result ma_decoder_init_vfs(ma_vfs* pVFS, const char* pFilePath, const } #ifdef MA_HAS_WAV - if (result != MA_SUCCESS && ma_path_extension_equal(pFilePath, "wav")) { + if (result != MA_SUCCESS && ma_path_extension_equal(pFilePath, "wav")) { + result = ma_decoder_init_wav__internal(&config, pDecoder); + if (result != MA_SUCCESS) { + ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); + } + } + #endif + #ifdef MA_HAS_FLAC + if (result != MA_SUCCESS && ma_path_extension_equal(pFilePath, "flac")) { + result = ma_decoder_init_flac__internal(&config, pDecoder); + if (result != MA_SUCCESS) { + ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); + } + } + #endif + #ifdef MA_HAS_MP3 + if (result != MA_SUCCESS && ma_path_extension_equal(pFilePath, "mp3")) { + result = ma_decoder_init_mp3__internal(&config, pDecoder); + if (result != MA_SUCCESS) { + ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); + } + } + #endif + } + + /* If we still haven't got a result just use trial and error. Otherwise we can finish up. */ + if (result != MA_SUCCESS) { + result = ma_decoder_init__internal(ma_decoder__on_read_vfs, ma_decoder__on_seek_vfs, NULL, &config, pDecoder); + } else { + result = ma_decoder__postinit(&config, pDecoder); + } + + if (result != MA_SUCCESS) { + if (pDecoder->data.vfs.file != NULL) { /* <-- Will be reset to NULL if ma_decoder_uninit() is called in one of the steps above which allows us to avoid a double close of the file. */ + ma_vfs_or_default_close(pVFS, pDecoder->data.vfs.file); + } + + return result; + } + + return MA_SUCCESS; +} + + +static ma_result ma_decoder__preinit_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result; + ma_vfs_file file; + + result = ma_decoder__preinit(ma_decoder__on_read_vfs, ma_decoder__on_seek_vfs, ma_decoder__on_tell_vfs, NULL, pConfig, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + + if (pFilePath == NULL || pFilePath[0] == '\0') { + return MA_INVALID_ARGS; + } + + result = ma_vfs_or_default_open_w(pVFS, pFilePath, MA_OPEN_MODE_READ, &file); + if (result != MA_SUCCESS) { + return result; + } + + pDecoder->data.vfs.pVFS = pVFS; + pDecoder->data.vfs.file = file; + + return MA_SUCCESS; +} + +MA_API ma_result ma_decoder_init_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result; + ma_decoder_config config; + + config = ma_decoder_config_init_copy(pConfig); + result = ma_decoder__preinit_vfs_w(pVFS, pFilePath, &config, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + + result = MA_NO_BACKEND; + + if (config.encodingFormat != ma_encoding_format_unknown) { + #ifdef MA_HAS_WAV + if (config.encodingFormat == ma_encoding_format_wav) { + result = ma_decoder_init_wav__internal(&config, pDecoder); + } + #endif + #ifdef MA_HAS_FLAC + if (config.encodingFormat == ma_encoding_format_flac) { + result = ma_decoder_init_flac__internal(&config, pDecoder); + } + #endif + #ifdef MA_HAS_MP3 + if (config.encodingFormat == ma_encoding_format_mp3) { + result = ma_decoder_init_mp3__internal(&config, pDecoder); + } + #endif + #ifdef MA_HAS_VORBIS + if (config.encodingFormat == ma_encoding_format_vorbis) { + result = ma_decoder_init_vorbis__internal(&config, pDecoder); + } + #endif + + /* Make sure we seek back to the start if we didn't initialize a decoder successfully so the next attempts have a fresh start. */ + if (result != MA_SUCCESS) { + ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); + } + } + + if (result != MA_SUCCESS) { + /* Getting here means we weren't able to initialize a decoder of a specific encoding format. */ + + /* + We use trial and error to open a decoder. We prioritize custom decoders so that if they + implement the same encoding format they take priority over the built-in decoders. + */ + if (result != MA_SUCCESS) { + result = ma_decoder_init_custom__internal(&config, pDecoder); + if (result != MA_SUCCESS) { + ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); + } + } + + /* + If we get to this point and we still haven't found a decoder, and the caller has requested a + specific encoding format, there's no hope for it. Abort. + */ + if (config.encodingFormat != ma_encoding_format_unknown) { + return MA_NO_BACKEND; + } + + #ifdef MA_HAS_WAV + if (result != MA_SUCCESS && ma_path_extension_equal_w(pFilePath, L"wav")) { result = ma_decoder_init_wav__internal(&config, pDecoder); if (result != MA_SUCCESS) { ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); @@ -64134,7 +64707,7 @@ MA_API ma_result ma_decoder_init_vfs(ma_vfs* pVFS, const char* pFilePath, const } #endif #ifdef MA_HAS_FLAC - if (result != MA_SUCCESS && ma_path_extension_equal(pFilePath, "flac")) { + if (result != MA_SUCCESS && ma_path_extension_equal_w(pFilePath, L"flac")) { result = ma_decoder_init_flac__internal(&config, pDecoder); if (result != MA_SUCCESS) { ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); @@ -64142,7 +64715,7 @@ MA_API ma_result ma_decoder_init_vfs(ma_vfs* pVFS, const char* pFilePath, const } #endif #ifdef MA_HAS_MP3 - if (result != MA_SUCCESS && ma_path_extension_equal(pFilePath, "mp3")) { + if (result != MA_SUCCESS && ma_path_extension_equal_w(pFilePath, L"mp3")) { result = ma_decoder_init_mp3__internal(&config, pDecoder); if (result != MA_SUCCESS) { ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); @@ -64159,10 +64732,7 @@ MA_API ma_result ma_decoder_init_vfs(ma_vfs* pVFS, const char* pFilePath, const } if (result != MA_SUCCESS) { - if (pDecoder->data.vfs.file != NULL) { /* <-- Will be reset to NULL if ma_decoder_uninit() is called in one of the steps above which allows us to avoid a double close of the file. */ - ma_vfs_or_default_close(pVFS, pDecoder->data.vfs.file); - } - + ma_vfs_or_default_close(pVFS, pDecoder->data.vfs.file); return result; } @@ -64170,12 +64740,11 @@ MA_API ma_result ma_decoder_init_vfs(ma_vfs* pVFS, const char* pFilePath, const } -static ma_result ma_decoder__preinit_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +static ma_result ma_decoder__preinit_file(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) { ma_result result; - ma_vfs_file file; - result = ma_decoder__preinit(ma_decoder__on_read_vfs, ma_decoder__on_seek_vfs, ma_decoder__on_tell_vfs, NULL, pConfig, pDecoder); + result = ma_decoder__preinit(NULL, NULL, NULL, NULL, pConfig, pDecoder); if (result != MA_SUCCESS) { return result; } @@ -64184,56 +64753,194 @@ static ma_result ma_decoder__preinit_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePat return MA_INVALID_ARGS; } - result = ma_vfs_or_default_open_w(pVFS, pFilePath, MA_OPEN_MODE_READ, &file); + return MA_SUCCESS; +} + +MA_API ma_result ma_decoder_init_file(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result; + ma_decoder_config config; + + config = ma_decoder_config_init_copy(pConfig); + result = ma_decoder__preinit_file(pFilePath, &config, pDecoder); if (result != MA_SUCCESS) { return result; } - pDecoder->data.vfs.pVFS = pVFS; - pDecoder->data.vfs.file = file; + /* If the backend has support for loading from a file path we'll want to use that. If that all fails we'll fall back to the VFS path. */ + result = MA_NO_BACKEND; + + if (config.encodingFormat != ma_encoding_format_unknown) { + #ifdef MA_HAS_WAV + if (config.encodingFormat == ma_encoding_format_wav) { + result = ma_decoder_init_wav_from_file__internal(pFilePath, &config, pDecoder); + } + #endif + #ifdef MA_HAS_FLAC + if (config.encodingFormat == ma_encoding_format_flac) { + result = ma_decoder_init_flac_from_file__internal(pFilePath, &config, pDecoder); + } + #endif + #ifdef MA_HAS_MP3 + if (config.encodingFormat == ma_encoding_format_mp3) { + result = ma_decoder_init_mp3_from_file__internal(pFilePath, &config, pDecoder); + } + #endif + #ifdef MA_HAS_VORBIS + if (config.encodingFormat == ma_encoding_format_vorbis) { + result = ma_decoder_init_vorbis_from_file__internal(pFilePath, &config, pDecoder); + } + #endif + } + + if (result != MA_SUCCESS) { + /* Getting here means we weren't able to initialize a decoder of a specific encoding format. */ + + /* + We use trial and error to open a decoder. We prioritize custom decoders so that if they + implement the same encoding format they take priority over the built-in decoders. + */ + result = ma_decoder_init_custom_from_file__internal(pFilePath, &config, pDecoder); + + /* + If we get to this point and we still haven't found a decoder, and the caller has requested a + specific encoding format, there's no hope for it. Abort. + */ + if (result != MA_SUCCESS && config.encodingFormat != ma_encoding_format_unknown) { + return MA_NO_BACKEND; + } + + /* First try loading based on the file extension so we don't waste time opening and closing files. */ + #ifdef MA_HAS_WAV + if (result != MA_SUCCESS && ma_path_extension_equal(pFilePath, "wav")) { + result = ma_decoder_init_wav_from_file__internal(pFilePath, &config, pDecoder); + } + #endif + #ifdef MA_HAS_FLAC + if (result != MA_SUCCESS && ma_path_extension_equal(pFilePath, "flac")) { + result = ma_decoder_init_flac_from_file__internal(pFilePath, &config, pDecoder); + } + #endif + #ifdef MA_HAS_MP3 + if (result != MA_SUCCESS && ma_path_extension_equal(pFilePath, "mp3")) { + result = ma_decoder_init_mp3_from_file__internal(pFilePath, &config, pDecoder); + } + #endif + #ifdef MA_HAS_VORBIS + if (result != MA_SUCCESS && ma_path_extension_equal(pFilePath, "ogg")) { + result = ma_decoder_init_vorbis_from_file__internal(pFilePath, &config, pDecoder); + } + #endif + + /* + If we still haven't got a result just use trial and error. Custom decoders have already been attempted, so here we + need only iterate over our stock decoders. + */ + if (result != MA_SUCCESS) { + #ifdef MA_HAS_WAV + if (result != MA_SUCCESS) { + result = ma_decoder_init_wav_from_file__internal(pFilePath, &config, pDecoder); + } + #endif + #ifdef MA_HAS_FLAC + if (result != MA_SUCCESS) { + result = ma_decoder_init_flac_from_file__internal(pFilePath, &config, pDecoder); + } + #endif + #ifdef MA_HAS_MP3 + if (result != MA_SUCCESS) { + result = ma_decoder_init_mp3_from_file__internal(pFilePath, &config, pDecoder); + } + #endif + #ifdef MA_HAS_VORBIS + if (result != MA_SUCCESS) { + result = ma_decoder_init_vorbis_from_file__internal(pFilePath, &config, pDecoder); + } + #endif + } + } + + /* + If at this point we still haven't successfully initialized the decoder it most likely means + the backend doesn't have an implementation for loading from a file path. We'll try using + miniaudio's built-in file IO for loading file. + */ + if (result == MA_SUCCESS) { + /* Initialization was successful. Finish up. */ + result = ma_decoder__postinit(&config, pDecoder); + if (result != MA_SUCCESS) { + /* + The backend was initialized successfully, but for some reason post-initialization failed. This is most likely + due to an out of memory error. We're going to abort with an error here and not try to recover. + */ + if (pDecoder->pBackendVTable != NULL && pDecoder->pBackendVTable->onUninit != NULL) { + pDecoder->pBackendVTable->onUninit(pDecoder->pBackendUserData, &pDecoder->pBackend, &pDecoder->allocationCallbacks); + } + + return result; + } + } else { + /* Probably no implementation for loading from a file path. Use miniaudio's file IO instead. */ + result = ma_decoder_init_vfs(NULL, pFilePath, pConfig, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + } return MA_SUCCESS; } -MA_API ma_result ma_decoder_init_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +static ma_result ma_decoder__preinit_file_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result; + + result = ma_decoder__preinit(NULL, NULL, NULL, NULL, pConfig, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + + if (pFilePath == NULL || pFilePath[0] == '\0') { + return MA_INVALID_ARGS; + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_decoder_init_file_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) { ma_result result; ma_decoder_config config; config = ma_decoder_config_init_copy(pConfig); - result = ma_decoder__preinit_vfs_w(pVFS, pFilePath, &config, pDecoder); + result = ma_decoder__preinit_file_w(pFilePath, &config, pDecoder); if (result != MA_SUCCESS) { return result; } + /* If the backend has support for loading from a file path we'll want to use that. If that all fails we'll fall back to the VFS path. */ result = MA_NO_BACKEND; if (config.encodingFormat != ma_encoding_format_unknown) { #ifdef MA_HAS_WAV if (config.encodingFormat == ma_encoding_format_wav) { - result = ma_decoder_init_wav__internal(&config, pDecoder); + result = ma_decoder_init_wav_from_file_w__internal(pFilePath, &config, pDecoder); } #endif #ifdef MA_HAS_FLAC if (config.encodingFormat == ma_encoding_format_flac) { - result = ma_decoder_init_flac__internal(&config, pDecoder); + result = ma_decoder_init_flac_from_file_w__internal(pFilePath, &config, pDecoder); } #endif #ifdef MA_HAS_MP3 if (config.encodingFormat == ma_encoding_format_mp3) { - result = ma_decoder_init_mp3__internal(&config, pDecoder); + result = ma_decoder_init_mp3_from_file_w__internal(pFilePath, &config, pDecoder); } #endif #ifdef MA_HAS_VORBIS if (config.encodingFormat == ma_encoding_format_vorbis) { - result = ma_decoder_init_vorbis__internal(&config, pDecoder); + result = ma_decoder_init_vorbis_from_file_w__internal(pFilePath, &config, pDecoder); } #endif - - /* Make sure we seek back to the start if we didn't initialize a decoder successfully so the next attempts have a fresh start. */ - if (result != MA_SUCCESS) { - ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); - } } if (result != MA_SUCCESS) { @@ -64243,72 +64950,96 @@ MA_API ma_result ma_decoder_init_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePath, c We use trial and error to open a decoder. We prioritize custom decoders so that if they implement the same encoding format they take priority over the built-in decoders. */ - if (result != MA_SUCCESS) { - result = ma_decoder_init_custom__internal(&config, pDecoder); - if (result != MA_SUCCESS) { - ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); - } - } + result = ma_decoder_init_custom_from_file_w__internal(pFilePath, &config, pDecoder); /* If we get to this point and we still haven't found a decoder, and the caller has requested a specific encoding format, there's no hope for it. Abort. */ - if (config.encodingFormat != ma_encoding_format_unknown) { + if (result != MA_SUCCESS && config.encodingFormat != ma_encoding_format_unknown) { return MA_NO_BACKEND; } + /* First try loading based on the file extension so we don't waste time opening and closing files. */ #ifdef MA_HAS_WAV if (result != MA_SUCCESS && ma_path_extension_equal_w(pFilePath, L"wav")) { - result = ma_decoder_init_wav__internal(&config, pDecoder); - if (result != MA_SUCCESS) { - ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); - } + result = ma_decoder_init_wav_from_file_w__internal(pFilePath, &config, pDecoder); } #endif #ifdef MA_HAS_FLAC if (result != MA_SUCCESS && ma_path_extension_equal_w(pFilePath, L"flac")) { - result = ma_decoder_init_flac__internal(&config, pDecoder); - if (result != MA_SUCCESS) { - ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); - } + result = ma_decoder_init_flac_from_file_w__internal(pFilePath, &config, pDecoder); } #endif #ifdef MA_HAS_MP3 if (result != MA_SUCCESS && ma_path_extension_equal_w(pFilePath, L"mp3")) { - result = ma_decoder_init_mp3__internal(&config, pDecoder); + result = ma_decoder_init_mp3_from_file_w__internal(pFilePath, &config, pDecoder); + } + #endif + #ifdef MA_HAS_VORBIS + if (result != MA_SUCCESS && ma_path_extension_equal_w(pFilePath, L"ogg")) { + result = ma_decoder_init_vorbis_from_file_w__internal(pFilePath, &config, pDecoder); + } + #endif + + /* + If we still haven't got a result just use trial and error. Custom decoders have already been attempted, so here we + need only iterate over our stock decoders. + */ + if (result != MA_SUCCESS) { + #ifdef MA_HAS_WAV if (result != MA_SUCCESS) { - ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); + result = ma_decoder_init_wav_from_file_w__internal(pFilePath, &config, pDecoder); + } + #endif + #ifdef MA_HAS_FLAC + if (result != MA_SUCCESS) { + result = ma_decoder_init_flac_from_file_w__internal(pFilePath, &config, pDecoder); + } + #endif + #ifdef MA_HAS_MP3 + if (result != MA_SUCCESS) { + result = ma_decoder_init_mp3_from_file_w__internal(pFilePath, &config, pDecoder); + } + #endif + #ifdef MA_HAS_VORBIS + if (result != MA_SUCCESS) { + result = ma_decoder_init_vorbis_from_file_w__internal(pFilePath, &config, pDecoder); } + #endif } - #endif } - /* If we still haven't got a result just use trial and error. Otherwise we can finish up. */ - if (result != MA_SUCCESS) { - result = ma_decoder_init__internal(ma_decoder__on_read_vfs, ma_decoder__on_seek_vfs, NULL, &config, pDecoder); - } else { + /* + If at this point we still haven't successfully initialized the decoder it most likely means + the backend doesn't have an implementation for loading from a file path. We'll try using + miniaudio's built-in file IO for loading file. + */ + if (result == MA_SUCCESS) { + /* Initialization was successful. Finish up. */ result = ma_decoder__postinit(&config, pDecoder); - } + if (result != MA_SUCCESS) { + /* + The backend was initialized successfully, but for some reason post-initialization failed. This is most likely + due to an out of memory error. We're going to abort with an error here and not try to recover. + */ + if (pDecoder->pBackendVTable != NULL && pDecoder->pBackendVTable->onUninit != NULL) { + pDecoder->pBackendVTable->onUninit(pDecoder->pBackendUserData, &pDecoder->pBackend, &pDecoder->allocationCallbacks); + } - if (result != MA_SUCCESS) { - ma_vfs_or_default_close(pVFS, pDecoder->data.vfs.file); - return result; + return result; + } + } else { + /* Probably no implementation for loading from a file path. Use miniaudio's file IO instead. */ + result = ma_decoder_init_vfs_w(NULL, pFilePath, pConfig, pDecoder); + if (result != MA_SUCCESS) { + return result; + } } return MA_SUCCESS; } -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_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_uninit(ma_decoder* pDecoder) { if (pDecoder == NULL) { @@ -65548,13 +66279,16 @@ MA_API ma_pulsewave_config ma_pulsewave_config_init(ma_format format, ma_uint32 MA_API ma_result ma_pulsewave_init(const ma_pulsewave_config* pConfig, ma_pulsewave* pWaveform) { + ma_result result; + ma_waveform_config config; + if (pWaveform == NULL) { return MA_INVALID_ARGS; } MA_ZERO_OBJECT(pWaveform); - ma_waveform_config config = ma_waveform_config_init( + config = ma_waveform_config_init( pConfig->format, pConfig->channels, pConfig->sampleRate, @@ -65563,7 +66297,10 @@ MA_API ma_result ma_pulsewave_init(const ma_pulsewave_config* pConfig, ma_pulsew pConfig->frequency ); - return ma_waveform_init(&config, &pWaveform->waveform); + result = ma_waveform_init(&config, &pWaveform->waveform); + ma_pulsewave_set_duty_cycle(pWaveform, pConfig->dutyCycle); + + return result; } MA_API void ma_pulsewave_uninit(ma_pulsewave* pWaveform) @@ -68064,7 +68801,7 @@ static ma_result ma_resource_manager_data_buffer_init_ex_internal(ma_resource_ma 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 + Fences need to be acquired before doing anything. These must be acquired 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. @@ -71336,7 +72073,7 @@ MA_API ma_result ma_node_init_preallocated(ma_node_graph* pNodeGraph, const ma_n } if (heapLayout.outputBusOffset != MA_SIZE_MAX) { - pNodeBase->pOutputBuses = (ma_node_output_bus*)ma_offset_ptr(pHeap, heapLayout.inputBusOffset); + pNodeBase->pOutputBuses = (ma_node_output_bus*)ma_offset_ptr(pHeap, heapLayout.outputBusOffset); } else { pNodeBase->pOutputBuses = pNodeBase->_outputBuses; } @@ -71827,11 +72564,11 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde /* 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 + our frame count and output pointer to accommodate 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 + therefore need to offset it by a number of frames to accommodate. The same thing applies for the stop time. */ timeOffsetBeg = (globalTimeBeg < startTime) ? (ma_uint32)(globalTimeEnd - startTime) : 0; @@ -73407,6 +74144,26 @@ static void ma_engine_node_process_pcm_frames__general(ma_engine_node* pEngineNo totalFramesProcessedIn = 0; totalFramesProcessedOut = 0; + /* Update the fader if applicable. */ + { + ma_uint64 fadeLengthInFrames = ma_atomic_uint64_get(&pEngineNode->fadeSettings.fadeLengthInFrames); + if (fadeLengthInFrames != ~(ma_uint64)0) { + float fadeVolumeBeg = ma_atomic_float_get(&pEngineNode->fadeSettings.volumeBeg); + float fadeVolumeEnd = ma_atomic_float_get(&pEngineNode->fadeSettings.volumeEnd); + ma_int64 fadeStartOffsetInFrames = (ma_int64)ma_atomic_uint64_get(&pEngineNode->fadeSettings.absoluteGlobalTimeInFrames); + if (fadeStartOffsetInFrames == (ma_int64)(~(ma_uint64)0)) { + fadeStartOffsetInFrames = 0; + } else { + fadeStartOffsetInFrames -= ma_engine_get_time_in_pcm_frames(pEngineNode->pEngine); + } + + ma_fader_set_fade_ex(&pEngineNode->fader, fadeVolumeBeg, fadeVolumeEnd, fadeLengthInFrames, fadeStartOffsetInFrames); + + /* Reset the fade length so we don't erroneously apply it again. */ + ma_atomic_uint64_set(&pEngineNode->fadeSettings.fadeLengthInFrames, ~(ma_uint64)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); @@ -73425,10 +74182,10 @@ static void ma_engine_node_process_pcm_frames__general(ma_engine_node* pEngineNo 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. + Note that we're always running the resampler if pitching is enabled, even when the pitch + is 1. 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 @@ -73926,6 +74683,10 @@ MA_API ma_result ma_engine_node_init_preallocated(const ma_engine_node_config* p pEngineNode->isPitchDisabled = pConfig->isPitchDisabled; pEngineNode->isSpatializationDisabled = pConfig->isSpatializationDisabled; pEngineNode->pinnedListenerIndex = pConfig->pinnedListenerIndex; + ma_atomic_float_set(&pEngineNode->fadeSettings.volumeBeg, 1); + ma_atomic_float_set(&pEngineNode->fadeSettings.volumeEnd, 1); + ma_atomic_uint64_set(&pEngineNode->fadeSettings.fadeLengthInFrames, (~(ma_uint64)0)); + ma_atomic_uint64_set(&pEngineNode->fadeSettings.absoluteGlobalTimeInFrames, (~(ma_uint64)0)); /* <-- Indicates that the fade should start immediately. */ channelsIn = (pConfig->channelsIn != 0) ? pConfig->channelsIn : ma_engine_get_channels(pConfig->pEngine); channelsOut = (pConfig->channelsOut != 0) ? pConfig->channelsOut : ma_engine_get_channels(pConfig->pEngine); @@ -74193,6 +74954,8 @@ MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEng pEngine->monoExpansionMode = engineConfig.monoExpansionMode; pEngine->defaultVolumeSmoothTimeInPCMFrames = engineConfig.defaultVolumeSmoothTimeInPCMFrames; + pEngine->onProcess = engineConfig.onProcess; + pEngine->pProcessUserData = engineConfig.pProcessUserData; ma_allocation_callbacks_init_copy(&pEngine->allocationCallbacks, &engineConfig.allocationCallbacks); #if !defined(MA_NO_RESOURCE_MANAGER) @@ -74219,7 +74982,7 @@ MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEng deviceConfig.playback.format = ma_format_f32; deviceConfig.playback.channels = engineConfig.channels; deviceConfig.sampleRate = engineConfig.sampleRate; - deviceConfig.dataCallback = ma_engine_data_callback_internal; + deviceConfig.dataCallback = (engineConfig.dataCallback != NULL) ? engineConfig.dataCallback : ma_engine_data_callback_internal; deviceConfig.pUserData = pEngine; deviceConfig.notificationCallback = engineConfig.notificationCallback; deviceConfig.periodSizeInFrames = engineConfig.periodSizeInFrames; @@ -74491,7 +75254,27 @@ 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) { - return ma_node_graph_read_pcm_frames(&pEngine->nodeGraph, pFramesOut, frameCount, pFramesRead); + ma_result result; + ma_uint64 framesRead = 0; + + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + result = ma_node_graph_read_pcm_frames(&pEngine->nodeGraph, pFramesOut, frameCount, &framesRead); + if (result != MA_SUCCESS) { + return result; + } + + if (pFramesRead != NULL) { + *pFramesRead = framesRead; + } + + if (pEngine->onProcess) { + pEngine->onProcess(pEngine->pProcessUserData, (float*)pFramesOut, framesRead); /* Safe cast to float* because the engine always works on floating point samples. */ + } + + return MA_SUCCESS; } MA_API ma_node_graph* ma_engine_get_node_graph(ma_engine* pEngine) @@ -74677,13 +75460,23 @@ MA_API ma_result ma_engine_set_volume(ma_engine* pEngine, float volume) 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) +MA_API float ma_engine_get_volume(ma_engine* pEngine) { if (pEngine == NULL) { - return MA_INVALID_ARGS; + return 0; } - return ma_node_set_output_bus_volume(ma_node_graph_get_endpoint(&pEngine->nodeGraph), 0, ma_volume_db_to_linear(gainDB)); + return ma_node_get_output_bus_volume(ma_node_graph_get_endpoint(&pEngine->nodeGraph), 0); +} + +MA_API ma_result ma_engine_set_gain_db(ma_engine* pEngine, float gainDB) +{ + return ma_engine_set_volume(pEngine, ma_volume_db_to_linear(gainDB)); +} + +MA_API float ma_engine_get_gain_db(ma_engine* pEngine) +{ + return ma_volume_linear_to_db(ma_engine_get_volume(pEngine)); } @@ -74798,6 +75591,10 @@ MA_API void ma_engine_listener_get_cone(const ma_engine* pEngine, ma_uint32 list *pOuterGain = 0; } + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return; + } + ma_spatializer_listener_get_cone(&pEngine->listeners[listenerIndex], pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain); } @@ -75360,6 +76157,31 @@ MA_API ma_result ma_sound_stop(ma_sound* pSound) return MA_SUCCESS; } +MA_API ma_result ma_sound_stop_with_fade_in_pcm_frames(ma_sound* pSound, ma_uint64 fadeLengthInFrames) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + /* Stopping with a fade out requires us to schedule the stop into the future by the fade length. */ + ma_sound_set_stop_time_with_fade_in_pcm_frames(pSound, ma_engine_get_time_in_pcm_frames(ma_sound_get_engine(pSound)) + fadeLengthInFrames, fadeLengthInFrames); + + return MA_SUCCESS; +} + +MA_API ma_result ma_sound_stop_with_fade_in_milliseconds(ma_sound* pSound, ma_uint64 fadeLengthInMilliseconds) +{ + ma_uint64 sampleRate; + + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + sampleRate = ma_engine_get_sample_rate(ma_sound_get_engine(pSound)); + + return ma_sound_stop_with_fade_in_pcm_frames(pSound, (fadeLengthInMilliseconds * sampleRate) / 1000); +} + MA_API void ma_sound_set_volume(ma_sound* pSound, float volume) { if (pSound == NULL) { @@ -75715,6 +76537,10 @@ MA_API void ma_sound_get_cone(const ma_sound* pSound, float* pInnerAngleInRadian *pOuterGain = 0; } + if (pSound == NULL) { + return; + } + ma_spatializer_get_cone(&pSound->engineNode.spatializer, pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain); } @@ -75761,7 +76587,7 @@ MA_API void ma_sound_set_fade_in_pcm_frames(ma_sound* pSound, float volumeBeg, f return; } - ma_fader_set_fade(&pSound->engineNode.fader, volumeBeg, volumeEnd, fadeLengthInFrames); + ma_sound_set_fade_start_in_pcm_frames(pSound, volumeBeg, volumeEnd, fadeLengthInFrames, (~(ma_uint64)0)); } MA_API void ma_sound_set_fade_in_milliseconds(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds) @@ -75773,6 +76599,36 @@ MA_API void ma_sound_set_fade_in_milliseconds(ma_sound* pSound, float volumeBeg, ma_sound_set_fade_in_pcm_frames(pSound, volumeBeg, volumeEnd, (fadeLengthInMilliseconds * pSound->engineNode.fader.config.sampleRate) / 1000); } +MA_API void ma_sound_set_fade_start_in_pcm_frames(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames, ma_uint64 absoluteGlobalTimeInFrames) +{ + if (pSound == NULL) { + return; + } + + /* + We don't want to update the fader at this point because we need to use the engine's current time + to derive the fader's start offset. The timer is being updated on the audio thread so in order to + do this as accurately as possible we'll need to defer this to the audio thread. + */ + ma_atomic_float_set(&pSound->engineNode.fadeSettings.volumeBeg, volumeBeg); + ma_atomic_float_set(&pSound->engineNode.fadeSettings.volumeEnd, volumeEnd); + ma_atomic_uint64_set(&pSound->engineNode.fadeSettings.fadeLengthInFrames, fadeLengthInFrames); + ma_atomic_uint64_set(&pSound->engineNode.fadeSettings.absoluteGlobalTimeInFrames, absoluteGlobalTimeInFrames); +} + +MA_API void ma_sound_set_fade_start_in_milliseconds(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds, ma_uint64 absoluteGlobalTimeInMilliseconds) +{ + ma_uint32 sampleRate; + + if (pSound == NULL) { + return; + } + + sampleRate = ma_engine_get_sample_rate(ma_sound_get_engine(pSound)); + + ma_sound_set_fade_start_in_pcm_frames(pSound, volumeBeg, volumeEnd, (fadeLengthInMilliseconds * sampleRate) / 1000, (absoluteGlobalTimeInMilliseconds * sampleRate) / 1000); +} + MA_API float ma_sound_get_current_fade_volume(const ma_sound* pSound) { if (pSound == NULL) { @@ -75806,7 +76662,7 @@ MA_API void ma_sound_set_stop_time_in_pcm_frames(ma_sound* pSound, ma_uint64 abs return; } - ma_node_set_state_time(pSound, ma_node_state_stopped, absoluteGlobalTimeInFrames); + ma_sound_set_stop_time_with_fade_in_pcm_frames(pSound, absoluteGlobalTimeInFrames, 0); } MA_API void ma_sound_set_stop_time_in_milliseconds(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInMilliseconds) @@ -75818,6 +76674,36 @@ MA_API void ma_sound_set_stop_time_in_milliseconds(ma_sound* pSound, ma_uint64 a ma_sound_set_stop_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_with_fade_in_pcm_frames(ma_sound* pSound, ma_uint64 stopAbsoluteGlobalTimeInFrames, ma_uint64 fadeLengthInFrames) +{ + if (pSound == NULL) { + return; + } + + if (fadeLengthInFrames > 0) { + if (fadeLengthInFrames > stopAbsoluteGlobalTimeInFrames) { + fadeLengthInFrames = stopAbsoluteGlobalTimeInFrames; + } + + ma_sound_set_fade_start_in_pcm_frames(pSound, -1, 0, fadeLengthInFrames, stopAbsoluteGlobalTimeInFrames - fadeLengthInFrames); + } + + ma_node_set_state_time(pSound, ma_node_state_stopped, stopAbsoluteGlobalTimeInFrames); +} + +MA_API void ma_sound_set_stop_time_with_fade_in_milliseconds(ma_sound* pSound, ma_uint64 stopAbsoluteGlobalTimeInMilliseconds, ma_uint64 fadeLengthInMilliseconds) +{ + ma_uint32 sampleRate; + + if (pSound == NULL) { + return; + } + + sampleRate = ma_engine_get_sample_rate(ma_sound_get_engine(pSound)); + + ma_sound_set_stop_time_with_fade_in_pcm_frames(pSound, (stopAbsoluteGlobalTimeInMilliseconds * sampleRate) / 1000, (fadeLengthInMilliseconds * sampleRate) / 1000); +} + MA_API ma_bool32 ma_sound_is_playing(const ma_sound* pSound) { if (pSound == NULL) { @@ -75836,6 +76722,11 @@ MA_API ma_uint64 ma_sound_get_time_in_pcm_frames(const ma_sound* pSound) return ma_node_get_time(pSound); } +MA_API ma_uint64 ma_sound_get_time_in_milliseconds(const ma_sound* pSound) +{ + return ma_sound_get_time_in_pcm_frames(pSound) * 1000 / ma_engine_get_sample_rate(ma_sound_get_engine(pSound)); +} + MA_API void ma_sound_set_looping(ma_sound* pSound, ma_bool32 isLooping) { if (pSound == NULL) { @@ -75931,6 +76822,8 @@ MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64* pCursor) { + ma_uint64 seekTarget; + if (pSound == NULL) { return MA_INVALID_ARGS; } @@ -75940,7 +76833,13 @@ MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64* return MA_INVALID_OPERATION; } - return ma_data_source_get_cursor_in_pcm_frames(pSound->pDataSource, pCursor); + seekTarget = ma_atomic_load_64(&pSound->seekTarget); + if (seekTarget != MA_SEEK_TARGET_NONE) { + *pCursor = seekTarget; + return MA_SUCCESS; + } else { + 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) @@ -75959,16 +76858,28 @@ MA_API ma_result ma_sound_get_length_in_pcm_frames(ma_sound* pSound, ma_uint64* MA_API ma_result ma_sound_get_cursor_in_seconds(ma_sound* pSound, float* pCursor) { - if (pSound == NULL) { - return MA_INVALID_ARGS; + ma_result result; + ma_uint64 cursorInPCMFrames; + ma_uint32 sampleRate; + + if (pCursor != NULL) { + *pCursor = 0; } - /* 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; + result = ma_sound_get_cursor_in_pcm_frames(pSound, &cursorInPCMFrames); + if (result != MA_SUCCESS) { + return result; } - return ma_data_source_get_cursor_in_seconds(pSound->pDataSource, pCursor); + result = ma_sound_get_data_format(pSound, NULL, NULL, &sampleRate, NULL, 0); + if (result != MA_SUCCESS) { + return result; + } + + /* VC6 does not support division of unsigned 64-bit integers with floating point numbers. Need to use a signed number. This shouldn't effect anything in practice. */ + *pCursor = (ma_int64)cursorInPCMFrames / (float)sampleRate; + + return MA_SUCCESS; } MA_API ma_result ma_sound_get_length_in_seconds(ma_sound* pSound, float* pLength) @@ -76367,8 +77278,8 @@ code below please report the bug to the respective repository for the relevant p #define ma_dr_wav_clamp(x, lo, hi) (ma_dr_wav_max((lo), ma_dr_wav_min((hi), (x)))) #define ma_dr_wav_offset_ptr(p, offset) (((ma_uint8*)(p)) + (offset)) #define MA_DR_WAV_MAX_SIMD_VECTOR_SIZE 32 -#define MA_DR_WAV_INT64_MIN ((ma_int64)0x80000000 << 32) -#define MA_DR_WAV_INT64_MAX ((((ma_int64)0x7FFFFFFF) << 32) | 0xFFFFFFFF) +#define MA_DR_WAV_INT64_MIN ((ma_int64) ((ma_uint64)0x80000000 << 32)) +#define MA_DR_WAV_INT64_MAX ((ma_int64)(((ma_uint64)0x7FFFFFFF << 32) | 0xFFFFFFFF)) #if defined(_MSC_VER) && _MSC_VER >= 1400 #define MA_DR_WAV_HAS_BYTESWAP16_INTRINSIC #define MA_DR_WAV_HAS_BYTESWAP32_INTRINSIC @@ -76767,11 +77678,7 @@ MA_PRIVATE ma_result ma_dr_wav__read_chunk_header(ma_dr_wav_read_proc onRead, vo return MA_INVALID_FILE; } pHeaderOut->sizeInBytes = ma_dr_wav_bytes_to_u32_ex(sizeInBytes, container); - if (container == ma_dr_wav_container_aiff) { - pHeaderOut->paddingSize = 0; - } else { - pHeaderOut->paddingSize = ma_dr_wav__chunk_padding_size_riff(pHeaderOut->sizeInBytes); - } + pHeaderOut->paddingSize = ma_dr_wav__chunk_padding_size_riff(pHeaderOut->sizeInBytes); *pRunningBytesReadOut += 8; } else if (container == ma_dr_wav_container_w64) { ma_uint8 sizeInBytes[8]; @@ -77610,6 +78517,7 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p ma_bool8 foundChunk_fmt = MA_FALSE; ma_bool8 foundChunk_data = MA_FALSE; ma_bool8 isAIFCFormType = MA_FALSE; + ma_uint64 aiffFrameCount = 0; cursor = 0; sequential = (flags & MA_DR_WAV_SEQUENTIAL) != 0; MA_DR_WAV_ZERO_OBJECT(&fmt); @@ -77870,6 +78778,7 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p ma_uint8 commData[24]; ma_uint32 commDataBytesToRead; ma_uint16 channels; + ma_uint32 frameCount; ma_uint16 sampleSizeInBits; ma_int64 sampleRate; ma_uint16 compressionFormat; @@ -77889,6 +78798,7 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p return MA_FALSE; } channels = ma_dr_wav_bytes_to_u16_ex (commData + 0, pWav->container); + frameCount = ma_dr_wav_bytes_to_u32_ex (commData + 2, pWav->container); sampleSizeInBits = ma_dr_wav_bytes_to_u16_ex (commData + 6, pWav->container); sampleRate = ma_dr_wav_aiff_extented_to_s64(commData + 8); if (sampleRate < 0 || sampleRate > 0xFFFFFFFF) { @@ -77898,14 +78808,19 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p const ma_uint8* type = commData + 18; if (ma_dr_wav_fourcc_equal(type, "NONE")) { compressionFormat = MA_DR_WAVE_FORMAT_PCM; + } else if (ma_dr_wav_fourcc_equal(type, "raw ")) { + compressionFormat = MA_DR_WAVE_FORMAT_PCM; + if (sampleSizeInBits == 8) { + pWav->aiff.isUnsigned = MA_TRUE; + } } else if (ma_dr_wav_fourcc_equal(type, "sowt")) { compressionFormat = MA_DR_WAVE_FORMAT_PCM; pWav->aiff.isLE = MA_TRUE; } else if (ma_dr_wav_fourcc_equal(type, "fl32") || ma_dr_wav_fourcc_equal(type, "fl64") || ma_dr_wav_fourcc_equal(type, "FL32") || ma_dr_wav_fourcc_equal(type, "FL64")) { compressionFormat = MA_DR_WAVE_FORMAT_IEEE_FLOAT; - } else if (ma_dr_wav_fourcc_equal(type, "alaw")) { + } else if (ma_dr_wav_fourcc_equal(type, "alaw") || ma_dr_wav_fourcc_equal(type, "ALAW")) { compressionFormat = MA_DR_WAVE_FORMAT_ALAW; - } else if (ma_dr_wav_fourcc_equal(type, "ulaw")) { + } else if (ma_dr_wav_fourcc_equal(type, "ulaw") || ma_dr_wav_fourcc_equal(type, "ULAW")) { compressionFormat = MA_DR_WAVE_FORMAT_MULAW; } else if (ma_dr_wav_fourcc_equal(type, "ima4")) { compressionFormat = MA_DR_WAVE_FORMAT_DVI_ADPCM; @@ -77917,6 +78832,7 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p } else { compressionFormat = MA_DR_WAVE_FORMAT_PCM; } + aiffFrameCount = frameCount; fmt.formatTag = compressionFormat; fmt.channels = channels; fmt.sampleRate = (ma_uint32)sampleRate; @@ -77926,6 +78842,13 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p if (fmt.blockAlign == 0 && compressionFormat == MA_DR_WAVE_FORMAT_DVI_ADPCM) { fmt.blockAlign = 34 * fmt.channels; } + if (compressionFormat == MA_DR_WAVE_FORMAT_ALAW || compressionFormat == MA_DR_WAVE_FORMAT_MULAW) { + if (fmt.bitsPerSample > 8) { + fmt.bitsPerSample = 8; + fmt.blockAlign = fmt.channels; + } + } + fmt.bitsPerSample += (fmt.bitsPerSample & 7); if (isAIFCFormType) { if (ma_dr_wav__seek_forward(pWav->onSeek, (chunkSize - commDataBytesToRead), pWav->pUserData) == MA_FALSE) { return MA_FALSE; @@ -78026,10 +78949,10 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p break; } } - if (ma_dr_wav__seek_from_start(pWav->onSeek, pWav->dataChunkDataPos, pWav->pUserData) == MA_FALSE) { - ma_dr_wav_free(pWav->pMetadata, &pWav->allocationCallbacks); - return MA_FALSE; - } + } + if (ma_dr_wav__seek_from_start(pWav->onSeek, pWav->dataChunkDataPos, pWav->pUserData) == MA_FALSE) { + ma_dr_wav_free(pWav->pMetadata, &pWav->allocationCallbacks); + return MA_FALSE; } pWav->fmt = fmt; pWav->sampleRate = fmt.sampleRate; @@ -78040,6 +78963,8 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p pWav->dataChunkDataSize = dataChunkSize; if (sampleCountFromFactChunk != 0) { pWav->totalPCMFrameCount = sampleCountFromFactChunk; + } else if (aiffFrameCount != 0) { + pWav->totalPCMFrameCount = aiffFrameCount; } else { ma_uint32 bytesPerFrame = ma_dr_wav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { @@ -79128,12 +80053,17 @@ MA_API ma_uint64 ma_dr_wav_read_pcm_frames_le(ma_dr_wav* pWav, ma_uint64 framesT { ma_uint32 bytesPerFrame; ma_uint64 bytesToRead; + ma_uint64 framesRemainingInFile; if (pWav == NULL || framesToRead == 0) { return 0; } if (ma_dr_wav__is_compressed_format_tag(pWav->translatedFormatTag)) { return 0; } + framesRemainingInFile = pWav->totalPCMFrameCount - pWav->readCursorInPCMFrames; + if (framesToRead > framesRemainingInFile) { + framesToRead = framesRemainingInFile; + } bytesPerFrame = ma_dr_wav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; @@ -79161,20 +80091,34 @@ MA_API ma_uint64 ma_dr_wav_read_pcm_frames_be(ma_dr_wav* pWav, ma_uint64 framesT } MA_API ma_uint64 ma_dr_wav_read_pcm_frames(ma_dr_wav* pWav, ma_uint64 framesToRead, void* pBufferOut) { + ma_uint64 framesRead = 0; if (ma_dr_wav_is_container_be(pWav->container)) { if (pWav->container != ma_dr_wav_container_aiff || pWav->aiff.isLE == MA_FALSE) { if (ma_dr_wav__is_little_endian()) { - return ma_dr_wav_read_pcm_frames_be(pWav, framesToRead, pBufferOut); + framesRead = ma_dr_wav_read_pcm_frames_be(pWav, framesToRead, pBufferOut); } else { - return ma_dr_wav_read_pcm_frames_le(pWav, framesToRead, pBufferOut); + framesRead = ma_dr_wav_read_pcm_frames_le(pWav, framesToRead, pBufferOut); } + goto post_process; } } if (ma_dr_wav__is_little_endian()) { - return ma_dr_wav_read_pcm_frames_le(pWav, framesToRead, pBufferOut); + framesRead = ma_dr_wav_read_pcm_frames_le(pWav, framesToRead, pBufferOut); } else { - return ma_dr_wav_read_pcm_frames_be(pWav, framesToRead, pBufferOut); + framesRead = ma_dr_wav_read_pcm_frames_be(pWav, framesToRead, pBufferOut); } + post_process: + { + if (pWav->container == ma_dr_wav_container_aiff && pWav->bitsPerSample == 8 && pWav->aiff.isUnsigned == MA_FALSE) { + if (pBufferOut != NULL) { + ma_uint64 iSample; + for (iSample = 0; iSample < framesRead * pWav->channels; iSample += 1) { + ((ma_uint8*)pBufferOut)[iSample] += 128; + } + } + } + } + return framesRead; } MA_PRIVATE ma_bool32 ma_dr_wav_seek_to_first_pcm_frame(ma_dr_wav* pWav) { @@ -79250,7 +80194,6 @@ MA_API ma_bool32 ma_dr_wav_seek_to_pcm_frame(ma_dr_wav* pWav, ma_uint64 targetFr return MA_FALSE; } totalSizeInBytes = pWav->totalPCMFrameCount * bytesPerFrame; - MA_DR_WAV_ASSERT(totalSizeInBytes >= pWav->bytesRemaining); currentBytePos = totalSizeInBytes - pWav->bytesRemaining; targetBytePos = targetFrameIndex * bytesPerFrame; if (currentBytePos < targetBytePos) { @@ -79841,6 +80784,16 @@ MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s16__alaw(ma_dr_wav* pWav, ma_uin break; } ma_dr_wav_alaw_to_s16(pBufferOut, sampleData, (size_t)samplesRead); + #ifdef MA_DR_WAV_LIBSNDFILE_COMPAT + { + if (pWav->container == ma_dr_wav_container_aiff) { + ma_uint64 iSample; + for (iSample = 0; iSample < samplesRead; iSample += 1) { + pBufferOut[iSample] = -pBufferOut[iSample]; + } + } + } + #endif pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; @@ -79879,6 +80832,16 @@ MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s16__mulaw(ma_dr_wav* pWav, ma_ui break; } ma_dr_wav_mulaw_to_s16(pBufferOut, sampleData, (size_t)samplesRead); + #ifdef MA_DR_WAV_LIBSNDFILE_COMPAT + { + if (pWav->container == ma_dr_wav_container_aiff) { + ma_uint64 iSample; + for (iSample = 0; iSample < samplesRead; iSample += 1) { + pBufferOut[iSample] = -pBufferOut[iSample]; + } + } + } + #endif pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; @@ -80178,6 +81141,16 @@ MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_f32__alaw(ma_dr_wav* pWav, ma_uin break; } ma_dr_wav_alaw_to_f32(pBufferOut, sampleData, (size_t)samplesRead); + #ifdef MA_DR_WAV_LIBSNDFILE_COMPAT + { + if (pWav->container == ma_dr_wav_container_aiff) { + ma_uint64 iSample; + for (iSample = 0; iSample < samplesRead; iSample += 1) { + pBufferOut[iSample] = -pBufferOut[iSample]; + } + } + } + #endif pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; @@ -80213,6 +81186,16 @@ MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_f32__mulaw(ma_dr_wav* pWav, ma_ui break; } ma_dr_wav_mulaw_to_f32(pBufferOut, sampleData, (size_t)samplesRead); + #ifdef MA_DR_WAV_LIBSNDFILE_COMPAT + { + if (pWav->container == ma_dr_wav_container_aiff) { + ma_uint64 iSample; + for (iSample = 0; iSample < samplesRead; iSample += 1) { + pBufferOut[iSample] = -pBufferOut[iSample]; + } + } + } + #endif pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; @@ -80518,6 +81501,16 @@ MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s32__alaw(ma_dr_wav* pWav, ma_uin break; } ma_dr_wav_alaw_to_s32(pBufferOut, sampleData, (size_t)samplesRead); + #ifdef MA_DR_WAV_LIBSNDFILE_COMPAT + { + if (pWav->container == ma_dr_wav_container_aiff) { + ma_uint64 iSample; + for (iSample = 0; iSample < samplesRead; iSample += 1) { + pBufferOut[iSample] = -pBufferOut[iSample]; + } + } + } + #endif pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; @@ -80553,6 +81546,16 @@ MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s32__mulaw(ma_dr_wav* pWav, ma_ui break; } ma_dr_wav_mulaw_to_s32(pBufferOut, sampleData, (size_t)samplesRead); + #ifdef MA_DR_WAV_LIBSNDFILE_COMPAT + { + if (pWav->container == ma_dr_wav_container_aiff) { + ma_uint64 iSample; + for (iSample = 0; iSample < samplesRead; iSample += 1) { + pBufferOut[iSample] = -pBufferOut[iSample]; + } + } + } + #endif pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; @@ -81404,7 +82407,7 @@ static MA_INLINE ma_uint32 ma_dr_flac__swap_endian_uint32(ma_uint32 n) #if defined(_MSC_VER) && !defined(__clang__) return _byteswap_ulong(n); #elif defined(__GNUC__) || defined(__clang__) - #if defined(MA_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 6) && !defined(MA_64BIT) + #if defined(MA_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 6) && !defined(__ARM_ARCH_6M__) && !defined(MA_64BIT) ma_uint32 r; __asm__ __volatile__ ( #if defined(MA_64BIT) @@ -82145,7 +83148,7 @@ static MA_INLINE ma_uint32 ma_dr_flac__clz_lzcnt(ma_dr_flac_cache_t x) ); return r; } - #elif defined(MA_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5) && !defined(MA_64BIT) + #elif defined(MA_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5) && !defined(__ARM_ARCH_6M__) && !defined(MA_64BIT) { unsigned int r; __asm__ __volatile__ ( @@ -84886,7 +85889,7 @@ static ma_bool32 ma_dr_flac__read_and_decode_metadata(ma_dr_flac_read_proc onRea for (;;) { ma_dr_flac_metadata metadata; ma_uint8 isLastBlock = 0; - ma_uint8 blockType; + ma_uint8 blockType = 0; ma_uint32 blockSize; if (ma_dr_flac__read_and_decode_block_header(onRead, pUserData, &isLastBlock, &blockType, &blockSize) == MA_FALSE) { return MA_FALSE; @@ -88966,7 +89969,7 @@ static int ma_dr_mp3_have_simd(void) #else #define MA_DR_MP3_HAVE_SIMD 0 #endif -#if defined(__ARM_ARCH) && (__ARM_ARCH >= 6) && !defined(__aarch64__) && !defined(_M_ARM64) +#if defined(__ARM_ARCH) && (__ARM_ARCH >= 6) && !defined(__aarch64__) && !defined(_M_ARM64) && !defined(__ARM_ARCH_6M__) #define MA_DR_MP3_HAVE_ARMV6 1 static __inline__ __attribute__((always_inline)) ma_int32 ma_dr_mp3_clip_int16_arm(ma_int32 a) { @@ -90724,6 +91727,9 @@ static ma_uint32 ma_dr_mp3_decode_next_frame_ex__callbacks(ma_dr_mp3* pMP3, ma_d } MA_DR_MP3_ASSERT(pMP3->pData != NULL); MA_DR_MP3_ASSERT(pMP3->dataCapacity > 0); + if (pMP3->pData == NULL) { + return 0; + } pcmFramesRead = ma_dr_mp3dec_decode_frame(&pMP3->decoder, pMP3->pData + pMP3->dataConsumed, (int)pMP3->dataSize, pPCMFrames, &info); if (info.frame_bytes > 0) { pMP3->dataConsumed += (size_t)info.frame_bytes; diff --git a/src/file.c b/src/file.c index 26d3087..079ded9 100644 --- a/src/file.c +++ b/src/file.c @@ -343,7 +343,7 @@ int removeDirectory(const char *path) int deleteFile(const char *filePath) { - if (unlink(filePath) == 0) + if (remove(filePath) == 0) { return 0; } diff --git a/src/kew.c b/src/kew.c index 6bb3701..0865c9d 100644 --- a/src/kew.c +++ b/src/kew.c @@ -62,6 +62,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "volume.h" #include "playerops.h" #include "mpris.h" +#include "soundcommon.h" // #define DEBUG 1 #define MAX_SEQ_LEN 1024 // Maximum length of sequence buffer @@ -299,7 +300,7 @@ void prepareNextSong() if (loadingFailed) return; - if (!skipPrev && !gotoSong && !repeatEnabled) + if (!skipPrev && !gotoSong && !isRepeatEnabled()) { if (nextSong != NULL) currentSong = nextSong; @@ -333,7 +334,7 @@ void prepareNextSong() refresh = true; - if (!repeatEnabled) + if (!isRepeatEnabled()) { pthread_mutex_lock(&(loadingdata.mutex)); if (usingSongDataA && @@ -616,12 +617,14 @@ gboolean mainloop_callback(gpointer data) loadAudioData(); if (songHasErrors) - tryLoadNext(); + tryLoadNext(); if (isPlaybackDone()) - { + { updateLastSongSwitchTime(); - prepareNextSong(); + prepareNextSong(); + if (!doQuit) + switchAudioImplementation(); } if (doQuit || loadingFailed) @@ -673,6 +676,7 @@ void play(Node *song) void cleanupOnExit() { cleanupPlaybackDevice(); + cleanupAudioContext(); emitPlaybackStoppedMpris(); unloadSongData(&loadingdata.songdataA); unloadSongData(&loadingdata.songdataB); @@ -692,7 +696,7 @@ void cleanupOnExit() showCursor(); #ifdef DEBUG - fclose(logFile); + fclose(logFile); #endif freopen("/dev/stderr", "w", stderr); } diff --git a/src/mpris.c b/src/mpris.c index f481226..fa32395 100644 --- a/src/mpris.c +++ b/src/mpris.c @@ -368,7 +368,7 @@ static gboolean get_shuffle(GDBusConnection *connection, const gchar *sender, const gchar *property_name, GVariant **value, GError **error, gpointer user_data) { - *value = g_variant_new_boolean(shuffleEnabled ? TRUE : FALSE); + *value = g_variant_new_boolean(isShuffleEnabled() ? TRUE : FALSE); return TRUE; } diff --git a/src/player.c b/src/player.c index 5ccec82..af5017a 100644 --- a/src/player.c +++ b/src/player.c @@ -24,7 +24,7 @@ typedef struct } PixelData; #endif -const char VERSION[] = "1.5.2"; +const char VERSION[] = "1.6.0"; const int LOGO_COLOR = 3; const int VERSION_COLOR = 6; const int ABSOLUTE_MIN_WIDTH = 38; @@ -299,10 +299,6 @@ void printBasicMetadata(TagSettings const *metadata) printf("\e[1m\e[39m"); printWithDelay(metadata->title, 9, maxWidth - 2); - - // Alternative (no delay): - // printf("\033[1K\r %.*s", maxWidth, metadata->title); - // printf("\n"); } cursorJumpDown(rows - 1); } @@ -433,13 +429,13 @@ void printLastRow() char text[100] = " [F2 Playlist] [F3 Keys] [Q Quit]"; - if (repeatEnabled) + if (isRepeatEnabled()) { char repeatText[] = " R"; strcat(text, repeatText); } - if (shuffleEnabled) + if (isShuffleEnabled()) { char shuffleText[] = " S"; strcat(text, shuffleText); diff --git a/src/playerops.c b/src/playerops.c index 4b84dcb..bd67639 100644 --- a/src/playerops.c +++ b/src/playerops.c @@ -55,7 +55,6 @@ void updateLastInputTime() void emitStringPropertyChanged(const gchar *propertyName, const gchar *newValue) { - GVariantBuilder changed_properties_builder; g_variant_builder_init(&changed_properties_builder, G_VARIANT_TYPE("a{sv}")); g_variant_builder_add(&changed_properties_builder, "{sv}", propertyName, g_variant_new_string(newValue)); @@ -154,7 +153,10 @@ void togglePause(double *totalPauseSeconds, double *pauseSeconds, struct timespe void toggleRepeat() { - repeatEnabled = !repeatEnabled; + + bool repeatEnabled = !isRepeatEnabled(); + setRepeatEnabled(repeatEnabled); + if (repeatEnabled) { emitStringPropertyChanged("LoopStatus", "Track"); @@ -180,7 +182,8 @@ void addToPlaylist() void toggleShuffle() { - shuffleEnabled = !shuffleEnabled; + bool shuffleEnabled = !isShuffleEnabled(); + setShuffleEnabled(shuffleEnabled); if (shuffleEnabled) { @@ -254,7 +257,7 @@ void calcElapsedTime() { elapsedSeconds = (double)(current_time.tv_sec - start_time.tv_sec) + (double)(current_time.tv_nsec - start_time.tv_nsec) / 1e9; - elapsedSeconds += seekElapsed + seekAccumulatedSeconds; + elapsedSeconds += getSeekElapsed() + seekAccumulatedSeconds; elapsedSeconds -= totalPauseSeconds; if (elapsedSeconds > duration) elapsedSeconds = duration; @@ -272,17 +275,17 @@ void flushSeek() { if (seekAccumulatedSeconds != 0.0) { - seekElapsed += seekAccumulatedSeconds; + setSeekElapsed(getSeekElapsed() + seekAccumulatedSeconds); seekAccumulatedSeconds = 0.0; calcElapsedTime(); - float percentage = elapsedSeconds / (float)duration * 100.0; + float percentage = elapsedSeconds / (float)duration * 100.0; if (percentage < 0.0) { - seekElapsed = 0.0; + setSeekElapsed(0.0); percentage = 0.0; } - + seekPercentage(percentage); } } @@ -333,6 +336,8 @@ void assignLoadedData() { userData.filenameB = loadingdata.songdataB->pcmFilePath; userData.songdataB = loadingdata.songdataB; + if (hasBuiltinDecoder(loadingdata.songdataB->filePath)) + prepareNextDecoder(loadingdata.songdataB->filePath); } else userData.filenameB = NULL; @@ -343,6 +348,8 @@ void assignLoadedData() { userData.filenameA = loadingdata.songdataA->pcmFilePath; userData.songdataA = loadingdata.songdataA; + if (hasBuiltinDecoder(loadingdata.songdataA->filePath)) + prepareNextDecoder(loadingdata.songdataA->filePath); } else userData.filenameA = NULL; @@ -516,7 +523,6 @@ void skipToNumberedSong(int songNumber) forceSkip = true; if (songNumber < playlist.count) skipToNumberedSong(songNumber + 1); - } updateLastSongSwitchTime(); diff --git a/src/settings.c b/src/settings.c index 747207e..fca6905 100644 --- a/src/settings.c +++ b/src/settings.c @@ -29,7 +29,7 @@ AppSettings constructAppSettings(KeyValuePair *pairs, int count) strncpy(settings.coverEnabled, "1", sizeof(settings.coverEnabled)); strncpy(settings.coverAnsi, "0", sizeof(settings.coverAnsi)); strncpy(settings.visualizerEnabled, "1", sizeof(settings.visualizerEnabled)); - strncpy(settings.useProfileColors, "0", sizeof(settings.useProfileColors)); + strncpy(settings.useProfileColors, "1", sizeof(settings.useProfileColors)); strncpy(settings.volumeUp, "+", sizeof(settings.volumeUp)); strncpy(settings.volumeDown, "-", sizeof(settings.volumeDown)); diff --git a/src/songloader.c b/src/songloader.c index 5d56e1f..8cced0a 100644 --- a/src/songloader.c +++ b/src/songloader.c @@ -43,7 +43,7 @@ void *child_cleanup(void *arg) int status; waitpid(data->pid, &status, 0); ffmpegPids[data->index] = -1; - numRunningProcesses--; + numRunningProcesses--; free(arg); return NULL; } @@ -72,7 +72,7 @@ void stopFFmpeg() } } -int convertToPcmFile(const char *filePath, const char *outputFilePath) +int convertToPcmFile(SongData* songData, const char *filePath, const char *outputFilePath) { char command[COMMAND_SIZE]; @@ -253,7 +253,7 @@ int loadPcmAudio(SongData *songdata) return -1; generateTempFilePath(songdata->filePath, songdata->pcmFilePath, "temp", ".pcm"); - convertToPcmFile(songdata->filePath, songdata->pcmFilePath); + convertToPcmFile(songdata, songdata->filePath, songdata->pcmFilePath); int count = 0; int result = -1; while (result < 1 && count < maxSleepTimes) @@ -306,7 +306,8 @@ SongData *loadSongData(char *filePath) c_sleep(10); loadDuration(songdata); c_sleep(10); - loadPcmAudio(songdata); + if (!hasBuiltinDecoder(songdata->filePath)) + loadPcmAudio(songdata); songdata->deleted = false; return songdata; } @@ -341,8 +342,11 @@ void unloadSongData(SongData **songdata) data->trackId = NULL; if (existsFile(data->pcmFilePath) > -1) + { + while (numRunningProcesses > 0) + c_sleep(100); deleteFile(data->pcmFilePath); - + } if (data->pcmFile != NULL) free(data->pcmFile); diff --git a/src/songloader.h b/src/songloader.h index 3dbd7e2..33741ae 100644 --- a/src/songloader.h +++ b/src/songloader.h @@ -10,6 +10,7 @@ #include "chafafunc.h" #include "albumart.h" #include "soundgapless.h" +#include "soundcommon.h" #ifndef KEYVALUEPAIR_STRUCT #define KEYVALUEPAIR_STRUCT diff --git a/src/soundbuiltin.c b/src/soundbuiltin.c new file mode 100644 index 0000000..3cc39f0 --- /dev/null +++ b/src/soundbuiltin.c @@ -0,0 +1,192 @@ +#include "soundbuiltin.h" +/* + +soundbuiltin.c + + Functions related to miniaudio implementation for miniaudio builtin decoders (flac, wav and mp3) + +*/ + +static ma_result builtin_file_data_source_read(ma_data_source *pDataSource, void *pFramesOut, ma_uint64 frameCount, ma_uint64 *pFramesRead) +{ + // Dummy implementation + (void)pDataSource; + (void)pFramesOut; + (void)frameCount; + (void)pFramesRead; + return MA_SUCCESS; +} + +static ma_result builtin_file_data_source_seek(ma_data_source *pDataSource, ma_uint64 frameIndex) +{ + PCMFileDataSource* pPCMDataSource = (PCMFileDataSource*)pDataSource; + + if (getCurrentDecoder() == NULL) + { + return MA_INVALID_ARGS; + } + + ma_result result = ma_decoder_seek_to_pcm_frame(getCurrentDecoder(), frameIndex); + + if (result == MA_SUCCESS) + { + pPCMDataSource->currentPCMFrame = (ma_uint32)frameIndex; + return MA_SUCCESS; + } + else + { + return result; + } +} + +static ma_result builtin_file_data_source_get_data_format(ma_data_source *pDataSource, ma_format *pFormat, ma_uint32 *pChannels, ma_uint32 *pSampleRate, ma_channel *pChannelMap, size_t channelMapCap) +{ + (void)pChannelMap; + (void)channelMapCap; + PCMFileDataSource *pPCMDataSource = (PCMFileDataSource *)pDataSource; + *pFormat = pPCMDataSource->format; + *pChannels = pPCMDataSource->channels; + *pSampleRate = pPCMDataSource->sampleRate; + return MA_SUCCESS; +} + +static ma_result builtin_file_data_source_get_cursor(ma_data_source *pDataSource, ma_uint64 *pCursor) +{ + PCMFileDataSource *pPCMDataSource = (PCMFileDataSource *)pDataSource; + *pCursor = pPCMDataSource->currentPCMFrame; + + return MA_SUCCESS; +} + +static ma_result builtin_file_data_source_get_length(ma_data_source *pDataSource, ma_uint64 *pLength) +{ + PCMFileDataSource *pPCMDataSource = (PCMFileDataSource *)pDataSource; + + ma_uint64 totalFrames = 0; + + if (getCurrentDecoder() == NULL) + { + return MA_INVALID_ARGS; + } + + ma_result result = ma_decoder_get_length_in_pcm_frames(getCurrentDecoder(), &totalFrames); + + if (result != MA_SUCCESS) + { + return result; + } + + *pLength = totalFrames; + + return MA_SUCCESS; +} + +static ma_result builtin_file_data_source_set_looping(ma_data_source *pDataSource, ma_bool32 isLooping) +{ + // Dummy implementation + (void)pDataSource; + (void)isLooping; + + return MA_SUCCESS; +} + +ma_data_source_vtable builtin_file_data_source_vtable = { + builtin_file_data_source_read, + builtin_file_data_source_seek, + builtin_file_data_source_get_data_format, + builtin_file_data_source_get_cursor, + builtin_file_data_source_get_length, + builtin_file_data_source_set_looping, + 0 // flags +}; + +void builtin_read_pcm_frames(ma_data_source *pDataSource, void *pFramesOut, ma_uint64 frameCount, ma_uint64 *pFramesRead) +{ + PCMFileDataSource *pPCMDataSource = (PCMFileDataSource *)pDataSource; + ma_uint64 framesRead = 0; + + while (framesRead < frameCount) + { + ma_uint64 remainingFrames = frameCount - framesRead; + + if (pPCMDataSource == NULL) + return; + + if (pPCMDataSource->switchFiles) + { + executeSwitch(pPCMDataSource); + break; + } + + if (getCurrentImplementationType() != BUILTIN && !isSkipToNext()) + return; + + ma_decoder *decoder = getCurrentDecoder(); + + if (decoder == NULL) + return; + + if (isSeekRequested()) + { + ma_uint64 totalFrames = 0; + ma_decoder_get_length_in_pcm_frames(decoder, &totalFrames); + ma_uint32 targetFrame = (totalFrames * getSeekPercentage()) / 100; + ma_result seekResult = ma_decoder_seek_to_pcm_frame(decoder, targetFrame); + + if (seekResult != MA_SUCCESS) + { + break; + } + + setSeekRequested(false); + } + + ma_uint64 framesToRead = 0; + ma_decoder *firstDecoder = getFirstDecoder(); + + if (decoder == NULL || firstDecoder == NULL) + return; + + ma_result result = ma_data_source_read_pcm_frames(firstDecoder, (ma_int32 *)pFramesOut + framesRead * decoder->outputChannels, remainingFrames, &framesToRead); + + ma_uint64 length; + ma_uint64 cursor; + + result = ma_data_source_get_cursor_in_pcm_frames(decoder, &cursor); + + if (((cursor != 0 && cursor >= pPCMDataSource->totalFrames) || framesToRead == 0 || isSkipToNext() || result != MA_SUCCESS) && !isEOFReached()) + { + activateSwitch(pPCMDataSource); + continue; + } + + framesRead += framesToRead; + setBufferSize(framesToRead); + } + + ma_int32 *audioBuffer = getAudioBuffer(); + if (audioBuffer == NULL) + { + audioBuffer = malloc(sizeof(ma_int32) * MAX_BUFFER_SIZE); + if (audioBuffer == NULL) + { + return; + } + } + + memcpy(audioBuffer, pFramesOut, sizeof(ma_int32) * framesRead); + setAudioBuffer(audioBuffer); + + if (pFramesRead != NULL) + { + *pFramesRead = framesRead; + } +} + +void builtin_on_audio_frames(ma_device *pDevice, void *pFramesOut, const void *pFramesIn, ma_uint32 frameCount) +{ + PCMFileDataSource *pDataSource = (PCMFileDataSource *)pDevice->pUserData; + ma_uint64 framesRead = 0; + builtin_read_pcm_frames(&pDataSource->base, pFramesOut, frameCount, &framesRead); + (void)pFramesIn; +} \ No newline at end of file diff --git a/src/soundbuiltin.h b/src/soundbuiltin.h new file mode 100644 index 0000000..ef626fc --- /dev/null +++ b/src/soundbuiltin.h @@ -0,0 +1,13 @@ +#ifndef SOUNDBUILTIN_H +#define SOUNDBUILTIN_H +#include +#include +#include +#include "songloader.h" +#include "soundcommon.h" + +extern ma_data_source_vtable builtin_file_data_source_vtable; + +void builtin_on_audio_frames(ma_device *pDevice, void *pFramesOut, const void *pFramesIn, ma_uint32 frameCount); + +#endif diff --git a/src/soundcommon.c b/src/soundcommon.c new file mode 100644 index 0000000..e4e4422 --- /dev/null +++ b/src/soundcommon.c @@ -0,0 +1,409 @@ +#include "soundcommon.h" + +#define MAX_DECODERS 2 + +const char BUILTIN_EXTENSIONS[] = "\\.(mp3|flac|wav)$"; + +bool repeatEnabled = false; +bool shuffleEnabled = false; +bool skipToNext = false; +bool seekRequested = false; +bool paused = false; +float seekPercent = 0.0; +double seekElapsed; +_Atomic bool EOFReached = false; +ma_device device = {0}; +ma_int32 *audioBuffer = NULL; +ma_decoder *firstDecoder; +ma_decoder *currentDecoder; +int bufSize; +ma_event switchAudioImpl; +enum AudioImplementation currentImplementation = NONE; +ma_decoder *decoders[MAX_DECODERS]; + +int decoderIndex = -1; + +enum AudioImplementation getCurrentImplementationType() +{ + return currentImplementation; +} + +void setCurrentImplementationType(enum AudioImplementation value) +{ + currentImplementation = value; +} + +ma_decoder *getFirstDecoder() +{ + return firstDecoder; +} + +ma_decoder *getCurrentDecoder() +{ + if (decoderIndex == -1) + return getFirstDecoder(); + else + return decoders[decoderIndex]; +} + +void switchDecoder() +{ + if (decoderIndex == -1) + decoderIndex = 0; + else + decoderIndex = 1 - decoderIndex; +} + +void setNextDecoder(ma_decoder *decoder) +{ + if (decoderIndex == -1 && firstDecoder == NULL) + { + firstDecoder = decoder; + } + else if (decoderIndex == -1) + { + decoders[0] = decoder; + } + else + { + int nextIndex = 1 - decoderIndex; + decoders[nextIndex] = decoder; + } +} + +void resetDecoders() +{ + decoderIndex = -1; + + if (firstDecoder != NULL) + { + ma_decoder_uninit(firstDecoder); + free(firstDecoder); + firstDecoder = NULL; + } + if (decoders[0] != NULL) + { + ma_decoder_uninit(decoders[0]); + free(decoders[0]); + decoders[0] = NULL; + } + if (decoders[1] != NULL) + { + ma_decoder_uninit(decoders[1]); + free(decoders[1]); + decoders[1] = NULL; + } +} + +void uninitPreviousDecoder() +{ + if (decoderIndex == -1) + { + return; + } + ma_decoder *toUninit = decoders[1 - decoderIndex]; + + if (toUninit != NULL) + { + ma_decoder_uninit(toUninit); + free(toUninit); + decoders[1 - decoderIndex] = NULL; + } +} + +void prepareNextDecoder(char *filepath) +{ + ma_decoder *currentDecoder; + + if (decoderIndex == -1) + { + currentDecoder = getFirstDecoder(); + } + else + { + currentDecoder = decoders[decoderIndex]; + } + + uninitPreviousDecoder(); + + ma_decoder_config config = ma_decoder_config_init(ma_format_s24, 2, 192000); + ma_decoder *decoder = (ma_decoder *)malloc(sizeof(ma_decoder)); + ma_decoder_init_file(filepath, NULL, decoder); + setNextDecoder(decoder); + if (currentDecoder != NULL) + ma_data_source_set_next(currentDecoder, decoder); +} + +void getFileInfo(const char *filename, ma_uint32 *sampleRate, ma_uint32 *channels, ma_format *format) +{ + ma_decoder decoder; + if (ma_decoder_init_file(filename, NULL, &decoder) == MA_SUCCESS) + { + *sampleRate = decoder.outputSampleRate; + *channels = decoder.outputChannels; + *format = decoder.outputFormat; + ma_decoder_uninit(&decoder); + } + else + { + // Handle file open error. + } +} + +int getBufferSize() +{ + return bufSize; +} + +void setBufferSize(int value) +{ + bufSize = value; +} + +ma_int32 *getAudioBuffer() +{ + return audioBuffer; +} + +void setAudioBuffer(ma_int32 *buf) +{ + audioBuffer = buf; +} + +void freeAudioBuffer() +{ + if (audioBuffer != NULL) + { + free(audioBuffer); + audioBuffer = NULL; + } +} + +bool isRepeatEnabled() +{ + return repeatEnabled; +} + +void setRepeatEnabled(bool value) +{ + repeatEnabled = value; +} + +bool isShuffleEnabled() +{ + return shuffleEnabled; +} + +void setShuffleEnabled(bool value) +{ + shuffleEnabled = value; +} + +bool isSkipToNext() +{ + return skipToNext; +} + +void setSkipToNext(bool value) +{ + skipToNext = value; +} + +double getSeekElapsed() +{ + return seekElapsed; +} + +void setSeekElapsed(double value) +{ + seekElapsed = value; +} + +bool isEOFReached() +{ + return atomic_load(&EOFReached) ? true : false; +} + +void setEOFReached() +{ + atomic_store(&EOFReached, true); +} + +void setEOFNotReached() +{ + atomic_store(&EOFReached, false); +} + +void skip() +{ + currentImplementation = NONE; + setSkipToNext(true); + setRepeatEnabled(false); +} + +bool isPlaybackDone() +{ + if (isEOFReached()) + { + return true; + } + else + { + return false; + } +} + +float getSeekPercentage() +{ + return seekPercent; +} + +bool isSeekRequested() +{ + return seekRequested; +} + +void setSeekRequested(bool value) +{ + seekRequested = value; +} + +void seekPercentage(float percent) +{ + seekPercent = percent; + seekRequested = true; +} + +void resumePlayback() +{ + if (!ma_device_is_started(&device)) + { + ma_device_start(&device); + } + paused = false; +} + +void pausePlayback() +{ + if (ma_device_is_started(&device)) + { + ma_device_stop(&device); + paused = true; + } +} + +void cleanupPlaybackDevice() +{ + ma_device_stop(&device); + while (ma_device_get_state(&device) == ma_device_state_started) + { + c_sleep(100); + } + ma_device_uninit(&device); +} + +void togglePausePlayback() +{ + if (ma_device_is_started(&device)) + { + ma_device_stop(&device); + paused = true; + } + else if (paused) + { + resumePlayback(); + paused = false; + } +} + +bool isPaused() +{ + return paused; +} + +pthread_mutex_t deviceMutex = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t deviceStopped = PTHREAD_COND_INITIALIZER; + +void resetDevice() +{ + pthread_mutex_lock(&deviceMutex); + + if (ma_device_get_state(&device) == ma_device_state_started) + ma_device_stop(&device); + + while (ma_device_get_state(&device) == ma_device_state_started) + { + pthread_cond_wait(&deviceStopped, &deviceMutex); + } + + pthread_mutex_unlock(&deviceMutex); + + ma_device_uninit(&device); +} + +ma_device *getDevice() +{ + return &device; +} + +bool hasBuiltinDecoder(char *filePath) +{ + char exto[6]; + extractExtension(filePath, sizeof(exto) - 1, exto); + regex_t regex; + int ret = regcomp(®ex, BUILTIN_EXTENSIONS, REG_EXTENDED); + if (ret != 0) + { + printf("Failed to compile regular expression\n"); + return false; + } + return (match_regex(®ex, exto) == 0); +} + +void activateSwitch(PCMFileDataSource *pPCMDataSource) +{ + setSkipToNext(false); + if (!isRepeatEnabled()) + pPCMDataSource->currentFileIndex = 1 - pPCMDataSource->currentFileIndex; // Toggle between 0 and 1 + pPCMDataSource->switchFiles = true; +} + +void executeSwitch(PCMFileDataSource *pPCMDataSource) +{ + pPCMDataSource->switchFiles = false; + switchDecoder(); + + ma_uint64 length = 0; + ma_data_source_get_length_in_pcm_frames(getCurrentDecoder(), &length); + pPCMDataSource->totalFrames = length; + + // Close the current file, and open the new one + FILE *previousFile; + char *currentFilename; + SongData *currentSongData; + + if (pPCMDataSource->currentFileIndex == 0) + { + previousFile = pPCMDataSource->fileB; + currentFilename = pPCMDataSource->pUserData->filenameA; + currentSongData = pPCMDataSource->pUserData->songdataA; + pPCMDataSource->fileA = (currentFilename != NULL && strcmp(currentFilename, "") != 0) ? fopen(currentFilename, "rb") : NULL; + } + else + { + previousFile = pPCMDataSource->fileA; + currentFilename = pPCMDataSource->pUserData->filenameB; + currentSongData = pPCMDataSource->pUserData->songdataB; + pPCMDataSource->fileB = (currentFilename != NULL && strcmp(currentFilename, "") != 0) ? fopen(currentFilename, "rb") : NULL; + } + + pPCMDataSource->pUserData->currentSongData = currentSongData; + pPCMDataSource->currentPCMFrame = 0; + + if (previousFile != NULL) + fclose(previousFile); + + setSeekElapsed(0.0); + + setEOFReached(); +} diff --git a/src/soundcommon.h b/src/soundcommon.h new file mode 100644 index 0000000..97e461a --- /dev/null +++ b/src/soundcommon.h @@ -0,0 +1,189 @@ +#ifndef SOUND_COMMON_H +#define SOUND_COMMON_H +#include +#include +#include +#include +#include +#include +#include "file.h" +#include "cutils.h" +#include "stringfunc.h" + +#define CHANNELS 2 +#define SAMPLE_RATE 192000 +#define SAMPLE_WIDTH 3 +#define SAMPLE_FORMAT ma_format_s24 + +#ifndef MAX_BUFFER_SIZE +#define MAX_BUFFER_SIZE 3600 +#endif + +#ifndef TAGSETTINGS_STRUCT +#define TAGSETTINGS_STRUCT + +typedef struct +{ + char title[256]; + char artist[256]; + char album_artist[256]; + char album[256]; + char date[256]; +} TagSettings; + +#endif + +#ifndef SONGDATA_STRUCT +#define SONGDATA_STRUCT + +typedef struct +{ + gchar *trackId; + char filePath[MAXPATHLEN]; + char coverArtPath[MAXPATHLEN]; + char pcmFilePath[MAXPATHLEN]; + unsigned char *red; + unsigned char *green; + unsigned char *blue; + TagSettings *metadata; + FIBITMAP *cover; + double *duration; + char *pcmFile; + long pcmFileSize; + bool hasErrors; + bool deleted; + +} SongData; + +#endif + +#ifndef USERDATA_STRUCT +#define USERDATA_STRUCT +typedef struct +{ + char *filenameA; + char *filenameB; + SongData *songdataA; + SongData *songdataB; + SongData *currentSongData; + ma_uint32 currentPCMFrame; +} UserData; +#endif + + +#ifndef PCMFILEDATASOURCE_STRUCT +#define PCMFILEDATASOURCE_STRUCT +typedef struct +{ + ma_data_source_base base; + UserData *pUserData; + const char *filenameA; + const char *filenameB; + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + ma_uint32 currentPCMFrame; + ma_decoder decoderA; + ma_decoder decoderB; + ma_decoder currentDecoder; + FILE *fileA; + FILE *fileB; + bool switchFiles; + int currentFileIndex; + ma_uint64 totalFrames; +} PCMFileDataSource; +#endif + +enum AudioImplementation +{ + PCM, + BUILTIN, + OGG, + OPUS, + M4A, + NONE +}; + +enum AudioImplementation getCurrentImplementationType(); + +void setCurrentImplementationType(enum AudioImplementation value); + +int getBufferSize(); + +void setBufferSize(int value); + +ma_decoder *getFirstDecoder(); + +ma_decoder *getCurrentDecoder(); + +ma_decoder *getPreviousDecoder(); + +void switchDecoder(); + +void prepareNextDecoder(char *filepath); + +void resetDecoders(); + +void getFileInfo(const char* filename, ma_uint32* sampleRate, ma_uint32* channels, ma_format* format); + +ma_int32 *getAudioBuffer(); + +void setAudioBuffer(ma_int32 *buf); + +void freeAudioBuffer(); + +bool isRepeatEnabled(); + +void setRepeatEnabled(bool value); + +bool isShuffleEnabled(); + +void setShuffleEnabled(bool value); + +bool isSkipToNext(); + +void setSkipToNext(bool value); + +double getSeekElapsed(); + +void setSeekElapsed(double value); + +bool isEOFReached(); + +void setEOFReached(); + +void setEOFNotReached(); + +void skip(); + +bool isPlaybackDone(); + +float getSeekPercentage(); + +bool isSeekRequested(); + +void setSeekRequested(bool value); + +void seekPercentage(float percent); + +void resumePlayback(); + +void pausePlayback(); + +void cleanupPlaybackDevice(); + +void togglePausePlayback(); + +bool isPaused(); + +void resetDevice(); + +ma_device *getDevice(); + +bool hasBuiltinDecoder(char *filePath); + +void activateSwitch(PCMFileDataSource *pPCMDataSource); + +void executeSwitch(PCMFileDataSource *pPCMDataSource); + +#endif \ No newline at end of file diff --git a/src/soundgapless.c b/src/soundgapless.c index 7c15df1..29d3ba6 100644 --- a/src/soundgapless.c +++ b/src/soundgapless.c @@ -2,16 +2,8 @@ #define MA_NO_ENGINE #define MINIAUDIO_IMPLEMENTATION #include -#include -#include -#include -#include -#include -#include -#include -#include -#include "file.h" #include "soundgapless.h" + /* soundgapless.c @@ -19,406 +11,142 @@ soundgapless.c Functions related to miniaudio implementation */ -ma_int32 *g_audioBuffer = NULL; -ma_device device = {0}; + ma_context context; -ma_device_config deviceConfig; +UserData *g_userData; PCMFileDataSource pcmDataSource; -bool paused = false; -bool skipToNext = false; -bool repeatEnabled = false; -bool shuffleEnabled = false; -double seekElapsed; - -_Atomic bool EOFReached = false; - -bool isEOFReached() -{ - return atomic_load(&EOFReached) ? true : false; -} - -void setEOFReached() -{ - atomic_store(&EOFReached, true); -} -void setEOFNotReached() +ma_result pcm_file_data_source_init(PCMFileDataSource *pPCMDataSource, UserData *pUserData) { - atomic_store(&EOFReached, false); -} + char *filePath = NULL; -static ma_result pcm_file_data_source_read(ma_data_source *pDataSource, void *pFramesOut, ma_uint64 frameCount, ma_uint64 *pFramesRead) -{ - // Dummy implementation - (void)pDataSource; - (void)pFramesOut; - (void)frameCount; - (void)pFramesRead; - return MA_SUCCESS; -} + filePath = (pPCMDataSource->currentFileIndex == 0) ? pUserData->songdataA->filePath : pUserData->songdataB->filePath; -static ma_result pcm_file_data_source_seek(ma_data_source *pDataSource, ma_uint64 frameIndex) -{ - // Cast to the correct data source type - PCMFileDataSource *pPCMDataSource = (PCMFileDataSource *)pDataSource; - - // Calculate the byte index - ma_uint64 byteIndex = frameIndex * ma_get_bytes_per_frame(pPCMDataSource->format, pPCMDataSource->channels); - - // Find the correct file - FILE *currentFile; - if (pPCMDataSource->currentFileIndex == 0) + if (hasBuiltinDecoder(filePath)) { - currentFile = pPCMDataSource->fileA; + prepareNextDecoder(filePath); + ma_decoder *first = getFirstDecoder(); + pPCMDataSource->format = first->outputFormat; + pPCMDataSource->channels = first->outputChannels; + pPCMDataSource->sampleRate = first->outputSampleRate; + + ma_data_source_get_length_in_pcm_frames(first, &pPCMDataSource->totalFrames); } else { - currentFile = pPCMDataSource->fileB; - } - - if (currentFile != NULL) - { - // Seek to the byte index in the file - int result = fseek(currentFile, byteIndex, SEEK_SET); - - // Set the current frame to frameIndex - pPCMDataSource->currentPCMFrame = (ma_uint32)frameIndex; + char *filePath = NULL; - // Check for errors - if (result == 0) + if (pPCMDataSource->fileA == NULL) { - return MA_SUCCESS; + pPCMDataSource->filenameA = pUserData->filenameA; + pPCMDataSource->fileA = fopen(pUserData->filenameA, "rb"); } - else - { - return MA_ERROR; - } - } - else - { - return MA_ERROR; - } -} -static ma_result pcm_file_data_source_get_data_format(ma_data_source *pDataSource, ma_format *pFormat, ma_uint32 *pChannels, ma_uint32 *pSampleRate, ma_channel *pChannelMap, size_t channelMapCap) -{ - (void)pChannelMap; - (void)channelMapCap; - PCMFileDataSource *pPCMDataSource = (PCMFileDataSource *)pDataSource; - *pFormat = pPCMDataSource->format; - *pChannels = pPCMDataSource->channels; - *pSampleRate = pPCMDataSource->sampleRate; - return MA_SUCCESS; -} - -static ma_result pcm_file_data_source_get_cursor(ma_data_source *pDataSource, ma_uint64 *pCursor) -{ - PCMFileDataSource *pPCMDataSource = (PCMFileDataSource *)pDataSource; - *pCursor = pPCMDataSource->currentPCMFrame; - - return MA_SUCCESS; -} - -static ma_result pcm_file_data_source_get_length(ma_data_source *pDataSource, ma_uint64 *pLength) -{ - PCMFileDataSource *pPCMDataSource = (PCMFileDataSource *)pDataSource; - - // Get the current file based on the current file index - FILE *currentFile; - if (pPCMDataSource->currentFileIndex == 0) - { - currentFile = pPCMDataSource->fileA; + pPCMDataSource->format = SAMPLE_FORMAT; + pPCMDataSource->channels = CHANNELS; + pPCMDataSource->sampleRate = SAMPLE_RATE; } - else - { - currentFile = pPCMDataSource->fileB; - } - - ma_uint64 fileSize; - // Seek to the end of the current file to get its size - fseek(currentFile, 0, SEEK_END); - fileSize = ftell(currentFile); - fseek(currentFile, 0, SEEK_SET); - - // Calculate the total number of frames in the current file - ma_uint64 frameCount = fileSize / ma_get_bytes_per_frame(pPCMDataSource->format, pPCMDataSource->channels); - *pLength = frameCount; - - return MA_SUCCESS; -} - -static ma_result pcm_file_data_source_set_looping(ma_data_source *pDataSource, ma_bool32 isLooping) -{ - // Dummy implementation - (void)pDataSource; - (void)isLooping; - - return MA_SUCCESS; -} - -static ma_data_source_vtable pcm_file_data_source_vtable = { - pcm_file_data_source_read, - pcm_file_data_source_seek, - pcm_file_data_source_get_data_format, - pcm_file_data_source_get_cursor, - pcm_file_data_source_get_length, - pcm_file_data_source_set_looping, - 0 // flags -}; - -ma_result pcm_file_data_source_init(PCMFileDataSource *pPCMDataSource, const char *filenameA, UserData *pUserData) -{ pPCMDataSource->pUserData = pUserData; - pPCMDataSource->filenameA = filenameA; - pPCMDataSource->format = SAMPLE_FORMAT; - pPCMDataSource->channels = CHANNELS; - pPCMDataSource->sampleRate = SAMPLE_RATE; pPCMDataSource->currentPCMFrame = 0; - pPCMDataSource->currentFileIndex = 0; - pPCMDataSource->fileA = fopen(filenameA, "rb"); return MA_SUCCESS; } -void activateSwitch(PCMFileDataSource *pPCMDataSource) -{ - skipToNext = false; - if (!repeatEnabled) - pPCMDataSource->currentFileIndex = 1 - pPCMDataSource->currentFileIndex; // Toggle between 0 and 1 - pPCMDataSource->switchFiles = true; -} - -void executeSwitch(PCMFileDataSource *pPCMDataSource) -{ - pPCMDataSource->switchFiles = false; - - // Close the current file, and open the new one - FILE *currentFile; - if (pPCMDataSource->currentFileIndex == 0) - { - currentFile = pPCMDataSource->fileB; - if (currentFile != NULL) - fclose(currentFile); - pPCMDataSource->fileA = (pPCMDataSource->pUserData->filenameA != NULL) ? fopen(pPCMDataSource->pUserData->filenameA, "rb") : NULL; - pPCMDataSource->pUserData->currentSongData = pPCMDataSource->pUserData->songdataA; - } - else - { - currentFile = pPCMDataSource->fileA; - if (currentFile != NULL) - fclose(currentFile); - pPCMDataSource->fileB = (pPCMDataSource->pUserData->filenameB != NULL) ? fopen(pPCMDataSource->pUserData->filenameB, "rb") : NULL; - pPCMDataSource->pUserData->currentSongData = pPCMDataSource->pUserData->songdataB; - } - - pPCMDataSource->currentPCMFrame = 0; - - setEOFReached(); - - seekElapsed = 0; -} - -void pcm_file_data_source_read_pcm_frames(ma_data_source *pDataSource, void *pFramesOut, ma_uint64 frameCount, ma_uint64 *pFramesRead) -{ - PCMFileDataSource *pPCMDataSource = (PCMFileDataSource *)pDataSource; - ma_uint32 framesToRead = (ma_uint32)frameCount; - - ma_uint32 bytesPerFrame = ma_get_bytes_per_frame(pPCMDataSource->format, pPCMDataSource->channels); - ma_uint32 bytesToRead = framesToRead * bytesPerFrame; - ma_uint32 framesRead = 0; - - while (framesToRead > 0) - { - // Check if a file switch is required - if (pPCMDataSource->switchFiles) - { - executeSwitch(pPCMDataSource); - break; // Exit the loop after the file switch - } - - // Read from the current file - FILE *currentFile; - currentFile = (pPCMDataSource->currentFileIndex == 0) ? pPCMDataSource->fileA : pPCMDataSource->fileB; - ma_uint32 bytesRead = 0; - - if (pPCMDataSource->seekRequested == true) - { - if (currentFile != NULL) - { - fseek(currentFile, 0, SEEK_END); - ma_uint64 fileSize = ftell(currentFile); - pPCMDataSource->totalFrames = fileSize / bytesPerFrame; - pPCMDataSource->base.rangeEndInFrames = pPCMDataSource->totalFrames; - } - - ma_uint32 targetFrame = (pPCMDataSource->totalFrames * pPCMDataSource->seekPercentage) / 100; - - // Set the read pointer for the data source - ma_data_source_seek_to_pcm_frame(pDataSource, targetFrame); - - pPCMDataSource->seekRequested = false; // Reset seek flag - break; - framesRead = 0; // Reset framesRead - framesToRead = (ma_uint32)frameCount; // Reset framesToRead - bytesToRead = framesToRead * bytesPerFrame; // Reset bytesToRead - } - - if (currentFile != NULL) - bytesRead = (ma_uint32)fread((char *)pFramesOut + (framesRead * bytesPerFrame), 1, bytesToRead, currentFile); - - // If file is empty, skip - if ((bytesRead == 0 || skipToNext) && !isEOFReached()) - { - activateSwitch(pPCMDataSource); - continue; // Continue to the next iteration to read from the new file - } - - framesRead += bytesRead / bytesPerFrame; - framesToRead -= bytesRead / bytesPerFrame; - bytesToRead -= bytesRead; - } - - // Allocate memory for g_audioBuffer (if not already allocated) - if (g_audioBuffer == NULL) - { - g_audioBuffer = malloc(sizeof(ma_int32) * frameCount); - if (g_audioBuffer == NULL) - { - // Memory allocation failed - return; - } - } - - // No format conversion needed, just copy the audio samples - memcpy(g_audioBuffer, pFramesOut, sizeof(ma_int32) * framesRead); - - if (pFramesRead != NULL) - *pFramesRead = framesRead; -} - -void on_audio_frames(ma_device *pDevice, void *pFramesOut, const void *pFramesIn, ma_uint32 frameCount) +void createDevice(UserData *userData, ma_device *device, ma_context *context, ma_data_source_vtable *vtable, ma_device_data_proc callback) { - PCMFileDataSource *pDataSource = (PCMFileDataSource *)pDevice->pUserData; - ma_uint64 framesRead = 0; - pcm_file_data_source_read_pcm_frames(&pDataSource->base, pFramesOut, frameCount, &framesRead); - (void)pFramesIn; -} + ma_result result; -void createAudioDevice(UserData *userData) -{ - ma_result result = ma_context_init(NULL, 0, NULL, &context); if (result != MA_SUCCESS) - { - printf("Failed to initialize miniaudio context.\n"); return; - } - pcm_file_data_source_init(&pcmDataSource, userData->filenameA, userData); + ma_data_source_uninit(&pcmDataSource); + pcm_file_data_source_init(&pcmDataSource, userData); - pcmDataSource.base.vtable = &pcm_file_data_source_vtable; + pcmDataSource.base.vtable = vtable; ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback); - deviceConfig.playback.format = SAMPLE_FORMAT; - deviceConfig.playback.channels = CHANNELS; - deviceConfig.sampleRate = SAMPLE_RATE; - deviceConfig.dataCallback = on_audio_frames; + deviceConfig.playback.format = pcmDataSource.format; + deviceConfig.playback.channels = pcmDataSource.channels; + deviceConfig.sampleRate = pcmDataSource.sampleRate; + deviceConfig.dataCallback = callback; deviceConfig.pUserData = &pcmDataSource; - result = ma_device_init(&context, &deviceConfig, &device); + result = ma_device_init(context, &deviceConfig, device); if (result != MA_SUCCESS) - { - printf("Failed to initialize miniaudio device.\n"); return; - } - - result = ma_device_start(&device); + result = ma_device_start(device); if (result != MA_SUCCESS) - { - printf("Failed to start miniaudio device.\n"); return; - } } -void seekPercentage(float percent) +void builtin_createAudioDevice(UserData *userData, ma_device *device, ma_context *context, ma_data_source_vtable *vtable) { - pcmDataSource.seekPercentage = percent; - pcmDataSource.seekRequested = true; + createDevice(userData, device, context, vtable, builtin_on_audio_frames); } -void resumePlayback() +void pcm_createAudioDevice(UserData *userData, ma_device *device, ma_context *context, ma_data_source_vtable *vtable) { - if (!ma_device_is_started(&device)) - { - ma_device_start(&device); - } - paused = false; + createDevice(userData, device, context, vtable, pcm_on_audio_frames); } -void pausePlayback() +void switchAudioImplementation() { + enum AudioImplementation currentImplementation = getCurrentImplementationType(); + char *filePath = g_userData->currentSongData->filePath; - if (ma_device_is_started(&device)) + if (hasBuiltinDecoder(filePath)) { - ma_device_stop(&device); - paused = true; - } -} + ma_uint32 sampleRate; + ma_uint32 channels; + ma_format format; + ma_decoder *decoder = getCurrentDecoder(); -void togglePausePlayback() -{ + getFileInfo(filePath, &sampleRate, &channels, &format); - if (ma_device_is_started(&device)) - { - ma_device_stop(&device); - paused = true; - } - else if (paused) - { - resumePlayback(); - paused = false; - } -} - -bool isPaused() -{ - return paused; -} + bool sameFormat = (decoder == NULL || (sampleRate == decoder->outputSampleRate && + channels == decoder->outputChannels && + format == decoder->outputFormat)); -bool isPlaybackDone() -{ - if (atomic_load(&EOFReached)) - { - setEOFNotReached(); - return true; + if (sameFormat && currentImplementation == BUILTIN) + { + setEOFNotReached(); + setCurrentImplementationType(BUILTIN); + return; + } + else + { + cleanupPlaybackDevice(); + resetDecoders(); + builtin_createAudioDevice(g_userData, getDevice(), &context, &builtin_file_data_source_vtable); + setCurrentImplementationType(BUILTIN); + } } else { - return false; + if (currentImplementation == PCM) + { + setEOFNotReached(); + setCurrentImplementationType(PCM); + return; + } + cleanupPlaybackDevice(); + resetDecoders(); + pcm_createAudioDevice(g_userData, getDevice(), &context, &pcm_file_data_source_vtable); + setCurrentImplementationType(PCM); } -} -void cleanupPlaybackDevice() -{ - ma_device_stop(&device); - while (ma_device_get_state(&device) == ma_device_state_started) - { - c_sleep(100); - } - ma_device_uninit(&device); + setEOFNotReached(); } -void freeAudioBuffer() +void cleanupAudioContext() { - if (g_audioBuffer != NULL) - { - free(g_audioBuffer); - g_audioBuffer = NULL; - } + ma_context_uninit(&context); } -void skip() +void createAudioDevice(UserData *userData) { - skipToNext = true; - repeatEnabled = false; -} + g_userData = userData; + ma_context_init(NULL, 0, NULL, &context); + switchAudioImplementation(); +} \ No newline at end of file diff --git a/src/soundgapless.h b/src/soundgapless.h index fd99592..4541f47 100644 --- a/src/soundgapless.h +++ b/src/soundgapless.h @@ -6,72 +6,23 @@ #include #include #include +#include +#include #include +#include +#include +#include "file.h" #include "songloader.h" +#include "soundcommon.h" +#include "soundbuiltin.h" +#include "soundpcm.h" -#ifndef USERDATA_STRUCT -#define USERDATA_STRUCT -typedef struct -{ - char *filenameA; - char *filenameB; - SongData *songdataA; - SongData *songdataB; - SongData *currentSongData; - ma_uint32 currentPCMFrame; -} UserData; -#endif - -#ifndef PCMFILEDATASOURCE_STRUCT -#define PCMFILEDATASOURCE_STRUCT -typedef struct -{ - ma_data_source_base base; - UserData *pUserData; - const char *filenameA; - const char *filenameB; - ma_format format; - ma_uint32 channels; - ma_uint32 sampleRate; - ma_uint32 currentPCMFrame; - FILE *fileA; - FILE *fileB; - bool switchFiles; - int currentFileIndex; - bool seekRequested; - float seekPercentage; - ma_uint64 totalFrames; -} PCMFileDataSource; -#endif - -#define CHANNELS 2 -#define SAMPLE_RATE 192000 -#define SAMPLE_WIDTH 3 -#define SAMPLE_FORMAT ma_format_s24 - -extern ma_int32 *g_audioBuffer; -extern bool repeatEnabled; -extern bool shuffleEnabled; -extern double seekElapsed; +void setDecoders(bool usingA, char *filePath); void createAudioDevice(UserData *userData); -void resumePlayback(); - -void pausePlayback(); - -void togglePausePlayback(); - -bool isPaused(); - -void cleanupPlaybackDevice(); - -void freeAudioBuffer(); - -bool isPlaybackDone(); - -void skip(); +void switchAudioImplementation(); -void seekPercentage(float percent); +void cleanupAudioContext(); #endif diff --git a/src/soundpcm.c b/src/soundpcm.c new file mode 100644 index 0000000..dd1c3c2 --- /dev/null +++ b/src/soundpcm.c @@ -0,0 +1,225 @@ +#include "soundpcm.h" + +/* + +soundpcm.c + + Functions related to miniaudio implementation for pcm audio files + +*/ + +#define CHANNELS 2 +#define SAMPLE_RATE 192000 +#define SAMPLE_WIDTH 3 +#define SAMPLE_FORMAT ma_format_s24 + +void pcm_read_pcm_frames(ma_data_source *pDataSource, void *pFramesOut, ma_uint64 frameCount, ma_uint64 *pFramesRead) +{ + PCMFileDataSource *pPCMDataSource = (PCMFileDataSource *)pDataSource; + ma_uint32 framesToRead = (ma_uint32)frameCount; + + ma_uint32 bytesPerFrame = ma_get_bytes_per_frame(pPCMDataSource->format, pPCMDataSource->channels); + ma_uint32 bytesToRead = framesToRead * bytesPerFrame; + ma_uint32 framesRead = 0; + + while (framesToRead > 0) + { + if (pPCMDataSource == NULL) + return; + + if (pPCMDataSource->switchFiles) + { + executeSwitch(pPCMDataSource); + break; + } + + if (getCurrentImplementationType() != PCM && !isSkipToNext()) + return; + + FILE *currentFile; + currentFile = (pPCMDataSource->currentFileIndex == 0) ? pPCMDataSource->fileA : pPCMDataSource->fileB; + ma_uint32 bytesRead = 0; + + if (isSeekRequested()) + { + if (currentFile != NULL) + { + fseek(currentFile, 0, SEEK_END); + ma_uint64 fileSize = ftell(currentFile); + pPCMDataSource->totalFrames = fileSize / bytesPerFrame; + pPCMDataSource->base.rangeEndInFrames = pPCMDataSource->totalFrames; + } + + ma_uint32 targetFrame = (pPCMDataSource->totalFrames * getSeekPercentage()) / 100; + + ma_data_source_seek_to_pcm_frame(pDataSource, targetFrame); + + setSeekRequested(false); // Reset seek flag + break; + framesRead = 0; + framesToRead = (ma_uint32)frameCount; + bytesToRead = framesToRead * bytesPerFrame; + } + + if (currentFile != NULL) + bytesRead = (ma_uint32)fread((char *)pFramesOut + (framesRead * bytesPerFrame), 1, bytesToRead, currentFile); + else if (pPCMDataSource->pUserData->currentSongData == NULL || hasBuiltinDecoder(pPCMDataSource->pUserData->currentSongData->filePath)) + return; + + // If file is empty, skip + if ((bytesRead == 0 || isSkipToNext()) && !isEOFReached()) + { + activateSwitch(pPCMDataSource); + continue; + } + + framesRead += bytesRead / bytesPerFrame; + framesToRead -= bytesRead / bytesPerFrame; + bytesToRead -= bytesRead; + setBufferSize(framesRead); + } + + ma_int32 *audioBuffer = getAudioBuffer(); + if (audioBuffer == NULL) + { + audioBuffer = malloc(sizeof(ma_int32) * MAX_BUFFER_SIZE); + if (audioBuffer == NULL) + { + return; + } + } + + memcpy(audioBuffer, pFramesOut, sizeof(ma_int32) * framesRead); + setAudioBuffer(audioBuffer); + + if (pFramesRead != NULL) + *pFramesRead = framesRead; +} + +void pcm_on_audio_frames(ma_device *pDevice, void *pFramesOut, const void *pFramesIn, ma_uint32 frameCount) +{ + PCMFileDataSource *pDataSource = (PCMFileDataSource *)pDevice->pUserData; + ma_uint64 framesRead = 0; + pcm_read_pcm_frames(&pDataSource->base, pFramesOut, frameCount, &framesRead); + (void)pFramesIn; +} + +static ma_result pcm_file_data_source_read(ma_data_source *pDataSource, void *pFramesOut, ma_uint64 frameCount, ma_uint64 *pFramesRead) +{ + // Dummy implementation + (void)pDataSource; + (void)pFramesOut; + (void)frameCount; + (void)pFramesRead; + return MA_SUCCESS; +} + +static ma_result pcm_file_data_source_seek(ma_data_source *pDataSource, ma_uint64 frameIndex) +{ + // Cast to the correct data source type + PCMFileDataSource *pPCMDataSource = (PCMFileDataSource *)pDataSource; + + // Calculate the byte index + ma_uint64 byteIndex = frameIndex * ma_get_bytes_per_frame(pPCMDataSource->format, pPCMDataSource->channels); + + // Find the correct file + FILE *currentFile; + if (pPCMDataSource->currentFileIndex == 0) + { + currentFile = pPCMDataSource->fileA; + } + else + { + currentFile = pPCMDataSource->fileB; + } + + if (currentFile != NULL) + { + // Seek to the byte index in the file + int result = fseek(currentFile, byteIndex, SEEK_SET); + + // Set the current frame to frameIndex + pPCMDataSource->currentPCMFrame = (ma_uint32)frameIndex; + + // Check for errors + if (result == 0) + { + return MA_SUCCESS; + } + else + { + return MA_ERROR; + } + } + else + { + return MA_ERROR; + } +} + +static ma_result pcm_file_data_source_get_data_format(ma_data_source *pDataSource, ma_format *pFormat, ma_uint32 *pChannels, ma_uint32 *pSampleRate, ma_channel *pChannelMap, size_t channelMapCap) +{ + (void)pChannelMap; + (void)channelMapCap; + PCMFileDataSource *pPCMDataSource = (PCMFileDataSource *)pDataSource; + *pFormat = pPCMDataSource->format; + *pChannels = pPCMDataSource->channels; + *pSampleRate = pPCMDataSource->sampleRate; + return MA_SUCCESS; +} + +static ma_result pcm_file_data_source_get_cursor(ma_data_source *pDataSource, ma_uint64 *pCursor) +{ + PCMFileDataSource *pPCMDataSource = (PCMFileDataSource *)pDataSource; + *pCursor = pPCMDataSource->currentPCMFrame; + + return MA_SUCCESS; +} + +static ma_result pcm_file_data_source_get_length(ma_data_source *pDataSource, ma_uint64 *pLength) +{ + PCMFileDataSource *pPCMDataSource = (PCMFileDataSource *)pDataSource; + + // Get the current file based on the current file index + FILE *currentFile; + if (pPCMDataSource->currentFileIndex == 0) + { + currentFile = pPCMDataSource->fileA; + } + else + { + currentFile = pPCMDataSource->fileB; + } + + ma_uint64 fileSize; + + // Seek to the end of the current file to get its size + fseek(currentFile, 0, SEEK_END); + fileSize = ftell(currentFile); + fseek(currentFile, 0, SEEK_SET); + + // Calculate the total number of frames in the current file + ma_uint64 frameCount = fileSize / ma_get_bytes_per_frame(pPCMDataSource->format, pPCMDataSource->channels); + *pLength = frameCount; + + return MA_SUCCESS; +} + +static ma_result pcm_file_data_source_set_looping(ma_data_source *pDataSource, ma_bool32 isLooping) +{ + // Dummy implementation + (void)pDataSource; + (void)isLooping; + + return MA_SUCCESS; +} + +ma_data_source_vtable pcm_file_data_source_vtable = { + pcm_file_data_source_read, + pcm_file_data_source_seek, + pcm_file_data_source_get_data_format, + pcm_file_data_source_get_cursor, + pcm_file_data_source_get_length, + pcm_file_data_source_set_looping, + 0 // flags +}; \ No newline at end of file diff --git a/src/soundpcm.h b/src/soundpcm.h new file mode 100644 index 0000000..a603d74 --- /dev/null +++ b/src/soundpcm.h @@ -0,0 +1,12 @@ +#ifndef SOUNDPCM_H +#define SOUNDPCM_H +#include +#include +#include +#include "soundcommon.h" + +extern ma_data_source_vtable pcm_file_data_source_vtable; + +void pcm_on_audio_frames(ma_device *pDevice, void *pFramesOut, const void *pFramesIn, ma_uint32 frameCount); + +#endif diff --git a/src/visuals.c b/src/visuals.c index 836f2ce..fcd71aa 100644 --- a/src/visuals.c +++ b/src/visuals.c @@ -1,23 +1,29 @@ #include "visuals.h" #include "albumart.h" #include "complex.h" -#define SAMPLE_RATE 192000 -#define BUFFER_SIZE 3600 #define CHANNELS 2 #define BEAT_THRESHOLD 0.3 -#define MAGNITUDE_CEIL 120 + #define MAGNITUDE_FLOOR_FRACTION 0.4 +#ifndef MAX_BUFFER_SIZE +#define MAX_BUFFER_SIZE 3600 +#endif + +int bufferSize = 3600; +float magnitudeCeil = 120; +float alpha = 0.2; + /* visuals.c This file should contain only functions related to the spectrum visualizer. - + */ int bufferIndex = 0; -float magnitudeBuffer[BUFFER_SIZE] = {0.0f}; -float lastMagnitudes[BUFFER_SIZE] = {0.0f}; +float magnitudeBuffer[MAX_BUFFER_SIZE] = {0.0f}; +float lastMagnitudes[MAX_BUFFER_SIZE] = {0.0f}; void printBlankSpaces(int numSpaces) { @@ -30,14 +36,14 @@ void printBlankSpaces(int numSpaces) void updateMagnitudeBuffer(float magnitude) { magnitudeBuffer[bufferIndex] = magnitude; - bufferIndex = (bufferIndex + 1) % BUFFER_SIZE; + bufferIndex = (bufferIndex + 1) % bufferSize; } float calculateMovingAverage() { float sum = 0.0f; int numSamples = 0; - for (int i = 0; i < BUFFER_SIZE; i++) + for (int i = 0; i < bufferSize; i++) { sum += magnitudeBuffer[i]; if (magnitudeBuffer[i] > 0.0f) @@ -53,7 +59,7 @@ float calculateThreshold() { float sum = 0.0f; int numSamples = 0; - for (int i = 0; i < BUFFER_SIZE; i++) + for (int i = 0; i < bufferSize; i++) { sum += magnitudeBuffer[i]; if (magnitudeBuffer[i] > 0.0f) @@ -65,7 +71,7 @@ float calculateThreshold() float mean = sum / numSamples; float variance = 0.0f; - for (int i = 0; i < BUFFER_SIZE; i++) + for (int i = 0; i < bufferSize; i++) { float diff = magnitudeBuffer[i] - mean; variance += diff * diff; @@ -113,16 +119,19 @@ void updateMagnitudes(int height, int width, float maxMagnitude, float *magnitud int beat = detectBeats(magnitudes, width); if (beat > 0) { - jumpFactor = jumpAmount; + // jumpFactor = jumpAmount; } for (int i = 0; i < width; i++) { if (i < 3) exponent = 1.0; - else if (magnitudes[i] > maxMagnitude * 0.25) + else if (magnitudes[i] > maxMagnitude * 0.37) { - exponent = 3.0; + exponent = 2.0; + } + else { + exponent = 1.0; } float normalizedMagnitude = magnitudes[i] / maxMagnitude; float scaledMagnitude = pow(normalizedMagnitude, exponent) * height + jumpFactor; @@ -139,13 +148,11 @@ void updateMagnitudes(int height, int width, float maxMagnitude, float *magnitud } } -float lastMax = MAGNITUDE_CEIL / 2; -float alpha = 0.2; - float calcMaxMagnitude(int numBars, float *magnitudes) { + float lastMax = magnitudeCeil / 2; float maxMagnitude = 0.0f; - float threshold = MAGNITUDE_CEIL * MAGNITUDE_FLOOR_FRACTION; + float threshold = magnitudeCeil * MAGNITUDE_FLOOR_FRACTION; for (int i = 0; i < numBars; i++) { if (magnitudes[i] > maxMagnitude) @@ -153,9 +160,9 @@ float calcMaxMagnitude(int numBars, float *magnitudes) maxMagnitude = magnitudes[i]; } } - if (maxMagnitude > MAGNITUDE_CEIL) + if (maxMagnitude > magnitudeCeil) { - maxMagnitude = MAGNITUDE_CEIL; + maxMagnitude = magnitudeCeil; } if (maxMagnitude < threshold) { @@ -173,75 +180,86 @@ void clearMagnitudes(int width, float *magnitudes) } } -void compressSpectrum(int width, fftwf_complex *fftOutput, float *compressedMagnitudes, int numBars, float fractionToKeep) +void calc(int height, int numBars, ma_int32 *audioBuffer, int bitDepth, fftwf_complex *fftInput, fftwf_complex *fftOutput, float *magnitudes, fftwf_plan plan) { - if (numBars <= 0) - { - return; - } + int bufferSize = getBufferSize(); - if (fractionToKeep < 0.0f || fractionToKeep > 1.0f) + if (audioBuffer == NULL) { return; } - int compressionFactor = width / numBars; - int barSpan = ceil(compressionFactor * fractionToKeep); - - for (int i = 0; i < numBars; i++) - { - int startIndex = (int)i * barSpan; + int j = 0; - int barEndIndex = startIndex + barSpan; + for (int i = 0; i < bufferSize; i++) + { + ma_int32 sample = audioBuffer[i]; - if (barEndIndex > width) - barEndIndex = width; + float normalizedSample; - float barMagnitude = 0.0f; - for (int j = startIndex; j < barEndIndex; j++) + // Adjust normalization based on bit depth + if (bitDepth == 8) { - float magnitude = cabsf(fftOutput[j][0] + fftOutput[j][1] * I); - barMagnitude += magnitude; + normalizedSample = ((float)sample - 128) / 127.0f; } - barMagnitude /= (barEndIndex - startIndex); // Normalize by the number of bins - compressedMagnitudes[i] = barMagnitude; - } -} - -void calcSpectrum(int height, int numBars, fftwf_complex *fftInput, fftwf_complex *fftOutput, float *magnitudes, fftwf_plan plan) -{ - if (g_audioBuffer == NULL) - { - return; - } + else if (bitDepth == 16) + { + normalizedSample = (float)sample / 32768.0f; + } + else if (bitDepth == 24) + { + // Extract the lower 24 bits from the sample + int lower24Bits = sample & 0xFFFFFF; - for (int i = 0; i < BUFFER_SIZE; i++) - { - ma_int32 sample = g_audioBuffer[i]; - // Extract the lower 24 bits from the sample - int lower24Bits = sample & 0xFFFFFF; + // Check if the 24th bit is set (indicating a negative value) + if (lower24Bits & 0x800000) + { + // Sign extension for two's complement + lower24Bits |= 0xFF000000; + } + else + { + // Ensure that the upper bits are cleared for positive values + lower24Bits &= 0x00FFFFFF; + } - // Check if the 24th bit is set (indicating a negative value) - if (lower24Bits & 0x800000) + // Normalize the 24-bit sample to the range [-1, 1] + normalizedSample = (float)lower24Bits / 8388607.0f; + } + else if (bitDepth == 32 || bitDepth == -32) // Assuming bitDepth == -32 for f32 { - // Sign extension for two's complement - lower24Bits |= 0xFF000000; + normalizedSample = (float)sample / 2147483647.0f; } else { - // Ensure that the upper bits are cleared for positive values - lower24Bits &= 0x00FFFFFF; + // Unsupported bit depth + return; + } + + if (bitDepth == 32) + { + if (i % 3 == 0) + { + continue; + } } - // Normalize the 24-bit sample to the range [-1, 1] - float normalizedSample = (float)lower24Bits / 8388607.0f; - fftInput[i][0] = normalizedSample; - fftInput[i][1] = 0; + + fftInput[j][0] = normalizedSample; + fftInput[j][1] = 0; + + j++; + } + + for (int k = j; k < bufferSize; k++) + { + fftInput[k][0] = 0; + fftInput[k][1] = 0; } // Apply Windowing (Hamming Window) - for (int i = 0; i < BUFFER_SIZE; i++) + for (int i = 0; i < bufferSize; i++) { - float window = 0.54f - 0.46f * cos(2 * M_PI * i / (BUFFER_SIZE - 1)); + float window = 0.54f - 0.46f * cos(2 * M_PI * i / (bufferSize - 1)); fftInput[i][0] *= window; } @@ -258,6 +276,45 @@ void calcSpectrum(int height, int numBars, fftwf_complex *fftInput, fftwf_comple updateMagnitudes(height, numBars, maxMagnitude, magnitudes); } +void calcSpectrum(int height, int numBars, fftwf_complex *fftInput, fftwf_complex *fftOutput, float *magnitudes, fftwf_plan plan) +{ + + ma_int32 *g_audioBuffer = getAudioBuffer(); + ma_decoder *decoder = getCurrentDecoder(); + int bitDepth = 24; + + ma_format format = SAMPLE_FORMAT; + + if (getCurrentImplementationType() == BUILTIN && decoder != NULL) + { + format = decoder->outputFormat; + + switch (format) + { + case ma_format_u8: + bitDepth = 8; + break; + + case ma_format_s16: + bitDepth = 16; + break; + + case ma_format_s24: + bitDepth = 24; + break; + + case ma_format_f32: + case ma_format_s32: + bitDepth = 32; + break; + default: + break; + } + } + + calc(height, numBars, g_audioBuffer, bitDepth, fftInput, fftOutput, magnitudes, plan); +} + PixelData increaseLuminosity(PixelData pixel, int amount) { PixelData pixel2; @@ -283,8 +340,6 @@ void printSpectrum(int height, int width, float *magnitudes, PixelData color) printf("\n"); clearRestOfScreen(); - PixelData pixelLight = increaseLuminosity(color, 100); - for (int j = height; j > 0; j--) { printf("\r"); @@ -295,16 +350,12 @@ void printSpectrum(int height, int width, float *magnitudes, PixelData color) { if (j == height) { - printf("\033[38;2;%d;%d;%dm", pixelLight.r, pixelLight.g, pixelLight.b); - } - else if (j == height - 1) - { - color = increaseLuminosity(color, 60); + color = increaseLuminosity(color, 100); printf("\033[38;2;%d;%d;%dm", color.r, color.g, color.b); } else { - color = decreaseLuminosity(color, 20); + color = decreaseLuminosity(color, 100 / height); printf("\033[38;2;%d;%d;%dm", color.r, color.g, color.b); } } @@ -335,6 +386,7 @@ void printSpectrum(int height, int width, float *magnitudes, PixelData color) void drawSpectrumVisualizer(int height, int width, PixelData c) { + bufferSize = getBufferSize(); PixelData color; color.r = c.r; color.g = c.g; @@ -348,20 +400,23 @@ void drawSpectrumVisualizer(int height, int width, PixelData c) return; } - fftwf_complex *fftInput = (fftwf_complex *)fftwf_malloc(sizeof(fftwf_complex) * BUFFER_SIZE); + if (bufferSize <= 0) + return; + + fftwf_complex *fftInput = (fftwf_complex *)fftwf_malloc(sizeof(fftwf_complex) * bufferSize); if (fftInput == NULL) { return; } - fftwf_complex *fftOutput = (fftwf_complex *)fftwf_malloc(sizeof(fftwf_complex) * BUFFER_SIZE); + fftwf_complex *fftOutput = (fftwf_complex *)fftwf_malloc(sizeof(fftwf_complex) * bufferSize); if (fftOutput == NULL) { fftwf_free(fftInput); return; } - fftwf_plan plan = fftwf_plan_dft_1d(BUFFER_SIZE, fftInput, fftOutput, FFTW_FORWARD, FFTW_ESTIMATE); + fftwf_plan plan = fftwf_plan_dft_1d(bufferSize, fftInput, fftOutput, FFTW_FORWARD, FFTW_ESTIMATE); float magnitudes[numBars]; for (int i = 0; i < numBars; i++)