Commit ea3f7f51 authored by David Reid's avatar David Reid

Add ma_audio_buffer API.

The ma_audio_buffer object is used for storing raw audio data in memory
and reading from it like any other data source. It supports flexible
memory management, reading, seeking, memory mapping and looping.

See documentation under "Audio Buffers" for a detailed description.
parent d836c0b5
...@@ -1318,6 +1318,81 @@ Below are the supported noise types. ...@@ -1318,6 +1318,81 @@ Below are the supported noise types.
Audio Buffers
=============
miniaudio supports reading from a buffer of raw audio data via the `ma_audio_buffer` API. This can read from both memory that's managed by the application, but
can also handle the memory management for you internally. The way memory is managed is flexible and should support most use cases.
Audio buffers are initialised using the standard configuration system used everywhere in miniaudio:
```c
ma_audio_buffer_config config = ma_audio_buffer_config_init(format, channels, sizeInFrames, pExistingData, &allocationCallbacks);
ma_audio_buffer buffer;
result = ma_audio_buffer_init(&config, &buffer);
if (result != MA_SUCCESS) {
// Error.
}
...
ma_audio_buffer_uninit(&buffer);
```
In the example above, the memory pointed to by `pExistingData` will _not_ be copied which is how an application can handle memory allocations themselves. If
you would rather make a copy of the data, use `ma_audio_buffer_init_copy()`. To uninitialize the buffer, use `ma_audio_buffer_uninit()`.
Sometimes it can be convenient to allocate the memory for the `ma_audio_buffer` structure _and_ the raw audio data in a contiguous block of memory. That is,
the raw audio data will be located immediately after the `ma_audio_buffer` structure. To do this, use `ma_audio_buffer_alloc_and_init()`:
```c
ma_audio_buffer* pBuffer
result = ma_audio_buffer_alloc_and_init(&config, &pBuffer);
if (result != MA_SUCCESS) {
// Error
}
...
ma_audio_buffer_uninit_and_free(&buffer);
```
If you initialize the buffer with `ma_audio_buffer_alloc_and_init()` you should uninitialize it with `ma_audio_buffer_uninit_and_free()`.
An audio buffer has a playback cursor just like a decoder. As you read frames from the buffer, the cursor moves forward. It does not automatically loop back to
the start. To do this, you should inspect the number of frames returned by `ma_audio_buffer_read_pcm_frames()` to determine if the end has been reached, which
you can know by comparing it with the requested frame count you specified when you called the function. If the return value is less it means the end has been
reached. In this case you can seem back to the start with `ma_audio_buffer_seek_to_pcm_frame(pAudioBuffer, 0)`. Below is an example for reading data from an
audio buffer.
```c
ma_uint64 framesRead = ma_audio_buffer_read_pcm_frames(pAudioBuffer, pFramesOut, desiredFrameCount, isLooping);
if (framesRead < desiredFrameCount) {
// If not looping, this means the end has been reached. This should never happen in looping mode with valid input.
}
```
Sometimes you may want to avoid the cost of data movement between the internal buffer and the output buffer as it's just a copy operation. Instead you can use
memory mapping to retrieve a pointer to a segment of data:
```c
void* pMappedFrames;
ma_uint64 frameCount = frameCountToTryMapping;
ma_result result = ma_audio_buffer_map(pAudioBuffer, &pMappedFrames, &frameCount);
if (result == MA_SUCCESS) {
// Map was successful. The value in frameCount will be how many frames were _actually_ mapped, which may be less due to the end of the buffer being reached.
ma_copy_pcm_frames(pFramesOut, pMappedFrames, frameCount, pAudioBuffer->format, pAudioBuffer->channels);
// You must unmap the buffer.
ma_audio_buffer_unmap(pAudioBuffer, frameCount);
}
```
When you use memory mapping, the read cursor is increment by the frame count passed in to `ma_audio_buffer_unmap()`. If you decide not to process every frame
you can pass in a value smaller than the value returned by `ma_audio_buffer_map()`. The disadvantage to using memory mapping is that it does not handle looping
for you. You can determine if the buffer is at the end for the purpose of looping with `ma_audio_buffer_at_end()`.
Ring Buffers Ring Buffers
============ ============
miniaudio supports lock free (single producer, single consumer) ring buffers which are exposed via the `ma_rb` and `ma_pcm_rb` APIs. The `ma_rb` API operates miniaudio supports lock free (single producer, single consumer) ring buffers which are exposed via the `ma_rb` and `ma_pcm_rb` APIs. The `ma_rb` API operates
...@@ -5247,6 +5322,56 @@ MA_API float ma_gain_db_to_factor(float gain); ...@@ -5247,6 +5322,56 @@ MA_API float ma_gain_db_to_factor(float gain);
#endif /* MA_NO_DEVICE_IO */ #endif /* MA_NO_DEVICE_IO */
typedef void ma_data_source;
typedef struct
{
ma_uint64 (* onRead)(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount);
ma_result (* onSeek)(ma_data_source* pDataSource, ma_uint64 frameIndex);
ma_result (* onGetDataFormat)(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels);
} ma_data_source_callbacks;
MA_API ma_uint64 ma_data_source_read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount);
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_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels);
typedef struct
{
ma_format format;
ma_uint32 channels;
ma_uint64 sizeInFrames;
const void* pData; /* If set to NULL, will allocate a block of memory for you. */
ma_allocation_callbacks allocationCallbacks;
} ma_audio_buffer_config;
MA_API ma_audio_buffer_config ma_audio_buffer_config_init(ma_format format, ma_uint32 channels, ma_uint64 sizeInFrames, const void* pData, const ma_allocation_callbacks* pAllocationCallbacks);
typedef struct
{
ma_data_source_callbacks ds;
ma_format format;
ma_uint32 channels;
ma_uint64 cursor;
ma_uint64 sizeInFrames;
const void* pData;
ma_allocation_callbacks allocationCallbacks;
ma_bool32 ownsData; /* Used to control whether or not miniaudio owns the data buffer. If set to true, pData will be freed in ma_audio_buffer_uninit(). */
ma_uint8 _pExtraData[1]; /* For allocating a buffer with the memory located directly after the other memory of the structure. */
} ma_audio_buffer;
MA_API ma_result ma_audio_buffer_init(const ma_audio_buffer_config* pConfig, ma_audio_buffer* pAudioBuffer);
MA_API ma_result ma_audio_buffer_init_copy(const ma_audio_buffer_config* pConfig, ma_audio_buffer* pAudioBuffer);
MA_API ma_result ma_audio_buffer_alloc_and_init(const ma_audio_buffer_config* pConfig, ma_audio_buffer** ppAudioBuffer); /* Always copies the data. Doesn't make sense to use this otherwise. Use ma_audio_buffer_uninit_and_free() to uninit. */
MA_API void ma_audio_buffer_uninit(ma_audio_buffer* pAudioBuffer);
MA_API void ma_audio_buffer_uninit_and_free(ma_audio_buffer* pAudioBuffer);
MA_API ma_uint64 ma_audio_buffer_read_pcm_frames(ma_audio_buffer* pAudioBuffer, void* pFramesOut, ma_uint64 frameCount, ma_bool32 loop);
MA_API ma_result ma_audio_buffer_seek_to_pcm_frame(ma_audio_buffer* pAudioBuffer, ma_uint64 frameIndex);
MA_API ma_result ma_audio_buffer_map(ma_audio_buffer* pAudioBuffer, void** ppFramesOut, ma_uint64* pFrameCount);
MA_API ma_result ma_audio_buffer_unmap(ma_audio_buffer* pAudioBuffer, ma_uint64 frameCount);
MA_API ma_result ma_audio_buffer_at_end(ma_audio_buffer* pAudioBuffer);
#if !defined(MA_NO_DECODING) || !defined(MA_NO_ENCODING) #if !defined(MA_NO_DECODING) || !defined(MA_NO_ENCODING)
typedef enum typedef enum
{ {
...@@ -5304,6 +5429,7 @@ typedef struct ...@@ -5304,6 +5429,7 @@ typedef struct
struct ma_decoder struct ma_decoder
{ {
ma_data_source_callbacks ds;
ma_decoder_read_proc onRead; ma_decoder_read_proc onRead;
ma_decoder_seek_proc onSeek; ma_decoder_seek_proc onSeek;
void* pUserData; void* pUserData;
...@@ -5479,6 +5605,7 @@ MA_API ma_waveform_config ma_waveform_config_init(ma_format format, ma_uint32 ch ...@@ -5479,6 +5605,7 @@ MA_API ma_waveform_config ma_waveform_config_init(ma_format format, ma_uint32 ch
typedef struct typedef struct
{ {
ma_data_source_callbacks ds;
ma_waveform_config config; ma_waveform_config config;
double advance; double advance;
double time; double time;
...@@ -5486,6 +5613,7 @@ typedef struct ...@@ -5486,6 +5613,7 @@ typedef struct
MA_API ma_result ma_waveform_init(const ma_waveform_config* pConfig, ma_waveform* pWaveform); MA_API ma_result ma_waveform_init(const ma_waveform_config* pConfig, ma_waveform* pWaveform);
MA_API ma_uint64 ma_waveform_read_pcm_frames(ma_waveform* pWaveform, void* pFramesOut, ma_uint64 frameCount); MA_API ma_uint64 ma_waveform_read_pcm_frames(ma_waveform* pWaveform, void* pFramesOut, ma_uint64 frameCount);
MA_API ma_result ma_waveform_seek_to_pcm_frame(ma_waveform* pWaveform, ma_uint64 frameIndex);
MA_API ma_result ma_waveform_set_amplitude(ma_waveform* pWaveform, double amplitude); MA_API ma_result ma_waveform_set_amplitude(ma_waveform* pWaveform, double amplitude);
MA_API ma_result ma_waveform_set_frequency(ma_waveform* pWaveform, double frequency); MA_API ma_result ma_waveform_set_frequency(ma_waveform* pWaveform, double frequency);
MA_API ma_result ma_waveform_set_sample_rate(ma_waveform* pWaveform, ma_uint32 sampleRate); MA_API ma_result ma_waveform_set_sample_rate(ma_waveform* pWaveform, ma_uint32 sampleRate);
...@@ -5513,6 +5641,7 @@ MA_API ma_noise_config ma_noise_config_init(ma_format format, ma_uint32 channels ...@@ -5513,6 +5641,7 @@ MA_API ma_noise_config ma_noise_config_init(ma_format format, ma_uint32 channels
typedef struct typedef struct
{ {
ma_data_source_callbacks ds;
ma_noise_config config; ma_noise_config config;
ma_lcg lcg; ma_lcg lcg;
union union
...@@ -39755,6 +39884,347 @@ MA_API ma_uint32 ma_get_bytes_per_sample(ma_format format) ...@@ -39755,6 +39884,347 @@ MA_API ma_uint32 ma_get_bytes_per_sample(ma_format format)
} }
MA_API ma_uint64 ma_data_source_read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount)
{
ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource;
if (pCallbacks == NULL || pCallbacks->onRead == NULL) {
return 0;
}
return pCallbacks->onRead(pDataSource, pFramesOut, frameCount);
}
MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex)
{
ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource;
if (pCallbacks == NULL || pCallbacks->onSeek == NULL) {
return MA_INVALID_ARGS;
}
return pCallbacks->onSeek(pDataSource, frameIndex);
}
MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels)
{
ma_result result;
ma_format format;
ma_uint32 channels;
ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource;
if (pCallbacks == NULL || pCallbacks->onGetDataFormat == NULL) {
return MA_INVALID_ARGS;
}
result = pCallbacks->onGetDataFormat(pDataSource, &format, &channels);
if (result != MA_SUCCESS) {
return result;
}
if (pFormat != NULL) {
*pFormat = format;
}
if (pChannels != NULL) {
*pChannels = channels;
}
return MA_SUCCESS;
}
MA_API ma_audio_buffer_config ma_audio_buffer_config_init(ma_format format, ma_uint32 channels, ma_uint64 sizeInFrames, const void* pData, const ma_allocation_callbacks* pAllocationCallbacks)
{
ma_audio_buffer_config config;
MA_ZERO_OBJECT(&config);
config.format = format;
config.channels = channels;
config.sizeInFrames = sizeInFrames;
config.pData = pData;
ma_allocation_callbacks_init_copy(&config.allocationCallbacks, pAllocationCallbacks);
return config;
}
static ma_uint64 ma_audio_buffer__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount)
{
return ma_audio_buffer_read_pcm_frames((ma_audio_buffer*)pDataSource, pFramesOut, frameCount, MA_FALSE);
}
static ma_result ma_audio_buffer__data_source_on_seek(ma_data_source* pDataSource, ma_uint64 frameIndex)
{
return ma_audio_buffer_seek_to_pcm_frame((ma_audio_buffer*)pDataSource, frameIndex);
}
static ma_result ma_audio_buffer__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels)
{
ma_audio_buffer* pAudioBuffer = (ma_audio_buffer*)pDataSource;
*pFormat = pAudioBuffer->format;
*pChannels = pAudioBuffer->channels;
return MA_SUCCESS;
}
static ma_result ma_audio_buffer_init_ex(const ma_audio_buffer_config* pConfig, ma_bool32 doCopy, ma_audio_buffer* pAudioBuffer)
{
if (pAudioBuffer == NULL) {
return MA_INVALID_ARGS;
}
MA_ZERO_MEMORY(pAudioBuffer, sizeof(*pAudioBuffer) - sizeof(pAudioBuffer->_pExtraData)); /* Safety. Don't overwrite the extra data. */
if (pConfig == NULL) {
return MA_INVALID_ARGS;
}
if (pConfig->sizeInFrames == 0) {
return MA_INVALID_ARGS; /* Not allowing buffer sizes of 0 frames. */
}
pAudioBuffer->ds.onRead = ma_audio_buffer__data_source_on_read;
pAudioBuffer->ds.onSeek = ma_audio_buffer__data_source_on_seek;
pAudioBuffer->ds.onGetDataFormat = ma_audio_buffer__data_source_on_get_data_format;
pAudioBuffer->format = pConfig->format;
pAudioBuffer->channels = pConfig->channels;
pAudioBuffer->cursor = 0;
pAudioBuffer->sizeInFrames = pConfig->sizeInFrames;
pAudioBuffer->pData = NULL; /* Set properly later. */
ma_allocation_callbacks_init_copy(&pAudioBuffer->allocationCallbacks, &pConfig->allocationCallbacks);
if (doCopy) {
ma_uint64 allocationSizeInBytes;
void* pData;
allocationSizeInBytes = pAudioBuffer->sizeInFrames * ma_get_bytes_per_frame(pAudioBuffer->format, pAudioBuffer->channels);
if (allocationSizeInBytes > MA_SIZE_MAX) {
return MA_OUT_OF_MEMORY; /* Too big. */
}
pData = ma__malloc_from_callbacks((size_t)allocationSizeInBytes, &pAudioBuffer->allocationCallbacks); /* Safe cast to size_t. */
if (pData == NULL) {
return MA_OUT_OF_MEMORY;
}
if (pConfig->pData != NULL) {
ma_copy_pcm_frames(pData, pConfig->pData, pAudioBuffer->sizeInFrames, pAudioBuffer->format, pAudioBuffer->channels);
} else {
ma_silence_pcm_frames(pData, pAudioBuffer->sizeInFrames, pAudioBuffer->format, pAudioBuffer->channels);
}
pAudioBuffer->pData = pData;
pAudioBuffer->ownsData = MA_TRUE;
} else {
pAudioBuffer->pData = pConfig->pData;
pAudioBuffer->ownsData = MA_FALSE;
}
return MA_SUCCESS;
}
static void ma_audio_buffer_uninit_ex(ma_audio_buffer* pAudioBuffer, ma_bool32 doFree)
{
if (pAudioBuffer == NULL) {
return;
}
if (pAudioBuffer->ownsData && pAudioBuffer->pData != &pAudioBuffer->_pExtraData[0]) {
ma__free_from_callbacks((void*)pAudioBuffer->pData, &pAudioBuffer->allocationCallbacks); /* Naugty const cast, but OK in this case since we've guarded it with the ownsData check. */
}
if (doFree) {
ma_allocation_callbacks allocationCallbacks = pAudioBuffer->allocationCallbacks;
ma__free_from_callbacks(pAudioBuffer, &allocationCallbacks);
}
}
MA_API ma_result ma_audio_buffer_init(const ma_audio_buffer_config* pConfig, ma_audio_buffer* pAudioBuffer)
{
return ma_audio_buffer_init_ex(pConfig, MA_FALSE, pAudioBuffer);
}
MA_API ma_result ma_audio_buffer_init_copy(const ma_audio_buffer_config* pConfig, ma_audio_buffer* pAudioBuffer)
{
return ma_audio_buffer_init_ex(pConfig, MA_TRUE, pAudioBuffer);
}
MA_API ma_result ma_audio_buffer_alloc_and_init(const ma_audio_buffer_config* pConfig, ma_audio_buffer** ppAudioBuffer)
{
ma_result result;
ma_audio_buffer* pAudioBuffer;
ma_audio_buffer_config innerConfig; /* We'll be making some changes to the config, so need to make a copy. */
ma_uint64 allocationSizeInBytes;
if (ppAudioBuffer == NULL) {
return MA_INVALID_ARGS;
}
*ppAudioBuffer = NULL; /* Safety. */
if (pConfig == NULL) {
return MA_INVALID_ARGS;
}
innerConfig = *pConfig;
ma_allocation_callbacks_init_copy(&innerConfig.allocationCallbacks, &pConfig->allocationCallbacks);
allocationSizeInBytes = sizeof(*pAudioBuffer) - sizeof(pAudioBuffer->_pExtraData) + (pConfig->sizeInFrames * ma_get_bytes_per_frame(pConfig->format, pConfig->channels));
if (allocationSizeInBytes > MA_SIZE_MAX) {
return MA_OUT_OF_MEMORY; /* Too big. */
}
pAudioBuffer = ma__malloc_from_callbacks((size_t)allocationSizeInBytes, &innerConfig.allocationCallbacks); /* Safe cast to size_t. */
if (pAudioBuffer == NULL) {
return MA_OUT_OF_MEMORY;
}
if (pConfig->pData != NULL) {
ma_copy_pcm_frames(&pAudioBuffer->_pExtraData[0], pConfig->pData, pConfig->sizeInFrames, pConfig->format, pConfig->channels);
} else {
ma_silence_pcm_frames(&pAudioBuffer->_pExtraData[0], pConfig->sizeInFrames, pConfig->format, pConfig->channels);
}
innerConfig.pData = &pAudioBuffer->_pExtraData[0];
result = ma_audio_buffer_init_ex(&innerConfig, MA_FALSE, pAudioBuffer);
if (result != MA_SUCCESS) {
ma__free_from_callbacks(pAudioBuffer, &innerConfig.allocationCallbacks);
return result;
}
*ppAudioBuffer = pAudioBuffer;
return MA_SUCCESS;
}
MA_API void ma_audio_buffer_uninit(ma_audio_buffer* pAudioBuffer)
{
ma_audio_buffer_uninit_ex(pAudioBuffer, MA_FALSE);
}
MA_API void ma_audio_buffer_uninit_and_free(ma_audio_buffer* pAudioBuffer)
{
ma_audio_buffer_uninit_ex(pAudioBuffer, MA_TRUE);
}
MA_API ma_uint64 ma_audio_buffer_read_pcm_frames(ma_audio_buffer* pAudioBuffer, void* pFramesOut, ma_uint64 frameCount, ma_bool32 loop)
{
ma_uint64 totalFramesRead = 0;
if (pAudioBuffer == NULL) {
return 0;
}
if (frameCount == 0) {
return 0;
}
while (totalFramesRead < frameCount) {
ma_uint64 framesAvailable = pAudioBuffer->sizeInFrames - pAudioBuffer->cursor;
ma_uint64 framesRemaining = frameCount - totalFramesRead;
ma_uint64 framesToRead;
framesToRead = framesRemaining;
if (framesToRead > framesAvailable) {
framesToRead = framesAvailable;
}
if (pFramesOut != NULL) {
ma_copy_pcm_frames(pFramesOut, ma_offset_ptr(pAudioBuffer->pData, pAudioBuffer->cursor * ma_get_bytes_per_frame(pAudioBuffer->format, pAudioBuffer->channels)), frameCount, pAudioBuffer->format, pAudioBuffer->channels);
}
totalFramesRead += framesToRead;
pAudioBuffer->cursor += framesToRead;
if (pAudioBuffer->cursor == pAudioBuffer->sizeInFrames) {
if (loop) {
pAudioBuffer->cursor = 0;
} else {
break; /* We've reached the end and we're not looping. Done. */
}
}
MA_ASSERT(pAudioBuffer->cursor < pAudioBuffer->sizeInFrames);
}
return frameCount;
}
MA_API ma_result ma_audio_buffer_seek_to_pcm_frame(ma_audio_buffer* pAudioBuffer, ma_uint64 frameIndex)
{
if (pAudioBuffer == NULL) {
return MA_INVALID_ARGS;
}
if (frameIndex > pAudioBuffer->sizeInFrames) {
return MA_INVALID_ARGS;
}
pAudioBuffer->cursor = (size_t)frameIndex;
return MA_SUCCESS;
}
MA_API ma_result ma_audio_buffer_map(ma_audio_buffer* pAudioBuffer, void** ppFramesOut, ma_uint64* pFrameCount)
{
ma_uint64 framesAvailable;
ma_uint64 frameCount;
if (ppFramesOut != NULL) {
*ppFramesOut = NULL; /* Safety. */
}
if (pFrameCount != NULL) {
frameCount = *pFrameCount;
*pFrameCount = 0; /* Safety. */
}
if (pAudioBuffer == NULL || ppFramesOut == NULL || pFrameCount == NULL) {
return MA_INVALID_ARGS;
}
framesAvailable = pAudioBuffer->sizeInFrames - pAudioBuffer->cursor;
if (frameCount > framesAvailable) {
frameCount = framesAvailable;
}
*ppFramesOut = ma_offset_ptr(pAudioBuffer->pData, pAudioBuffer->cursor * ma_get_bytes_per_frame(pAudioBuffer->format, pAudioBuffer->channels));
*pFrameCount = frameCount;
return MA_SUCCESS;
}
MA_API ma_result ma_audio_buffer_unmap(ma_audio_buffer* pAudioBuffer, ma_uint64 frameCount)
{
ma_uint64 framesAvailable;
if (pAudioBuffer == NULL) {
return MA_INVALID_ARGS;
}
framesAvailable = pAudioBuffer->sizeInFrames - pAudioBuffer->cursor;
if (frameCount > framesAvailable) {
return MA_INVALID_ARGS; /* The frame count was too big. This should never happen in an unmapping. Need to make sure the caller is aware of this. */
}
pAudioBuffer->cursor += frameCount;
return MA_SUCCESS;
}
MA_API ma_result ma_audio_buffer_at_end(ma_audio_buffer* pAudioBuffer)
{
if (pAudioBuffer == NULL) {
return MA_FALSE;
}
return pAudioBuffer->cursor == pAudioBuffer->sizeInFrames;
}
/************************************************************************************************************************************************************** /**************************************************************************************************************************************************************
Decoding Decoding
...@@ -40662,6 +41132,26 @@ static ma_result ma_decoder__init_allocation_callbacks(const ma_decoder_config* ...@@ -40662,6 +41132,26 @@ static ma_result ma_decoder__init_allocation_callbacks(const ma_decoder_config*
} }
} }
static ma_uint64 ma_decoder__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount)
{
return ma_decoder_read_pcm_frames((ma_decoder*)pDataSource, pFramesOut, frameCount);
}
static ma_result ma_decoder__data_source_on_seek(ma_data_source* pDataSource, ma_uint64 frameIndex)
{
return ma_decoder_seek_to_pcm_frame((ma_decoder*)pDataSource, frameIndex);
}
static ma_result ma_decoder__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels)
{
ma_decoder* pDecoder = (ma_decoder*)pDataSource;
*pFormat = pDecoder->outputFormat;
*pChannels = pDecoder->outputChannels;
return MA_SUCCESS;
}
static ma_result ma_decoder__preinit(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) static ma_result ma_decoder__preinit(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder)
{ {
ma_result result; ma_result result;
...@@ -40678,6 +41168,10 @@ static ma_result ma_decoder__preinit(ma_decoder_read_proc onRead, ma_decoder_see ...@@ -40678,6 +41168,10 @@ static ma_result ma_decoder__preinit(ma_decoder_read_proc onRead, ma_decoder_see
return MA_INVALID_ARGS; return MA_INVALID_ARGS;
} }
pDecoder->ds.onRead = ma_decoder__data_source_on_read;
pDecoder->ds.onSeek = ma_decoder__data_source_on_seek;
pDecoder->ds.onGetDataFormat = ma_decoder__data_source_on_get_data_format;
pDecoder->onRead = onRead; pDecoder->onRead = onRead;
pDecoder->onSeek = onSeek; pDecoder->onSeek = onSeek;
pDecoder->pUserData = pUserData; pDecoder->pUserData = pUserData;
...@@ -42029,6 +42523,26 @@ MA_API ma_waveform_config ma_waveform_config_init(ma_format format, ma_uint32 ch ...@@ -42029,6 +42523,26 @@ MA_API ma_waveform_config ma_waveform_config_init(ma_format format, ma_uint32 ch
return config; return config;
} }
static ma_uint64 ma_waveform__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount)
{
return ma_waveform_read_pcm_frames((ma_waveform*)pDataSource, pFramesOut, frameCount);
}
static ma_result ma_waveform__data_source_on_seek(ma_data_source* pDataSource, ma_uint64 frameIndex)
{
return ma_waveform_seek_to_pcm_frame((ma_waveform*)pDataSource, frameIndex);
}
static ma_result ma_waveform__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels)
{
ma_waveform* pWaveform = (ma_waveform*)pDataSource;
*pFormat = pWaveform->config.format;
*pChannels = pWaveform->config.channels;
return MA_SUCCESS;
}
MA_API ma_result ma_waveform_init(const ma_waveform_config* pConfig, ma_waveform* pWaveform) MA_API ma_result ma_waveform_init(const ma_waveform_config* pConfig, ma_waveform* pWaveform)
{ {
if (pWaveform == NULL) { if (pWaveform == NULL) {
...@@ -42036,6 +42550,9 @@ MA_API ma_result ma_waveform_init(const ma_waveform_config* pConfig, ma_waveform ...@@ -42036,6 +42550,9 @@ MA_API ma_result ma_waveform_init(const ma_waveform_config* pConfig, ma_waveform
} }
MA_ZERO_OBJECT(pWaveform); MA_ZERO_OBJECT(pWaveform);
pWaveform->ds.onRead = ma_waveform__data_source_on_read;
pWaveform->ds.onSeek = ma_waveform__data_source_on_seek;
pWaveform->ds.onGetDataFormat = ma_waveform__data_source_on_get_data_format;
pWaveform->config = *pConfig; pWaveform->config = *pConfig;
pWaveform->advance = 1.0 / pWaveform->config.sampleRate; pWaveform->advance = 1.0 / pWaveform->config.sampleRate;
pWaveform->time = 0; pWaveform->time = 0;
...@@ -42341,6 +42858,17 @@ MA_API ma_uint64 ma_waveform_read_pcm_frames(ma_waveform* pWaveform, void* pFram ...@@ -42341,6 +42858,17 @@ MA_API ma_uint64 ma_waveform_read_pcm_frames(ma_waveform* pWaveform, void* pFram
return frameCount; return frameCount;
} }
MA_API ma_result ma_waveform_seek_to_pcm_frame(ma_waveform* pWaveform, ma_uint64 frameIndex)
{
if (pWaveform == NULL) {
return MA_INVALID_ARGS;
}
pWaveform->time = pWaveform->advance * frameIndex;
return MA_SUCCESS;
}
MA_API ma_noise_config ma_noise_config_init(ma_format format, ma_uint32 channels, ma_noise_type type, ma_int32 seed, double amplitude) MA_API ma_noise_config ma_noise_config_init(ma_format format, ma_uint32 channels, ma_noise_type type, ma_int32 seed, double amplitude)
{ {
...@@ -42360,6 +42888,30 @@ MA_API ma_noise_config ma_noise_config_init(ma_format format, ma_uint32 channels ...@@ -42360,6 +42888,30 @@ MA_API ma_noise_config ma_noise_config_init(ma_format format, ma_uint32 channels
return config; return config;
} }
static ma_uint64 ma_noise__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount)
{
return ma_noise_read_pcm_frames((ma_noise*)pDataSource, pFramesOut, frameCount);
}
static ma_result ma_noise__data_source_on_seek(ma_data_source* pDataSource, ma_uint64 frameIndex)
{
/* No-op. Just pretend to be successful. */
(void)pDataSource;
(void)frameIndex;
return MA_SUCCESS;
}
static ma_result ma_noise__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels)
{
ma_noise* pNoise = (ma_noise*)pDataSource;
*pFormat = pNoise->config.format;
*pChannels = pNoise->config.channels;
return MA_SUCCESS;
}
MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, ma_noise* pNoise) MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, ma_noise* pNoise)
{ {
if (pNoise == NULL) { if (pNoise == NULL) {
...@@ -42372,6 +42924,9 @@ MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, ma_noise* pNoise) ...@@ -42372,6 +42924,9 @@ MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, ma_noise* pNoise)
return MA_INVALID_ARGS; return MA_INVALID_ARGS;
} }
pNoise->ds.onRead = ma_noise__data_source_on_read;
pNoise->ds.onSeek = ma_noise__data_source_on_seek; /* <-- No-op for noise. */
pNoise->ds.onGetDataFormat = ma_noise__data_source_on_get_data_format;
pNoise->config = *pConfig; pNoise->config = *pConfig;
ma_lcg_seed(&pNoise->lcg, pConfig->seed); ma_lcg_seed(&pNoise->lcg, pConfig->seed);
...@@ -19,6 +19,7 @@ ma_noise g_noise; ...@@ -19,6 +19,7 @@ ma_noise g_noise;
ma_waveform g_waveform; ma_waveform g_waveform;
ma_decoder g_decoder; ma_decoder g_decoder;
ma_bool32 g_hasDecoder = MA_FALSE; ma_bool32 g_hasDecoder = MA_FALSE;
ma_audio_buffer* g_pAudioBuffer;
void data_callback(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) void data_callback(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount)
{ {
...@@ -46,6 +47,9 @@ void data_callback(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ...@@ -46,6 +47,9 @@ void data_callback(ma_device* pDevice, void* pFramesOut, const void* pFramesIn,
if (g_hasDecoder) { if (g_hasDecoder) {
ma_mixer_mix_decoder(&g_mixerEffects, &g_decoder, submixFrameCountIn, MA_TRUE); ma_mixer_mix_decoder(&g_mixerEffects, &g_decoder, submixFrameCountIn, MA_TRUE);
} }
if (g_pAudioBuffer != NULL) {
ma_mixer_mix_audio_buffer(&g_mixerEffects, g_pAudioBuffer, submixFrameCountIn, MA_TRUE);
}
} }
ma_mixer_end(&g_mixerEffects, &g_mixer, NULL); ma_mixer_end(&g_mixerEffects, &g_mixer, NULL);
} }
...@@ -65,12 +69,19 @@ int main(int argc, char** argv) ...@@ -65,12 +69,19 @@ int main(int argc, char** argv)
ma_noise_config noiseConfig; ma_noise_config noiseConfig;
ma_waveform_config waveformConfig; ma_waveform_config waveformConfig;
ma_decoder_config decoderConfig; ma_decoder_config decoderConfig;
const char* pInputFilePath = NULL; ma_audio_buffer_config audioBufferConfig;
void* pInputFileData2;
ma_uint64 inputFileDataSize2;
const char* pInputFilePath1 = NULL;
const char* pInputFilePath2 = NULL;
ma_effect_config effectConfig; ma_effect_config effectConfig;
ma_effect effect; ma_effect effect;
if (argc > 1) { if (argc > 1) {
pInputFilePath = argv[1]; pInputFilePath1 = argv[1];
}
if (argc > 2) {
pInputFilePath2 = argv[2];
} }
deviceConfig = ma_device_config_init(ma_device_type_playback); deviceConfig = ma_device_config_init(ma_device_type_playback);
...@@ -125,20 +136,34 @@ int main(int argc, char** argv) ...@@ -125,20 +136,34 @@ int main(int argc, char** argv)
return result; return result;
} }
waveformConfig = ma_waveform_config_init(device.playback.format, device.playback.channels, device.sampleRate, ma_waveform_type_sine, 0.2, 220); waveformConfig = ma_waveform_config_init(device.playback.format, device.playback.channels, device.sampleRate, ma_waveform_type_sine, 0.5, 220);
result = ma_waveform_init(&waveformConfig, &g_waveform); result = ma_waveform_init(&waveformConfig, &g_waveform);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
return result; return result;
} }
if (pInputFilePath != NULL) { if (pInputFilePath1 != NULL) {
decoderConfig = ma_decoder_config_init(device.playback.format, device.playback.channels, device.sampleRate); decoderConfig = ma_decoder_config_init(device.playback.format, device.playback.channels, device.sampleRate);
result = ma_decoder_init_file(pInputFilePath, &decoderConfig, &g_decoder); result = ma_decoder_init_file(pInputFilePath1, &decoderConfig, &g_decoder);
if (result == MA_SUCCESS) { if (result == MA_SUCCESS) {
g_hasDecoder = MA_TRUE; g_hasDecoder = MA_TRUE;
} }
} }
if (pInputFilePath2 != NULL) {
ma_decoder_config config = ma_decoder_config_init(ma_format_f32, 2, 0);
result = ma_decode_file(pInputFilePath2, &config, &inputFileDataSize2, &pInputFileData2);
if (result == MA_SUCCESS) {
audioBufferConfig = ma_audio_buffer_config_init(config.format, config.channels, inputFileDataSize2, pInputFileData2, NULL);
result = ma_audio_buffer_alloc_and_init(&audioBufferConfig, &g_pAudioBuffer);
ma_free(pInputFileData2, NULL);
pInputFileData2 = NULL;
}
}
/* Everything is setup. We can now start the device. */ /* Everything is setup. We can now start the device. */
result = ma_device_start(&device); result = ma_device_start(&device);
......
...@@ -801,6 +801,7 @@ ma_mixer_end() ...@@ -801,6 +801,7 @@ ma_mixer_end()
MA_API ma_result ma_mixer_mix_decoder(ma_mixer* pMixer, ma_decoder* pDecoder, ma_uint64 frameCountIn, ma_bool32 loop); MA_API ma_result ma_mixer_mix_decoder(ma_mixer* pMixer, ma_decoder* pDecoder, ma_uint64 frameCountIn, ma_bool32 loop);
#endif #endif
MA_API ma_result ma_mixer_mix_audio_buffer(ma_mixer* pMixer, ma_audio_buffer* pAudioBuffer, ma_uint64 frameCountIn, ma_bool32 loop);
#ifndef MA_NO_GENERATION #ifndef MA_NO_GENERATION
MA_API ma_result ma_mixer_mix_waveform(ma_mixer* pMixer, ma_waveform* pWaveform, ma_uint64 frameCountIn); MA_API ma_result ma_mixer_mix_waveform(ma_mixer* pMixer, ma_waveform* pWaveform, ma_uint64 frameCountIn);
MA_API ma_result ma_mixer_mix_noise(ma_mixer* pMixer, ma_noise* pNoise, ma_uint64 frameCountIn); MA_API ma_result ma_mixer_mix_noise(ma_mixer* pMixer, ma_noise* pNoise, ma_uint64 frameCountIn);
...@@ -808,6 +809,7 @@ MA_API ma_result ma_mixer_mix_noise(ma_mixer* pMixer, ma_noise* pNoise, ma_uint6 ...@@ -808,6 +809,7 @@ MA_API ma_result ma_mixer_mix_noise(ma_mixer* pMixer, ma_noise* pNoise, ma_uint6
MA_API ma_result ma_mixer_mix_pcm_rb(ma_mixer* pMixer, ma_pcm_rb* pRB, ma_uint64 frameCountIn); /* Caller is the consumer. */ MA_API ma_result ma_mixer_mix_pcm_rb(ma_mixer* pMixer, ma_pcm_rb* pRB, ma_uint64 frameCountIn); /* Caller is the consumer. */
MA_API ma_result ma_mixer_mix_rb(ma_mixer* pMixer, ma_rb* pRB, ma_uint64 frameCountIn); /* Caller is the consumer. Assumes data is in the same format as the mixer. */ MA_API ma_result ma_mixer_mix_rb(ma_mixer* pMixer, ma_rb* pRB, ma_uint64 frameCountIn); /* Caller is the consumer. Assumes data is in the same format as the mixer. */
MA_API ma_result ma_mixer_mix_rb_ex(ma_mixer* pMixer, ma_rb* pRB, ma_uint64 frameCountIn, ma_format formatIn, ma_uint32 channelsIn); /* Caller is the consumer. */ MA_API ma_result ma_mixer_mix_rb_ex(ma_mixer* pMixer, ma_rb* pRB, ma_uint64 frameCountIn, ma_format formatIn, ma_uint32 channelsIn); /* Caller is the consumer. */
MA_API ma_result ma_mixer_mix_data_source(ma_mixer* pMixer, ma_data_source* pDataSource, ma_uint64 frameCountIn, ma_bool32 loop);
MA_API ma_result ma_mixer_set_volume(ma_mixer* pMixer, float volume); MA_API ma_result ma_mixer_set_volume(ma_mixer* pMixer, float volume);
MA_API ma_result ma_mixer_get_volume(ma_mixer* pMixer, float* pVolume); MA_API ma_result ma_mixer_get_volume(ma_mixer* pMixer, float* pVolume);
MA_API ma_result ma_mixer_set_gain_db(ma_mixer* pMixer, float gainDB); MA_API ma_result ma_mixer_set_gain_db(ma_mixer* pMixer, float gainDB);
...@@ -2810,66 +2812,68 @@ MA_API ma_result ma_mixer_mix_callback(ma_mixer* pMixer, ma_mixer_mix_callback_p ...@@ -2810,66 +2812,68 @@ MA_API ma_result ma_mixer_mix_callback(ma_mixer* pMixer, ma_mixer_mix_callback_p
} }
#ifndef MA_NO_DECODING #ifndef MA_NO_DECODING
typedef struct MA_API ma_result ma_mixer_mix_decoder(ma_mixer* pMixer, ma_decoder* pDecoder, ma_uint64 frameCountIn, ma_bool32 loop)
{ {
ma_decoder* pDecoder; return ma_mixer_mix_data_source(pMixer, pDecoder, frameCountIn, loop);
ma_bool32 loop; }
} ma_mixer_mix_decoder_data; #endif /* MA_NO_DECODING */
static ma_uint32 ma_mixer_mix_decoder__callback(void* pUserData, void* pFramesOut, ma_uint32 frameCount) MA_API ma_result ma_mixer_mix_audio_buffer(ma_mixer* pMixer, ma_audio_buffer* pAudioBuffer, ma_uint64 frameCountIn, ma_bool32 loop)
{ {
ma_mixer_mix_decoder_data* pData = (ma_mixer_mix_decoder_data*)pUserData; /*
ma_uint32 totalFramesRead = 0; The ma_audio_buffer object is a data source, but we can do a specialized implementation to optimize data movement by utilizing memory mapping, kind
void* pRunningFramesOut = pFramesOut; of like what we do with `ma_mixer_mix_pcm_rb()`.
*/
ma_result result;
ma_uint64 totalFramesProcessed = 0;
void* pRunningAccumulationBuffer = pMixer->pAccumulationBuffer;
while (totalFramesRead < frameCount) { if (pMixer == NULL || pAudioBuffer == NULL) {
ma_uint32 framesToRead = frameCount - totalFramesRead; return MA_INVALID_ARGS;
ma_uint32 framesRead = (ma_uint32)ma_decoder_read_pcm_frames(pData->pDecoder, pRunningFramesOut, framesToRead); /* Safe cast because frameCount is 32-bit. */ }
if (framesRead < framesToRead) {
if (pData->loop) { if (frameCountIn > pMixer->mixingState.frameCountIn) {
ma_decoder_seek_to_pcm_frame(pData->pDecoder, 0); return MA_INVALID_ARGS; /* Passing in too many input frames. */
}
while (totalFramesProcessed < frameCountIn) {
void* pMappedBuffer;
ma_uint64 framesToProcess = frameCountIn - totalFramesProcessed;
result = ma_audio_buffer_map(pAudioBuffer, &pMappedBuffer, &framesToProcess);
if (framesToProcess == 0) {
break; /* Wasn't able to map any data. Abort. */
}
ma_mix_pcm_frames_ex(pRunningAccumulationBuffer, pMixer->format, pMixer->channels, pMappedBuffer, pAudioBuffer->format, pAudioBuffer->channels, framesToProcess);
ma_audio_buffer_unmap(pAudioBuffer, framesToProcess);
/* If after mapping we're at the end we'll need to decide if we want to loop. */
if (ma_audio_buffer_at_end(pAudioBuffer)) {
if (loop) {
ma_audio_buffer_seek_to_pcm_frame(pAudioBuffer, 0);
} else { } else {
break; break; /* We've reached the end and we're not looping. */
} }
} }
totalFramesRead += framesRead; totalFramesProcessed += framesToProcess;
pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesRead * ma_get_bytes_per_frame(pData->pDecoder->outputFormat, pData->pDecoder->outputChannels)); pRunningAccumulationBuffer = ma_offset_ptr(pRunningAccumulationBuffer, framesToProcess * ma_get_accumulation_bytes_per_frame(pMixer->format, pMixer->channels));
} }
return totalFramesRead; return MA_SUCCESS;
}
MA_API ma_result ma_mixer_mix_decoder(ma_mixer* pMixer, ma_decoder* pDecoder, ma_uint64 frameCountIn, ma_bool32 loop)
{
ma_mixer_mix_decoder_data data;
data.pDecoder = pDecoder;
data.loop = loop;
return ma_mixer_mix_callback(pMixer, ma_mixer_mix_decoder__callback, &data, frameCountIn, pDecoder->outputFormat, pDecoder->outputChannels);
} }
#endif /* MA_NO_DECODING */
#ifndef MA_NO_GENERATION #ifndef MA_NO_GENERATION
static ma_uint32 ma_mixer_mix_waveform__callback(void* pUserData, void* pFramesOut, ma_uint32 frameCount)
{
return (ma_uint32)ma_waveform_read_pcm_frames((ma_waveform*)pUserData, pFramesOut, frameCount); /* Safe case to ma_uint32 because frameCount is 32-bit. */
}
MA_API ma_result ma_mixer_mix_waveform(ma_mixer* pMixer, ma_waveform* pWaveform, ma_uint64 frameCountIn) MA_API ma_result ma_mixer_mix_waveform(ma_mixer* pMixer, ma_waveform* pWaveform, ma_uint64 frameCountIn)
{ {
return ma_mixer_mix_callback(pMixer, ma_mixer_mix_waveform__callback, pWaveform, frameCountIn, pWaveform->config.format, pWaveform->config.channels); return ma_mixer_mix_data_source(pMixer, pWaveform, frameCountIn, MA_FALSE);
}
static ma_uint32 ma_mixer_mix_noise__callback(void* pUserData, void* pFramesOut, ma_uint32 frameCount)
{
return (ma_uint32)ma_noise_read_pcm_frames((ma_noise*)pUserData, pFramesOut, frameCount); /* Safe case to ma_uint32 because frameCount is 32-bit. */
} }
MA_API ma_result ma_mixer_mix_noise(ma_mixer* pMixer, ma_noise* pNoise, ma_uint64 frameCountIn) MA_API ma_result ma_mixer_mix_noise(ma_mixer* pMixer, ma_noise* pNoise, ma_uint64 frameCountIn)
{ {
return ma_mixer_mix_callback(pMixer, ma_mixer_mix_noise__callback, pNoise, frameCountIn, pNoise->config.format, pNoise->config.channels); return ma_mixer_mix_data_source(pMixer, pNoise, frameCountIn, MA_FALSE);
} }
#endif /* MA_NO_GENERATION */ #endif /* MA_NO_GENERATION */
...@@ -2967,6 +2971,54 @@ MA_API ma_result ma_mixer_mix_rb_ex(ma_mixer* pMixer, ma_rb* pRB, ma_uint64 fram ...@@ -2967,6 +2971,54 @@ MA_API ma_result ma_mixer_mix_rb_ex(ma_mixer* pMixer, ma_rb* pRB, ma_uint64 fram
return MA_SUCCESS; return MA_SUCCESS;
} }
typedef struct
{
ma_data_source* pDataSource;
ma_format format;
ma_uint32 channels;
ma_bool32 loop;
} ma_mixer_mix_data_source_data;
static ma_uint32 ma_mixer_mix_data_source__callback(void* pUserData, void* pFramesOut, ma_uint32 frameCount)
{
ma_mixer_mix_data_source_data* pData = (ma_mixer_mix_data_source_data*)pUserData;
ma_uint32 totalFramesRead = 0;
void* pRunningFramesOut = pFramesOut;
while (totalFramesRead < frameCount) {
ma_uint32 framesToRead = frameCount - totalFramesRead;
ma_uint32 framesRead = (ma_uint32)ma_data_source_read_pcm_frames(pData->pDataSource, pRunningFramesOut, framesToRead); /* Safe cast because frameCount is 32-bit. */
if (framesRead < framesToRead) {
if (pData->loop) {
ma_data_source_seek_to_pcm_frame(pData->pDataSource, 0);
} else {
break;
}
}
totalFramesRead += framesRead;
pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesRead * ma_get_bytes_per_frame(pData->format, pData->channels));
}
return totalFramesRead;
}
MA_API ma_result ma_mixer_mix_data_source(ma_mixer* pMixer, ma_data_source* pDataSource, ma_uint64 frameCountIn, ma_bool32 loop)
{
ma_result result;
ma_mixer_mix_data_source_data data;
result = ma_data_source_get_data_format(pDataSource, &data.format, &data.channels);
if (result != MA_SUCCESS) {
return result;
}
data.pDataSource = pDataSource;
data.loop = loop;
return ma_mixer_mix_callback(pMixer, ma_mixer_mix_data_source__callback, &data, frameCountIn, data.format, data.channels);
}
MA_API ma_result ma_mixer_set_volume(ma_mixer* pMixer, float volume) MA_API ma_result ma_mixer_set_volume(ma_mixer* pMixer, float volume)
{ {
......
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