diff --git a/miniaudio.h b/miniaudio.h index 1474fe7e..043a67be 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -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 ============ 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); #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) typedef enum { @@ -5304,6 +5429,7 @@ typedef struct struct ma_decoder { + ma_data_source_callbacks ds; ma_decoder_read_proc onRead; ma_decoder_seek_proc onSeek; void* pUserData; @@ -5479,6 +5605,7 @@ MA_API ma_waveform_config ma_waveform_config_init(ma_format format, ma_uint32 ch typedef struct { + ma_data_source_callbacks ds; ma_waveform_config config; double advance; double time; @@ -5486,6 +5613,7 @@ typedef struct 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_result ma_waveform_seek_to_pcm_frame(ma_waveform* pWaveform, ma_uint64 frameIndex); MA_API ma_result ma_waveform_set_amplitude(ma_waveform* pWaveform, double amplitude); MA_API ma_result ma_waveform_set_frequency(ma_waveform* pWaveform, double frequency); 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 typedef struct { + ma_data_source_callbacks ds; ma_noise_config config; ma_lcg lcg; union @@ -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 @@ -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) { ma_result result; @@ -40678,6 +41168,10 @@ static ma_result ma_decoder__preinit(ma_decoder_read_proc onRead, ma_decoder_see 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->onSeek = onSeek; pDecoder->pUserData = pUserData; @@ -42029,6 +42523,26 @@ MA_API ma_waveform_config ma_waveform_config_init(ma_format format, ma_uint32 ch 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) { if (pWaveform == NULL) { @@ -42036,6 +42550,9 @@ MA_API ma_result ma_waveform_init(const ma_waveform_config* pConfig, ma_waveform } 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->advance = 1.0 / pWaveform->config.sampleRate; pWaveform->time = 0; @@ -42341,6 +42858,17 @@ MA_API ma_uint64 ma_waveform_read_pcm_frames(ma_waveform* pWaveform, void* pFram 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) { @@ -42360,6 +42888,30 @@ MA_API ma_noise_config ma_noise_config_init(ma_format format, ma_uint32 channels 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) { if (pNoise == NULL) { @@ -42372,6 +42924,9 @@ MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, ma_noise* pNoise) 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; ma_lcg_seed(&pNoise->lcg, pConfig->seed); diff --git a/research/ma_mixing.c b/research/ma_mixing.c index ef9442fc..5bca58e8 100644 --- a/research/ma_mixing.c +++ b/research/ma_mixing.c @@ -19,6 +19,7 @@ ma_noise g_noise; ma_waveform g_waveform; ma_decoder g_decoder; 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) { @@ -46,6 +47,9 @@ void data_callback(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, if (g_hasDecoder) { 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); } @@ -65,12 +69,19 @@ int main(int argc, char** argv) ma_noise_config noiseConfig; ma_waveform_config waveformConfig; 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 effect; if (argc > 1) { - pInputFilePath = argv[1]; + pInputFilePath1 = argv[1]; + } + if (argc > 2) { + pInputFilePath2 = argv[2]; } deviceConfig = ma_device_config_init(ma_device_type_playback); @@ -125,20 +136,34 @@ int main(int argc, char** argv) 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); if (result != MA_SUCCESS) { return result; } - if (pInputFilePath != NULL) { + if (pInputFilePath1 != NULL) { 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) { 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. */ result = ma_device_start(&device); diff --git a/research/ma_mixing.h b/research/ma_mixing.h index dcb0dc4c..6cf3534c 100644 --- a/research/ma_mixing.h +++ b/research/ma_mixing.h @@ -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); #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 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); @@ -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_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_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_get_volume(ma_mixer* pMixer, float* pVolume); 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 } #ifndef MA_NO_DECODING -typedef struct -{ - ma_decoder* pDecoder; - ma_bool32 loop; -} ma_mixer_mix_decoder_data; - -static ma_uint32 ma_mixer_mix_decoder__callback(void* pUserData, void* pFramesOut, ma_uint32 frameCount) -{ - ma_mixer_mix_decoder_data* pData = (ma_mixer_mix_decoder_data*)pUserData; - ma_uint32 totalFramesRead = 0; - void* pRunningFramesOut = pFramesOut; - - while (totalFramesRead < frameCount) { - ma_uint32 framesToRead = frameCount - totalFramesRead; - 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) { - ma_decoder_seek_to_pcm_frame(pData->pDecoder, 0); - } else { - break; - } - } - - totalFramesRead += framesRead; - pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesRead * ma_get_bytes_per_frame(pData->pDecoder->outputFormat, pData->pDecoder->outputChannels)); - } - - return totalFramesRead; -} - 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); + return ma_mixer_mix_data_source(pMixer, pDecoder, frameCountIn, loop); } #endif /* MA_NO_DECODING */ -#ifndef MA_NO_GENERATION -static ma_uint32 ma_mixer_mix_waveform__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) { - return (ma_uint32)ma_waveform_read_pcm_frames((ma_waveform*)pUserData, pFramesOut, frameCount); /* Safe case to ma_uint32 because frameCount is 32-bit. */ + /* + 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 + of like what we do with `ma_mixer_mix_pcm_rb()`. + */ + ma_result result; + ma_uint64 totalFramesProcessed = 0; + void* pRunningAccumulationBuffer = pMixer->pAccumulationBuffer; + + if (pMixer == NULL || pAudioBuffer == NULL) { + return MA_INVALID_ARGS; + } + + if (frameCountIn > pMixer->mixingState.frameCountIn) { + 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 { + break; /* We've reached the end and we're not looping. */ + } + } + + totalFramesProcessed += framesToProcess; + pRunningAccumulationBuffer = ma_offset_ptr(pRunningAccumulationBuffer, framesToProcess * ma_get_accumulation_bytes_per_frame(pMixer->format, pMixer->channels)); + } + + return MA_SUCCESS; } +#ifndef MA_NO_GENERATION 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); -} - - -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. */ + return ma_mixer_mix_data_source(pMixer, pWaveform, frameCountIn, MA_FALSE); } 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 */ @@ -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; } +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) {