Commit 350784a9 authored by David Reid's avatar David Reid

Version 0.11.22

parent a65a7d13
v0.11.22 - TBD v0.11.22 - 2025-02-24
===================== =====================
* Starting from version 0.12, miniaudio will be switching to a split .c/h pair, away from a single header. In preparation for this, a file named "miniaudio.c" has been added to repository. Currently this is just a simple wrapper around miniaudio.h and `MINIAUDIO_IMPLEMENTATION`. Nothing has changed in miniaudio.h, however when version 0.12 is released you will need to use miniaudio.c for the implementation. It's recommended you start the transition away from `MINIAUDIO_IMPLEMENTATION` and towards miniaudio.c. If you want to keep building your project as a single translation unit, you can do `#include "miniaudio.c"` which will continue to be supported with version 0.12 and beyond. * Starting from version 0.12, miniaudio will be switching to a split .c/h pair, away from a single header. In preparation for this, a file named "miniaudio.c" has been added to repository. Currently this is just a simple wrapper around miniaudio.h and `MINIAUDIO_IMPLEMENTATION`. Nothing has changed in miniaudio.h, however when version 0.12 is released you will need to use miniaudio.c for the implementation. It's recommended you start the transition away from `MINIAUDIO_IMPLEMENTATION` and towards miniaudio.c. If you want to keep building your project as a single translation unit, you can do `#include "miniaudio.c"` which will continue to be supported with version 0.12 and beyond.
* In the extras folder, the `miniaudio_libvorbis.h` and `miniaudio_libopus.h` files have been deprecated. They have been replaced with versions in the `extras/decoders` folder. They are now split into a separate .c and .h files. The old files still exist for compatibility, but you need to transition over to the new versions. The transition should be trivial. Compile the .c files like a normal source file, and include the .h file like a normal header. * In the extras folder, the `miniaudio_libvorbis.h` and `miniaudio_libopus.h` files have been deprecated. They have been replaced with versions in the `extras/decoders` folder. They are now split into a separate .c and .h files. The old files still exist for compatibility, but you need to transition over to the new versions. The transition should be trivial. Compile the .c files like a normal source file, and include the .h file like a normal header.
......
This source diff could not be displayed because it is too large. You can view the blob instead.
/* /*
Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file. Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file.
miniaudio - v0.11.22 - TBD miniaudio - v0.11.22 - 2025-02-24
David Reid - mackron@gmail.com David Reid - mackron@gmail.com
...@@ -37,8 +37,7 @@ extern "C" { ...@@ -37,8 +37,7 @@ extern "C" {
#endif #endif
#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) || defined(__ppc64__)
#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__)
#define MA_SIZEOF_PTR 8 #define MA_SIZEOF_PTR 8
#else #else
#define MA_SIZEOF_PTR 4 #define MA_SIZEOF_PTR 4
...@@ -102,7 +101,7 @@ typedef void* ma_handle; ...@@ -102,7 +101,7 @@ typedef void* ma_handle;
typedef void* ma_ptr; typedef void* ma_ptr;
/* /*
ma_proc is annoying because when compiling with GCC we get pendantic warnings about converting ma_proc is annoying because when compiling with GCC we get pedantic warnings about converting
between `void*` and `void (*)()`. We can't use `void (*)()` with MSVC however, because we'll get between `void*` and `void (*)()`. We can't use `void (*)()` with MSVC however, because we'll get
warning C4191 about "type cast between incompatible function types". To work around this I'm going warning C4191 about "type cast between incompatible function types". To work around this I'm going
to use a different data type depending on the compiler. to use a different data type depending on the compiler.
...@@ -296,7 +295,7 @@ Special wchar_t type to ensure any structures in the public sections that refere ...@@ -296,7 +295,7 @@ Special wchar_t type to ensure any structures in the public sections that refere
consistent size across all platforms. consistent size across all platforms.
On Windows, wchar_t is 2 bytes, whereas everywhere else it's 4 bytes. Since Windows likes to use On Windows, wchar_t is 2 bytes, whereas everywhere else it's 4 bytes. Since Windows likes to use
wchar_t for it's IDs, we need a special explicitly sized wchar type that is always 2 bytes on all wchar_t for its IDs, we need a special explicitly sized wchar type that is always 2 bytes on all
platforms. platforms.
*/ */
#if !defined(MA_POSIX) && defined(MA_WIN32) #if !defined(MA_POSIX) && defined(MA_WIN32)
...@@ -322,7 +321,7 @@ MA_LOG_LEVEL_INFO ...@@ -322,7 +321,7 @@ MA_LOG_LEVEL_INFO
callback. callback.
MA_LOG_LEVEL_WARNING MA_LOG_LEVEL_WARNING
Warnings. You should enable this in you development builds and action them when encounted. These Warnings. You should enable this in you development builds and action them when encountered. These
logs usually indicate a potential problem or misconfiguration, but still allow you to keep logs usually indicate a potential problem or misconfiguration, but still allow you to keep
running. This will never be called from within the data callback. running. This will never be called from within the data callback.
...@@ -1754,7 +1753,7 @@ input frames. ...@@ -1754,7 +1753,7 @@ input frames.
MA_API ma_result ma_resampler_get_expected_output_frame_count(const ma_resampler* pResampler, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount); MA_API ma_result ma_resampler_get_expected_output_frame_count(const ma_resampler* pResampler, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount);
/* /*
Resets the resampler's timer and clears it's internal cache. Resets the resampler's timer and clears its internal cache.
*/ */
MA_API ma_result ma_resampler_reset(ma_resampler* pResampler); MA_API ma_result ma_resampler_reset(ma_resampler* pResampler);
...@@ -1975,7 +1974,7 @@ MA_API void ma_channel_map_init_standard(ma_standard_channel_map standardChannel ...@@ -1975,7 +1974,7 @@ MA_API void ma_channel_map_init_standard(ma_standard_channel_map standardChannel
/* /*
Copies a channel map. Copies a channel map.
Both input and output channel map buffers must have a capacity of at at least `channels`. Both input and output channel map buffers must have a capacity of at least `channels`.
*/ */
MA_API void ma_channel_map_copy(ma_channel* pOut, const ma_channel* pIn, ma_uint32 channels); MA_API void ma_channel_map_copy(ma_channel* pOut, const ma_channel* pIn, ma_uint32 channels);
...@@ -2114,6 +2113,8 @@ MA_API void ma_data_source_uninit(ma_data_source* pDataSource); ...@@ -2114,6 +2113,8 @@ MA_API void ma_data_source_uninit(ma_data_source* pDataSource);
MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); /* Must support pFramesOut = NULL in which case a forward seek should be performed. */ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); /* Must support pFramesOut = NULL in which case a forward seek should be performed. */
MA_API ma_result ma_data_source_seek_pcm_frames(ma_data_source* pDataSource, ma_uint64 frameCount, ma_uint64* pFramesSeeked); /* Can only seek forward. Equivalent to ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount, &framesRead); */ MA_API ma_result ma_data_source_seek_pcm_frames(ma_data_source* pDataSource, ma_uint64 frameCount, ma_uint64* pFramesSeeked); /* Can only seek forward. Equivalent to ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount, &framesRead); */
MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex); MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex);
MA_API ma_result ma_data_source_seek_seconds(ma_data_source* pDataSource, float secondCount, float* pSecondsSeeked); /* Can only seek forward. Abstraction to ma_data_source_seek_pcm_frames() */
MA_API ma_result ma_data_source_seek_to_second(ma_data_source* pDataSource, float seekPointInSeconds); /* Abstraction to ma_data_source_seek_to_pcm_frame() */
MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap);
MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor); MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor);
MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength); /* Returns MA_NOT_IMPLEMENTED if the length is unknown or cannot be determined. Decoders can return this. */ MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength); /* Returns MA_NOT_IMPLEMENTED if the length is unknown or cannot be determined. Decoders can return this. */
...@@ -2479,6 +2480,12 @@ MA_API ma_result ma_event_wait(ma_event* pEvent); ...@@ -2479,6 +2480,12 @@ MA_API ma_result ma_event_wait(ma_event* pEvent);
Signals the specified auto-reset event. Signals the specified auto-reset event.
*/ */
MA_API ma_result ma_event_signal(ma_event* pEvent); MA_API ma_result ma_event_signal(ma_event* pEvent);
MA_API ma_result ma_semaphore_init(int initialValue, ma_semaphore* pSemaphore);
MA_API void ma_semaphore_uninit(ma_semaphore* pSemaphore);
MA_API ma_result ma_semaphore_wait(ma_semaphore* pSemaphore);
MA_API ma_result ma_semaphore_release(ma_semaphore* pSemaphore);
#endif /* MA_NO_THREADING */ #endif /* MA_NO_THREADING */
...@@ -2570,7 +2577,7 @@ Job Queue ...@@ -2570,7 +2577,7 @@ Job Queue
/* /*
Slot Allocator Slot Allocator
-------------- --------------
The idea of the slot allocator is for it to be used in conjunction with a fixed sized buffer. You use the slot allocator to allocator an index that can be used The idea of the slot allocator is for it to be used in conjunction with a fixed sized buffer. You use the slot allocator to allocate an index that can be used
as the insertion point for an object. as the insertion point for an object.
Slots are reference counted to help mitigate the ABA problem in the lock-free queue we use for tracking jobs. Slots are reference counted to help mitigate the ABA problem in the lock-free queue we use for tracking jobs.
...@@ -3303,6 +3310,8 @@ typedef union ...@@ -3303,6 +3310,8 @@ typedef union
int nullbackend; /* The null backend uses an integer for device IDs. */ int nullbackend; /* The null backend uses an integer for device IDs. */
} ma_device_id; } ma_device_id;
MA_API ma_bool32 ma_device_id_equal(const ma_device_id* pA, const ma_device_id* pB);
typedef struct ma_context_config ma_context_config; typedef struct ma_context_config ma_context_config;
typedef struct ma_device_config ma_device_config; typedef struct ma_device_config ma_device_config;
...@@ -3390,6 +3399,7 @@ struct ma_device_config ...@@ -3390,6 +3399,7 @@ struct ma_device_config
{ {
const char* pStreamNamePlayback; const char* pStreamNamePlayback;
const char* pStreamNameCapture; const char* pStreamNameCapture;
int channelMap;
} pulse; } pulse;
struct struct
{ {
...@@ -3409,6 +3419,7 @@ struct ma_device_config ...@@ -3409,6 +3419,7 @@ struct ma_device_config
ma_aaudio_allowed_capture_policy allowedCapturePolicy; ma_aaudio_allowed_capture_policy allowedCapturePolicy;
ma_bool32 noAutoStartAfterReroute; ma_bool32 noAutoStartAfterReroute;
ma_bool32 enableCompatibilityWorkarounds; ma_bool32 enableCompatibilityWorkarounds;
ma_bool32 allowSetBufferCapacity;
} aaudio; } aaudio;
}; };
...@@ -3481,7 +3492,7 @@ and on output returns detailed information about the device in `ma_device_info`. ...@@ -3481,7 +3492,7 @@ and on output returns detailed information about the device in `ma_device_info`.
case when the device ID is NULL, in which case information about the default device needs to be retrieved. case when the device ID is NULL, in which case information about the default device needs to be retrieved.
Once the context has been created and the device ID retrieved (if using anything other than the default device), the device can be created. Once the context has been created and the device ID retrieved (if using anything other than the default device), the device can be created.
This is a little bit more complicated than initialization of the context due to it's more complicated configuration. When initializing a This is a little bit more complicated than initialization of the context due to its more complicated configuration. When initializing a
device, a duplex device may be requested. This means a separate data format needs to be specified for both playback and capture. On input, device, a duplex device may be requested. This means a separate data format needs to be specified for both playback and capture. On input,
the data format is set to what the application wants. On output it's set to the native format which should match as closely as possible to the data format is set to what the application wants. On output it's set to the native format which should match as closely as possible to
the requested format. The conversion between the format requested by the application and the device's native format will be handled the requested format. The conversion between the format requested by the application and the device's native format will be handled
...@@ -3502,10 +3513,10 @@ asynchronous reading and writing, `onDeviceStart()` and `onDeviceStop()` should ...@@ -3502,10 +3513,10 @@ asynchronous reading and writing, `onDeviceStart()` and `onDeviceStop()` should
The handling of data delivery between the application and the device is the most complicated part of the process. To make this a bit The handling of data delivery between the application and the device is the most complicated part of the process. To make this a bit
easier, some helper callbacks are available. If the backend uses a blocking read/write style of API, the `onDeviceRead()` and easier, some helper callbacks are available. If the backend uses a blocking read/write style of API, the `onDeviceRead()` and
`onDeviceWrite()` callbacks can optionally be implemented. These are blocking and work just like reading and writing from a file. If the `onDeviceWrite()` callbacks can optionally be implemented. These are blocking and work just like reading and writing from a file. If the
backend uses a callback for data delivery, that callback must call `ma_device_handle_backend_data_callback()` from within it's callback. backend uses a callback for data delivery, that callback must call `ma_device_handle_backend_data_callback()` from within its callback.
This allows miniaudio to then process any necessary data conversion and then pass it to the miniaudio data callback. This allows miniaudio to then process any necessary data conversion and then pass it to the miniaudio data callback.
If the backend requires absolute flexibility with it's data delivery, it can optionally implement the `onDeviceDataLoop()` callback If the backend requires absolute flexibility with its data delivery, it can optionally implement the `onDeviceDataLoop()` callback
which will allow it to implement the logic that will run on the audio thread. This is much more advanced and is completely optional. which will allow it to implement the logic that will run on the audio thread. This is much more advanced and is completely optional.
The audio thread should run data delivery logic in a loop while `ma_device_get_state() == ma_device_state_started` and no errors have been The audio thread should run data delivery logic in a loop while `ma_device_get_state() == ma_device_state_started` and no errors have been
...@@ -4244,6 +4255,7 @@ struct ma_device ...@@ -4244,6 +4255,7 @@ struct ma_device
{ {
/*AAudioStream**/ ma_ptr pStreamPlayback; /*AAudioStream**/ ma_ptr pStreamPlayback;
/*AAudioStream**/ ma_ptr pStreamCapture; /*AAudioStream**/ ma_ptr pStreamCapture;
ma_mutex rerouteLock;
ma_aaudio_usage usage; ma_aaudio_usage usage;
ma_aaudio_content_type contentType; ma_aaudio_content_type contentType;
ma_aaudio_input_preset inputPreset; ma_aaudio_input_preset inputPreset;
...@@ -4667,6 +4679,10 @@ Retrieves basic information about every active playback and/or capture device. ...@@ -4667,6 +4679,10 @@ Retrieves basic information about every active playback and/or capture device.
This function will allocate memory internally for the device lists and return a pointer to them through the `ppPlaybackDeviceInfos` and `ppCaptureDeviceInfos` This function will allocate memory internally for the device lists and return a pointer to them through the `ppPlaybackDeviceInfos` and `ppCaptureDeviceInfos`
parameters. If you do not want to incur the overhead of these allocations consider using `ma_context_enumerate_devices()` which will instead use a callback. parameters. If you do not want to incur the overhead of these allocations consider using `ma_context_enumerate_devices()` which will instead use a callback.
Note that this only retrieves the ID and name/description of the device. The reason for only retrieving basic information is that it would otherwise require
opening the backend device in order to probe it for more detailed information which can be inefficient. Consider using `ma_context_get_device_info()` for this,
but don't call it from within the enumeration callback.
Parameters Parameters
---------- ----------
...@@ -4708,7 +4724,7 @@ The returned pointers will become invalid upon the next call this this function, ...@@ -4708,7 +4724,7 @@ The returned pointers will become invalid upon the next call this this function,
See Also See Also
-------- --------
ma_context_get_devices() ma_context_enumerate_devices()
*/ */
MA_API ma_result ma_context_get_devices(ma_context* pContext, ma_device_info** ppPlaybackDeviceInfos, ma_uint32* pPlaybackDeviceCount, ma_device_info** ppCaptureDeviceInfos, ma_uint32* pCaptureDeviceCount); MA_API ma_result ma_context_get_devices(ma_context* pContext, ma_device_info** ppPlaybackDeviceInfos, ma_uint32* pPlaybackDeviceCount, ma_device_info** ppCaptureDeviceInfos, ma_uint32* pCaptureDeviceCount);
...@@ -4847,7 +4863,7 @@ from a microphone. Whether or not you should send or receive data from the devic ...@@ -4847,7 +4863,7 @@ from a microphone. Whether or not you should send or receive data from the devic
playback, capture, full-duplex or loopback. (Note that loopback mode is only supported on select backends.) Sending and receiving audio data to and from the playback, capture, full-duplex or loopback. (Note that loopback mode is only supported on select backends.) Sending and receiving audio data to and from the
device is done via a callback which is fired by miniaudio at periodic time intervals. device is done via a callback which is fired by miniaudio at periodic time intervals.
The frequency at which data is delivered to and from a device depends on the size of it's period. The size of the period can be defined in terms of PCM frames The frequency at which data is delivered to and from a device depends on the size of its period. The size of the period can be defined in terms of PCM frames
or milliseconds, whichever is more convenient. Generally speaking, the smaller the period, the lower the latency at the expense of higher CPU usage and or milliseconds, whichever is more convenient. Generally speaking, the smaller the period, the lower the latency at the expense of higher CPU usage and
increased risk of glitching due to the more frequent and granular data deliver intervals. The size of a period will depend on your requirements, but increased risk of glitching due to the more frequent and granular data deliver intervals. The size of a period will depend on your requirements, but
miniaudio's defaults should work fine for most scenarios. If you're building a game you should leave this fairly small, whereas if you're building a simple miniaudio's defaults should work fine for most scenarios. If you're building a game you should leave this fairly small, whereas if you're building a simple
...@@ -4921,7 +4937,7 @@ then be set directly on the structure. Below are the members of the `ma_device_c ...@@ -4921,7 +4937,7 @@ then be set directly on the structure. Below are the members of the `ma_device_c
performanceProfile performanceProfile
A hint to miniaudio as to the performance requirements of your program. Can be either `ma_performance_profile_low_latency` (default) or A hint to miniaudio as to the performance requirements of your program. Can be either `ma_performance_profile_low_latency` (default) or
`ma_performance_profile_conservative`. This mainly affects the size of default buffers and can usually be left at it's default value. `ma_performance_profile_conservative`. This mainly affects the size of default buffers and can usually be left at its default value.
noPreSilencedOutputBuffer noPreSilencedOutputBuffer
When set to true, the contents of the output buffer passed into the data callback will be left undefined. When set to false (default), the contents of When set to true, the contents of the output buffer passed into the data callback will be left undefined. When set to false (default), the contents of
...@@ -4961,7 +4977,7 @@ then be set directly on the structure. Below are the members of the `ma_device_c ...@@ -4961,7 +4977,7 @@ then be set directly on the structure. Below are the members of the `ma_device_c
A pointer that will passed to callbacks in pBackendVTable. A pointer that will passed to callbacks in pBackendVTable.
resampling.linear.lpfOrder resampling.linear.lpfOrder
The linear resampler applies a low-pass filter as part of it's processing for anti-aliasing. This setting controls the order of the filter. The higher The linear resampler applies a low-pass filter as part of its processing for anti-aliasing. This setting controls the order of the filter. The higher
the value, the better the quality, in general. Setting this to 0 will disable low-pass filtering altogether. The maximum value is the value, the better the quality, in general. Setting this to 0 will disable low-pass filtering altogether. The maximum value is
`MA_MAX_FILTER_ORDER`. The default value is `min(4, MA_MAX_FILTER_ORDER)`. `MA_MAX_FILTER_ORDER`. The default value is `min(4, MA_MAX_FILTER_ORDER)`.
...@@ -5038,6 +5054,9 @@ then be set directly on the structure. Below are the members of the `ma_device_c ...@@ -5038,6 +5054,9 @@ then be set directly on the structure. Below are the members of the `ma_device_c
pulse.pStreamNameCapture pulse.pStreamNameCapture
PulseAudio only. Sets the stream name for capture. PulseAudio only. Sets the stream name for capture.
pulse.channelMap
PulseAudio only. Sets the channel map that is requested from PulseAudio. See MA_PA_CHANNEL_MAP_* constants. Defaults to MA_PA_CHANNEL_MAP_AIFF.
coreaudio.allowNominalSampleRateChange coreaudio.allowNominalSampleRateChange
Core Audio only. Desktop only. When enabled, allows the sample rate of the device to be changed at the operating system level. This Core Audio only. Desktop only. When enabled, allows the sample rate of the device to be changed at the operating system level. This
is disabled by default in order to prevent intrusive changes to the user's system. This is useful if you want to use a sample rate is disabled by default in order to prevent intrusive changes to the user's system. This is useful if you want to use a sample rate
...@@ -5211,7 +5230,7 @@ Unsafe. It is not safe to call this inside any callback. ...@@ -5211,7 +5230,7 @@ Unsafe. It is not safe to call this inside any callback.
Remarks Remarks
------- -------
You only need to use this function if you want to configure the context differently to it's defaults. You should never use this function if you want to manage You only need to use this function if you want to configure the context differently to its defaults. You should never use this function if you want to manage
your own context. your own context.
See the documentation for `ma_context_init()` for information on the different context configuration options. See the documentation for `ma_context_init()` for information on the different context configuration options.
...@@ -5976,7 +5995,7 @@ Utilities ...@@ -5976,7 +5995,7 @@ Utilities
************************************************************************************************************************************************************/ ************************************************************************************************************************************************************/
/* /*
Calculates a buffer size in milliseconds from the specified number of frames and sample rate. Calculates a buffer size in milliseconds (rounded up) from the specified number of frames and sample rate.
*/ */
MA_API ma_uint32 ma_calculate_buffer_size_in_milliseconds_from_frames(ma_uint32 bufferSizeInFrames, ma_uint32 sampleRate); MA_API ma_uint32 ma_calculate_buffer_size_in_milliseconds_from_frames(ma_uint32 bufferSizeInFrames, ma_uint32 sampleRate);
...@@ -6233,7 +6252,7 @@ struct ma_decoder ...@@ -6233,7 +6252,7 @@ struct ma_decoder
void* pInputCache; /* In input format. Can be null if it's not needed. */ void* pInputCache; /* In input format. Can be null if it's not needed. */
ma_uint64 inputCacheCap; /* The capacity of the input cache. */ ma_uint64 inputCacheCap; /* The capacity of the input cache. */
ma_uint64 inputCacheConsumed; /* The number of frames that have been consumed in the cache. Used for determining the next valid frame. */ ma_uint64 inputCacheConsumed; /* The number of frames that have been consumed in the cache. Used for determining the next valid frame. */
ma_uint64 inputCacheRemaining; /* The number of valid frames remaining in the cahce. */ ma_uint64 inputCacheRemaining; /* The number of valid frames remaining in the cache. */
ma_allocation_callbacks allocationCallbacks; ma_allocation_callbacks allocationCallbacks;
union union
{ {
...@@ -6274,7 +6293,7 @@ This is not thread safe without your own synchronization. ...@@ -6274,7 +6293,7 @@ This is not thread safe without your own synchronization.
MA_API ma_result ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); MA_API ma_result ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead);
/* /*
Seeks to a PCM frame based on it's absolute index. Seeks to a PCM frame based on its absolute index.
This is not thread safe without your own synchronization. This is not thread safe without your own synchronization.
*/ */
...@@ -6537,7 +6556,8 @@ typedef enum ...@@ -6537,7 +6556,8 @@ typedef enum
MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE = 0x00000002, /* Decode data before storing in memory. When set, decoding is done at the resource manager level rather than the mixing thread. Results in faster mixing, but higher memory usage. */ MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE = 0x00000002, /* Decode data before storing in memory. When set, decoding is done at the resource manager level rather than the mixing thread. Results in faster mixing, but higher memory usage. */
MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC = 0x00000004, /* When set, the resource manager will load the data source asynchronously. */ MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC = 0x00000004, /* When set, the resource manager will load the data source asynchronously. */
MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT = 0x00000008, /* When set, waits for initialization of the underlying data source before returning from ma_resource_manager_data_source_init(). */ MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT = 0x00000008, /* When set, waits for initialization of the underlying data source before returning from ma_resource_manager_data_source_init(). */
MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH = 0x00000010 /* Gives the resource manager a hint that the length of the data source is unknown and calling `ma_data_source_get_length_in_pcm_frames()` should be avoided. */ MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH = 0x00000010, /* Gives the resource manager a hint that the length of the data source is unknown and calling `ma_data_source_get_length_in_pcm_frames()` should be avoided. */
MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING = 0x00000020 /* When set, configures the data source to loop by default. */
} ma_resource_manager_data_source_flags; } ma_resource_manager_data_source_flags;
...@@ -6605,8 +6625,8 @@ typedef struct ...@@ -6605,8 +6625,8 @@ typedef struct
ma_uint64 rangeEndInPCMFrames; ma_uint64 rangeEndInPCMFrames;
ma_uint64 loopPointBegInPCMFrames; ma_uint64 loopPointBegInPCMFrames;
ma_uint64 loopPointEndInPCMFrames; ma_uint64 loopPointEndInPCMFrames;
ma_bool32 isLooping;
ma_uint32 flags; ma_uint32 flags;
ma_bool32 isLooping; /* Deprecated. Use the MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING flag in `flags` instead. */
} ma_resource_manager_data_source_config; } ma_resource_manager_data_source_config;
MA_API ma_resource_manager_data_source_config ma_resource_manager_data_source_config_init(void); MA_API ma_resource_manager_data_source_config ma_resource_manager_data_source_config_init(void);
...@@ -6849,6 +6869,16 @@ Node Graph ...@@ -6849,6 +6869,16 @@ Node Graph
/* Use this when the bus count is determined by the node instance rather than the vtable. */ /* Use this when the bus count is determined by the node instance rather than the vtable. */
#define MA_NODE_BUS_COUNT_UNKNOWN 255 #define MA_NODE_BUS_COUNT_UNKNOWN 255
/* For some internal memory management of ma_node_graph. */
typedef struct
{
size_t offset;
size_t sizeInBytes;
unsigned char _data[1];
} ma_stack;
typedef struct ma_node_graph ma_node_graph; typedef struct ma_node_graph ma_node_graph;
typedef void ma_node; typedef void ma_node;
...@@ -6888,7 +6918,7 @@ typedef struct ...@@ -6888,7 +6918,7 @@ typedef struct
void (* onProcess)(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut); void (* onProcess)(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut);
/* /*
A callback for retrieving the number of a input frames that are required to output the A callback for retrieving the number of input frames that are required to output the
specified number of output frames. You would only want to implement this when the node performs specified number of output frames. You would only want to implement this when the node performs
resampling. This is optional, even for nodes that perform resampling, but it does offer a resampling. This is optional, even for nodes that perform resampling, but it does offer a
small reduction in latency as it allows miniaudio to calculate the exact number of input frames small reduction in latency as it allows miniaudio to calculate the exact number of input frames
...@@ -6973,10 +7003,14 @@ typedef struct ma_node_base ma_node_base; ...@@ -6973,10 +7003,14 @@ typedef struct ma_node_base ma_node_base;
struct ma_node_base struct ma_node_base
{ {
/* These variables are set once at startup. */ /* These variables are set once at startup. */
ma_node_graph* pNodeGraph; /* The graph this node belongs to. */ ma_node_graph* pNodeGraph; /* The graph this node belongs to. */
const ma_node_vtable* vtable; const ma_node_vtable* vtable;
float* pCachedData; /* Allocated on the heap. Fixed size. Needs to be stored on the heap because reading from output buses is done in separate function calls. */ ma_uint32 inputBusCount;
ma_uint16 cachedDataCapInFramesPerBus; /* The capacity of the input data cache in frames, per bus. */ ma_uint32 outputBusCount;
ma_node_input_bus* pInputBuses;
ma_node_output_bus* pOutputBuses;
float* pCachedData; /* Allocated on the heap. Fixed size. Needs to be stored on the heap because reading from output buses is done in separate function calls. */
ma_uint16 cachedDataCapInFramesPerBus; /* The capacity of the input data cache in frames, per bus. */
/* These variables are read and written only from the audio thread. */ /* These variables are read and written only from the audio thread. */
ma_uint16 cachedFrameCountOut; ma_uint16 cachedFrameCountOut;
...@@ -6984,13 +7018,9 @@ struct ma_node_base ...@@ -6984,13 +7018,9 @@ struct ma_node_base
ma_uint16 consumedFrameCountIn; ma_uint16 consumedFrameCountIn;
/* These variables are read and written between different threads. */ /* These variables are read and written between different threads. */
MA_ATOMIC(4, ma_node_state) state; /* When set to stopped, nothing will be read, regardless of the times in stateTimes. */ MA_ATOMIC(4, ma_node_state) state; /* When set to stopped, nothing will be read, regardless of the times in stateTimes. */
MA_ATOMIC(8, ma_uint64) stateTimes[2]; /* Indexed by ma_node_state. Specifies the time based on the global clock that a node should be considered to be in the relevant state. */ MA_ATOMIC(8, ma_uint64) stateTimes[2]; /* Indexed by ma_node_state. Specifies the time based on the global clock that a node should be considered to be in the relevant state. */
MA_ATOMIC(8, ma_uint64) localTime; /* The node's local clock. This is just a running sum of the number of output frames that have been processed. Can be modified by any thread with `ma_node_set_time()`. */ MA_ATOMIC(8, ma_uint64) localTime; /* The node's local clock. This is just a running sum of the number of output frames that have been processed. Can be modified by any thread with `ma_node_set_time()`. */
ma_uint32 inputBusCount;
ma_uint32 outputBusCount;
ma_node_input_bus* pInputBuses;
ma_node_output_bus* pOutputBuses;
/* Memory management. */ /* Memory management. */
ma_node_input_bus _inputBuses[MA_MAX_NODE_LOCAL_BUS_COUNT]; ma_node_input_bus _inputBuses[MA_MAX_NODE_LOCAL_BUS_COUNT];
...@@ -7026,7 +7056,8 @@ MA_API ma_result ma_node_set_time(ma_node* pNode, ma_uint64 localTime); ...@@ -7026,7 +7056,8 @@ MA_API ma_result ma_node_set_time(ma_node* pNode, ma_uint64 localTime);
typedef struct typedef struct
{ {
ma_uint32 channels; ma_uint32 channels;
ma_uint16 nodeCacheCapInFrames; ma_uint32 processingSizeInFrames; /* This is the preferred processing size for node processing callbacks unless overridden by a node itself. Can be 0 in which case it will be based on the frame count passed into ma_node_graph_read_pcm_frames(), but will not be well defined. */
size_t preMixStackSizeInBytes; /* Defaults to 512KB per channel. Reducing this will save memory, but the depth of your node graph will be more restricted. */
} ma_node_graph_config; } ma_node_graph_config;
MA_API ma_node_graph_config ma_node_graph_config_init(ma_uint32 channels); MA_API ma_node_graph_config ma_node_graph_config_init(ma_uint32 channels);
...@@ -7037,10 +7068,15 @@ struct ma_node_graph ...@@ -7037,10 +7068,15 @@ struct ma_node_graph
/* Immutable. */ /* Immutable. */
ma_node_base base; /* The node graph itself is a node so it can be connected as an input to different node graph. This has zero inputs and calls ma_node_graph_read_pcm_frames() to generate it's output. */ ma_node_base base; /* The node graph itself is a node so it can be connected as an input to different node graph. This has zero inputs and calls ma_node_graph_read_pcm_frames() to generate it's output. */
ma_node_base endpoint; /* Special node that all nodes eventually connect to. Data is read from this node in ma_node_graph_read_pcm_frames(). */ ma_node_base endpoint; /* Special node that all nodes eventually connect to. Data is read from this node in ma_node_graph_read_pcm_frames(). */
ma_uint16 nodeCacheCapInFrames; float* pProcessingCache; /* This will be allocated when processingSizeInFrames is non-zero. This is needed because ma_node_graph_read_pcm_frames() can be called with a variable number of frames, and we may need to do some buffering in situations where the caller requests a frame count that's not a multiple of processingSizeInFrames. */
ma_uint32 processingCacheFramesRemaining;
ma_uint32 processingSizeInFrames;
/* Read and written by multiple threads. */ /* Read and written by multiple threads. */
MA_ATOMIC(4, ma_bool32) isReading; MA_ATOMIC(4, ma_bool32) isReading;
/* Modified only by the audio thread. */
ma_stack* pPreMixStack;
}; };
MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node_graph* pNodeGraph); MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node_graph* pNodeGraph);
...@@ -7325,6 +7361,7 @@ typedef enum ...@@ -7325,6 +7361,7 @@ typedef enum
MA_SOUND_FLAG_ASYNC = 0x00000004, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC */ MA_SOUND_FLAG_ASYNC = 0x00000004, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC */
MA_SOUND_FLAG_WAIT_INIT = 0x00000008, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT */ MA_SOUND_FLAG_WAIT_INIT = 0x00000008, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT */
MA_SOUND_FLAG_UNKNOWN_LENGTH = 0x00000010, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH */ MA_SOUND_FLAG_UNKNOWN_LENGTH = 0x00000010, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH */
MA_SOUND_FLAG_LOOPING = 0x00000020, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING */
/* ma_sound specific flags. */ /* ma_sound specific flags. */
MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT = 0x00001000, /* Do not attach to the endpoint by default. Useful for when setting up nodes in a complex graph system. */ MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT = 0x00001000, /* Do not attach to the endpoint by default. Useful for when setting up nodes in a complex graph system. */
...@@ -7364,7 +7401,7 @@ MA_API ma_engine_node_config ma_engine_node_config_init(ma_engine* pEngine, ma_e ...@@ -7364,7 +7401,7 @@ MA_API ma_engine_node_config ma_engine_node_config_init(ma_engine* pEngine, ma_e
/* Base node object for both ma_sound and ma_sound_group. */ /* Base node object for both ma_sound and ma_sound_group. */
typedef struct typedef struct
{ {
ma_node_base baseNode; /* Must be the first member for compatiblity with the ma_node API. */ ma_node_base baseNode; /* Must be the first member for compatibility with the ma_node API. */
ma_engine* pEngine; /* A pointer to the engine. Set based on the value from the config. */ ma_engine* pEngine; /* A pointer to the engine. Set based on the value from the config. */
ma_uint32 sampleRate; /* The sample rate of the input data. For sounds backed by a data source, this will be the data source's sample rate. Otherwise it'll be the engine's sample rate. */ ma_uint32 sampleRate; /* The sample rate of the input data. For sounds backed by a data source, this will be the data source's sample rate. Otherwise it'll be the engine's sample rate. */
ma_uint32 volumeSmoothTimeInPCMFrames; ma_uint32 volumeSmoothTimeInPCMFrames;
...@@ -7424,13 +7461,13 @@ typedef struct ...@@ -7424,13 +7461,13 @@ typedef struct
ma_uint64 rangeEndInPCMFrames; ma_uint64 rangeEndInPCMFrames;
ma_uint64 loopPointBegInPCMFrames; ma_uint64 loopPointBegInPCMFrames;
ma_uint64 loopPointEndInPCMFrames; ma_uint64 loopPointEndInPCMFrames;
ma_bool32 isLooping;
ma_sound_end_proc endCallback; /* Fired when the sound reaches the end. Will be fired from the audio thread. Do not restart, uninitialize or otherwise change the state of the sound from here. Instead fire an event or set a variable to indicate to a different thread to change the start of the sound. Will not be fired in response to a scheduled stop with ma_sound_set_stop_time_*(). */ ma_sound_end_proc endCallback; /* Fired when the sound reaches the end. Will be fired from the audio thread. Do not restart, uninitialize or otherwise change the state of the sound from here. Instead fire an event or set a variable to indicate to a different thread to change the start of the sound. Will not be fired in response to a scheduled stop with ma_sound_set_stop_time_*(). */
void* pEndCallbackUserData; void* pEndCallbackUserData;
#ifndef MA_NO_RESOURCE_MANAGER #ifndef MA_NO_RESOURCE_MANAGER
ma_resource_manager_pipeline_notifications initNotifications; ma_resource_manager_pipeline_notifications initNotifications;
#endif #endif
ma_fence* pDoneFence; /* Deprecated. Use initNotifications instead. Released when the resource manager has finished decoding the entire sound. Not used with streams. */ ma_fence* pDoneFence; /* Deprecated. Use initNotifications instead. Released when the resource manager has finished decoding the entire sound. Not used with streams. */
ma_bool32 isLooping; /* Deprecated. Use the MA_SOUND_FLAG_LOOPING flag in `flags` instead. */
} ma_sound_config; } ma_sound_config;
MA_API ma_sound_config ma_sound_config_init(void); /* Deprecated. Will be removed in version 0.12. Use ma_sound_config_2() instead. */ MA_API ma_sound_config ma_sound_config_init(void); /* Deprecated. Will be removed in version 0.12. Use ma_sound_config_2() instead. */
...@@ -7494,6 +7531,7 @@ typedef struct ...@@ -7494,6 +7531,7 @@ typedef struct
ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. If set to 0, will use gainSmoothTimeInMilliseconds. */ ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. If set to 0, will use gainSmoothTimeInMilliseconds. */
ma_uint32 gainSmoothTimeInMilliseconds; /* When set to 0, gainSmoothTimeInFrames will be used. If both are set to 0, a default value will be used. */ ma_uint32 gainSmoothTimeInMilliseconds; /* When set to 0, gainSmoothTimeInFrames will be used. If both are set to 0, a default value will be used. */
ma_uint32 defaultVolumeSmoothTimeInPCMFrames; /* Defaults to 0. Controls the default amount of smoothing to apply to volume changes to sounds. High values means more smoothing at the expense of high latency (will take longer to reach the new volume). */ ma_uint32 defaultVolumeSmoothTimeInPCMFrames; /* Defaults to 0. Controls the default amount of smoothing to apply to volume changes to sounds. High values means more smoothing at the expense of high latency (will take longer to reach the new volume). */
ma_uint32 preMixStackSizeInBytes; /* A stack is used for internal processing in the node graph. This allows you to configure the size of this stack. Smaller values will reduce the maximum depth of your node graph. You should rarely need to modify this. */
ma_allocation_callbacks allocationCallbacks; ma_allocation_callbacks allocationCallbacks;
ma_bool32 noAutoStart; /* When set to true, requires an explicit call to ma_engine_start(). This is false by default, meaning the engine will be started automatically in ma_engine_init(). */ ma_bool32 noAutoStart; /* When set to true, requires an explicit call to ma_engine_start(). This is false by default, meaning the engine will be started automatically in ma_engine_init(). */
ma_bool32 noDevice; /* When set to true, don't create a default device. ma_engine_read_pcm_frames() can be called manually to read data. */ ma_bool32 noDevice; /* When set to true, don't create a default device. ma_engine_read_pcm_frames() can be called manually to read data. */
...@@ -7508,12 +7546,12 @@ MA_API ma_engine_config ma_engine_config_init(void); ...@@ -7508,12 +7546,12 @@ MA_API ma_engine_config ma_engine_config_init(void);
struct ma_engine struct ma_engine
{ {
ma_node_graph nodeGraph; /* An engine is a node graph. It should be able to be plugged into any ma_node_graph API (with a cast) which means this must be the first member of this struct. */ ma_node_graph nodeGraph; /* An engine is a node graph. It should be able to be plugged into any ma_node_graph API (with a cast) which means this must be the first member of this struct. */
#if !defined(MA_NO_RESOURCE_MANAGER) #if !defined(MA_NO_RESOURCE_MANAGER)
ma_resource_manager* pResourceManager; ma_resource_manager* pResourceManager;
#endif #endif
#if !defined(MA_NO_DEVICE_IO) #if !defined(MA_NO_DEVICE_IO)
ma_device* pDevice; /* Optionally set via the config, otherwise allocated by the engine in ma_engine_init(). */ ma_device* pDevice; /* Optionally set via the config, otherwise allocated by the engine in ma_engine_init(). */
#endif #endif
ma_log* pLog; ma_log* pLog;
ma_uint32 sampleRate; ma_uint32 sampleRate;
...@@ -7522,10 +7560,10 @@ struct ma_engine ...@@ -7522,10 +7560,10 @@ struct ma_engine
ma_allocation_callbacks allocationCallbacks; ma_allocation_callbacks allocationCallbacks;
ma_bool8 ownsResourceManager; ma_bool8 ownsResourceManager;
ma_bool8 ownsDevice; ma_bool8 ownsDevice;
ma_spinlock inlinedSoundLock; /* For synchronizing access so the inlined sound list. */ ma_spinlock inlinedSoundLock; /* For synchronizing access to the inlined sound list. */
ma_sound_inlined* pInlinedSoundHead; /* The first inlined sound. Inlined sounds are tracked in a linked list. */ ma_sound_inlined* pInlinedSoundHead; /* The first inlined sound. Inlined sounds are tracked in a linked list. */
MA_ATOMIC(4, ma_uint32) inlinedSoundCount; /* The total number of allocated inlined sound objects. Used for debugging. */ MA_ATOMIC(4, ma_uint32) inlinedSoundCount; /* The total number of allocated inlined sound objects. Used for debugging. */
ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. */ ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. */
ma_uint32 defaultVolumeSmoothTimeInPCMFrames; ma_uint32 defaultVolumeSmoothTimeInPCMFrames;
ma_mono_expansion_mode monoExpansionMode; ma_mono_expansion_mode monoExpansionMode;
ma_engine_process_proc onProcess; ma_engine_process_proc onProcess;
...@@ -7650,6 +7688,7 @@ MA_API void ma_sound_set_looping(ma_sound* pSound, ma_bool32 isLooping); ...@@ -7650,6 +7688,7 @@ 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_is_looping(const ma_sound* pSound);
MA_API ma_bool32 ma_sound_at_end(const ma_sound* pSound); MA_API ma_bool32 ma_sound_at_end(const ma_sound* pSound);
MA_API ma_result ma_sound_seek_to_pcm_frame(ma_sound* pSound, ma_uint64 frameIndex); /* Just a wrapper around ma_data_source_seek_to_pcm_frame(). */ MA_API ma_result ma_sound_seek_to_pcm_frame(ma_sound* pSound, ma_uint64 frameIndex); /* Just a wrapper around ma_data_source_seek_to_pcm_frame(). */
MA_API ma_result ma_sound_seek_to_second(ma_sound* pSound, float seekPointInSeconds); /* Abstraction to ma_sound_seek_to_pcm_frame() */
MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap);
MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64* pCursor); MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64* pCursor);
MA_API ma_result ma_sound_get_length_in_pcm_frames(ma_sound* pSound, ma_uint64* pLength); MA_API ma_result ma_sound_get_length_in_pcm_frames(ma_sound* pSound, ma_uint64* pLength);
...@@ -7752,7 +7791,7 @@ For more information, please refer to <http://unlicense.org/> ...@@ -7752,7 +7791,7 @@ For more information, please refer to <http://unlicense.org/>
=============================================================================== ===============================================================================
ALTERNATIVE 2 - MIT No Attribution ALTERNATIVE 2 - MIT No Attribution
=============================================================================== ===============================================================================
Copyright 2023 David Reid Copyright 2025 David Reid
Permission is hereby granted, free of charge, to any person obtaining a copy of Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in this software and associated documentation files (the "Software"), to deal in
......
/* /*
Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file. Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file.
miniaudio - v0.11.22 - TBD miniaudio - v0.11.22 - 2025-02-24
David Reid - mackron@gmail.com David Reid - mackron@gmail.com
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment