diff --git a/mini_al.h b/mini_al.h index c47ab89c..1d5d4ef0 100644 --- a/mini_al.h +++ b/mini_al.h @@ -374,10 +374,36 @@ typedef struct } mal_timer; +typedef struct mal_src mal_src; +typedef mal_uint32 (* mal_src_read_proc)(mal_uint32 frameCount, void* pFramesOut, void* pUserData); // Returns the number of frames that were read. + +typedef enum +{ + mal_src_algorithm_none, + mal_src_algorithm_linear +} mal_src_algorithm; + +typedef struct +{ + mal_uint32 sampleRateIn; + mal_uint32 sampleRateOut; + mal_format formatIn; + mal_format formatOut; + mal_uint32 channels; + mal_src_algorithm algorithm; +} mal_src_config; + +struct mal_src +{ + mal_src_config config; + mal_src_read_proc onRead; + void* pUserData; + float ratio; + float bin[256]; +}; typedef struct mal_dsp mal_dsp; -typedef mal_uint32 (* mal_dsp_read_proc) (mal_uint32 frameCount, void* pSamplesOut, void* pUserData); -typedef mal_uint32 (* mal_dsp_process_proc)(mal_dsp* pDSP, mal_dsp_read_proc onRead, void* pUserData, void* pFramesOut, mal_uint32 frameCount); +typedef mal_uint32 (* mal_dsp_read_proc) (mal_uint32 frameCount, void* pSamplesOut, void* pUserData); typedef struct { @@ -394,13 +420,12 @@ typedef struct struct mal_dsp { mal_dsp_config config; - mal_dsp_process_proc onProcess; + mal_dsp_read_proc onRead; + void* pUserDataForOnRead; + mal_src src; // For sample rate conversion. mal_bool32 isUsingForeignChannelMap : 1; - struct - { - float ratio; - float bin[256]; // Will hold previous samples for handling interpolation and whatnot. - } src; + mal_bool32 isSRCRequired : 1; + mal_bool32 isPassthrough : 1; // <-- Will be set to true when the DSP pipeline is an optimized passthrough. }; @@ -584,6 +609,9 @@ struct mal_device mal_uint8 internalChannelMap[MAL_MAX_CHANNELS]; mal_uint8 channelShuffleTable[MAL_MAX_CHANNELS]; mal_dsp dsp; // Samples run through this to convert samples to a format suitable for use by the backend. + mal_uint32 _dspFrameCount; // Internal use only. Used when running the device -> DSP -> client pipeline. See mal_device__on_read_from_device(). + const mal_uint8* _dspFrames; // ^^^ AS ABOVE ^^^ + union { @@ -914,6 +942,22 @@ mal_uint32 mal_get_sample_size_in_bytes(mal_format format); +/////////////////////////////////////////////////////////////////////////////// +// +// SRC +// +/////////////////////////////////////////////////////////////////////////////// + +// Initializes a sample rate conversion object. +mal_result mal_src_init(mal_src_config* pConfig, mal_src_read_proc onRead, void* pUserData, mal_src* pSRC); + +// Reads a number of frames. +// +// Returns the number of frames actually read. +mal_uint32 mal_src_read_frames(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut); + + + /////////////////////////////////////////////////////////////////////////////// // // DSP @@ -923,10 +967,40 @@ mal_uint32 mal_get_sample_size_in_bytes(mal_format format); #include "tools/mal_build/bin/mini_al_dsp.h" #else // Initializes a DSP object. -mal_result mal_dsp_init(mal_dsp_config* pConfig, mal_dsp* pDSP); +mal_result mal_dsp_init(mal_dsp_config* pConfig, mal_dsp_read_proc onRead, void* pUserData, mal_dsp* pDSP); + +// Reads a number of frames and runs them through the DSP processor. +mal_uint32 mal_dsp_read_frames(mal_dsp* pDSP, mal_uint32 frameCount, void* pFramesOut); + + + +/////////////////////////////////////////////////////////////////////////////// +// +// Format Conversion +// +/////////////////////////////////////////////////////////////////////////////// +void mal_pcm_u8_to_s16(short* pOut, const unsigned char* pIn, unsigned int count); +void mal_pcm_u8_to_s24(void* pOut, const unsigned char* pIn, unsigned int count); +void mal_pcm_u8_to_s32(int* pOut, const unsigned char* pIn, unsigned int count); +void mal_pcm_u8_to_f32(float* pOut, const unsigned char* pIn, unsigned int count); +void mal_pcm_s16_to_u8(unsigned char* pOut, const short* pIn, unsigned int count); +void mal_pcm_s16_to_s24(void* pOut, const short* pIn, unsigned int count); +void mal_pcm_s16_to_s32(int* pOut, const short* pIn, unsigned int count); +void mal_pcm_s16_to_f32(float* pOut, const short* pIn, unsigned int count); +void mal_pcm_s24_to_u8(unsigned char* pOut, const void* pIn, unsigned int count); +void mal_pcm_s24_to_s16(short* pOut, const void* pIn, unsigned int count); +void mal_pcm_s24_to_s32(int* pOut, const void* pIn, unsigned int count); +void mal_pcm_s24_to_f32(float* pOut, const void* pIn, unsigned int count); +void mal_pcm_s32_to_u8(unsigned char* pOut, const int* pIn, unsigned int count); +void mal_pcm_s32_to_s16(short* pOut, const int* pIn, unsigned int count); +void mal_pcm_s32_to_s24(void* pOut, const int* pIn, unsigned int count); +void mal_pcm_s32_to_f32(float* pOut, const int* pIn, unsigned int count); +void mal_pcm_f32_to_u8(unsigned char* pOut, const float* pIn, unsigned int count); +void mal_pcm_f32_to_s16(short* pOut, const float* pIn, unsigned int count); +void mal_pcm_f32_to_s24(void* pOut, const float* pIn, unsigned int count); +void mal_pcm_f32_to_s32(int* pOut, const float* pIn, unsigned int count); +void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_format formatIn, unsigned int sampleCount); -// Reads a number of samples and runs them through the DSP processor. -mal_uint32 mal_dsp_process(mal_dsp* pDSP, mal_dsp_read_proc onRead, void* pUserData, void* pFramesOut, mal_uint32 frameCount); #endif #ifdef __cplusplus @@ -1095,6 +1169,10 @@ mal_uint8 MAL_CHANNEL_MAP_5POINT1[MAL_MAX_CHANNELS] = { #endif #endif +#define mal_countof(x) (sizeof(x) / sizeof(x[0])) +#define mal_max(x, y) (((x) > (y)) ? (x) : (y)) +#define mal_min(x, y) (((x) < (y)) ? (x) : (y)) + // Return Values: // 0: Success // 22: EINVAL @@ -1629,32 +1707,24 @@ static inline mal_uint32 mal_device__on_read_from_client(mal_uint32 frameCount, } // The callback for reading from the device -> DSP -> client. -typedef struct -{ - mal_device* pDevice; - mal_uint32 frameCount; - const mal_uint8* pFrames; -} mal_device__on_read_from_device__data; - static inline mal_uint32 mal_device__on_read_from_device(mal_uint32 frameCount, void* pFramesOut, void* pUserData) { - mal_device__on_read_from_device__data* pData = (mal_device__on_read_from_device__data*)pUserData; - mal_assert(pData != NULL); - mal_assert(pData->pDevice != NULL); + mal_device* pDevice = (mal_device*)pUserData; + mal_assert(pDevice != NULL); - if (pData->frameCount == 0) { + if (pDevice->_dspFrameCount == 0) { return 0; // Nothing left. } mal_uint32 framesToRead = frameCount; - if (framesToRead > pData->frameCount) { - framesToRead = pData->frameCount; + if (framesToRead > pDevice->_dspFrameCount) { + framesToRead = pDevice->_dspFrameCount; } - mal_uint32 bytesToRead = framesToRead * pData->pDevice->internalChannels * mal_get_sample_size_in_bytes(pData->pDevice->internalFormat); - mal_copy_memory(pFramesOut, pData->pFrames, bytesToRead); - pData->frameCount -= framesToRead; - pData->pFrames += bytesToRead; + mal_uint32 bytesToRead = framesToRead * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat); + mal_copy_memory(pFramesOut, pDevice->_dspFrames, bytesToRead); + pDevice->_dspFrameCount -= framesToRead; + pDevice->_dspFrames += bytesToRead; return framesToRead; } @@ -1667,7 +1737,7 @@ static inline mal_uint32 mal_device__read_frames_from_client(mal_device* pDevice mal_assert(frameCount > 0); mal_assert(pSamples != NULL); - mal_uint32 framesRead = mal_dsp_process(&pDevice->dsp, mal_device__on_read_from_client, pDevice, pSamples, frameCount); + mal_uint32 framesRead = mal_dsp_read_frames(&pDevice->dsp, frameCount, pSamples); mal_uint32 samplesRead = framesRead * pDevice->internalChannels; mal_uint32 sampleSize = mal_get_sample_size_in_bytes(pDevice->internalFormat); mal_uint32 consumedBytes = samplesRead*sampleSize; @@ -1686,16 +1756,14 @@ static inline void mal_device__send_frames_to_client(mal_device* pDevice, mal_ui mal_recv_proc onRecv = pDevice->onRecv; if (onRecv) { - mal_device__on_read_from_device__data data; - data.pDevice = pDevice; - data.frameCount = frameCount; - data.pFrames = (const mal_uint8*)pSamples; + pDevice->_dspFrameCount = frameCount; + pDevice->_dspFrames = (const mal_uint8*)pSamples; mal_uint8 chunkBuffer[4096]; mal_uint32 chunkFrameCount = sizeof(chunkBuffer) / mal_get_sample_size_in_bytes(pDevice->format) / pDevice->channels; for (;;) { - mal_uint32 framesJustRead = mal_dsp_process(&pDevice->dsp, mal_device__on_read_from_device, &data, chunkBuffer, chunkFrameCount); + mal_uint32 framesJustRead = mal_dsp_read_frames(&pDevice->dsp, chunkFrameCount, chunkBuffer); if (framesJustRead == 0) { break; } @@ -2296,7 +2364,7 @@ static mal_result mal_device_init__wasapi(mal_context* pContext, mal_device_type } pDevice->internalChannels = wf.Format.nChannels; - //pDevice->internalSampleRate = wf.Format.nSamplesPerSec; // TODO: Uncomment this when SRC is implemented. + pDevice->internalSampleRate = wf.Format.nSamplesPerSec; #ifdef __cplusplus hr = ((IAudioClient*)pDevice->wasapi.pAudioClient)->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, bufferDurationInMicroseconds*10, 0, (WAVEFORMATEX*)&wf, NULL); @@ -5412,7 +5480,7 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi } if (result != MAL_SUCCESS) { - return MAL_NO_BACKEND; // The error message will have been posted by the source of the error. + return MAL_NO_BACKEND; // The error message will have been posted with mal_post_error() by the source of the error so don't bother calling it here. } @@ -5461,6 +5529,7 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi dspConfig.channelsOut = pDevice->internalChannels; dspConfig.sampleRateOut = pDevice->internalSampleRate; mal_copy_memory(dspConfig.channelMapOut, pDevice->internalChannelMap, sizeof(dspConfig.channelMapOut)); + mal_dsp_init(&dspConfig, mal_device__on_read_from_client, pDevice, &pDevice->dsp); } else { dspConfig.formatIn = pDevice->internalFormat; dspConfig.channelsIn = pDevice->internalChannels; @@ -5470,9 +5539,10 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi dspConfig.channelsOut = pDevice->channels; dspConfig.sampleRateOut = pDevice->sampleRate; mal_copy_memory(dspConfig.channelMapOut, pDevice->channelMap, sizeof(dspConfig.channelMapOut)); + mal_dsp_init(&dspConfig, mal_device__on_read_from_device, pDevice, &pDevice->dsp); } - mal_dsp_init(&dspConfig, &pDevice->dsp); + // Some backends don't require the worker thread. @@ -5697,6 +5767,97 @@ mal_uint32 mal_get_sample_size_in_bytes(mal_format format) } +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// SRC +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +mal_uint32 mal_src_read_frames_passthrough(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut); +mal_uint32 mal_src_read_frames_linear(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut); + +mal_result mal_src_init(mal_src_config* pConfig, mal_src_read_proc onRead, void* pUserData, mal_src* pSRC) +{ + if (pSRC == NULL) return MAL_INVALID_ARGS; + mal_zero_object(pSRC); + + if (pConfig == NULL || onRead == NULL) return MAL_INVALID_ARGS; + if (pConfig->channels == 0 || pConfig->channels > MAL_MAX_CHANNELS) return MAL_INVALID_ARGS; + + pSRC->config = *pConfig; + pSRC->onRead = onRead; + pSRC->pUserData = pUserData; + + // If the in and out sample rates are the same, fall back to the passthrough algorithm. + if (pSRC->config.sampleRateIn == pSRC->config.sampleRateOut) { + pSRC->config.algorithm = mal_src_algorithm_none; + } + + pSRC->ratio = (float)pSRC->config.sampleRateIn / pSRC->config.sampleRateOut; + + return MAL_SUCCESS; +} + +mal_uint32 mal_src_read_frames(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut) +{ + if (pSRC == NULL || frameCount == 0 || pFramesOut == NULL) return 0; + + // Could just use a function pointer instead of a switch for this... + switch (pSRC->config.algorithm) + { + case mal_src_algorithm_none: return mal_src_read_frames_passthrough(pSRC, frameCount, pFramesOut); + case mal_src_algorithm_linear: return mal_src_read_frames_linear(pSRC, frameCount, pFramesOut); + default: return 0; + } +} + +mal_uint32 mal_src_read_frames_passthrough(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut) +{ + mal_assert(pSRC != NULL); + mal_assert(frameCount > 0); + mal_assert(pFramesOut != NULL); + + // Fast path. No need for data conversion - just pass right through. + if (pSRC->config.formatIn == pSRC->config.formatOut) { + return pSRC->onRead(frameCount, pFramesOut, pSRC->pUserData); + } + + // Slower path. Need to do a format conversion. + mal_uint32 totalFramesRead = 0; + while (frameCount > 0) { + mal_uint8 pStagingBuffer[MAL_MAX_CHANNELS * 2048]; + mal_uint32 stagingBufferSizeInFrames = sizeof(pStagingBuffer) / mal_get_sample_size_in_bytes(pSRC->config.formatIn) / pSRC->config.channels; + mal_uint32 framesToRead = stagingBufferSizeInFrames; + if (framesToRead > frameCount) { + framesToRead = frameCount; + } + + mal_uint32 framesRead = pSRC->onRead(framesToRead, pStagingBuffer, pSRC->pUserData); + if (framesRead == 0) { + break; + } + + mal_pcm_convert(pFramesOut, pSRC->config.formatOut, pStagingBuffer, pSRC->config.formatIn, framesRead * pSRC->config.channels); + + pFramesOut = (mal_uint8*)pFramesOut + (framesRead * pSRC->config.channels * mal_get_sample_size_in_bytes(pSRC->config.formatOut)); + frameCount -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +mal_uint32 mal_src_read_frames_linear(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut) +{ + mal_assert(pSRC != NULL); + mal_assert(frameCount > 0); + mal_assert(pFramesOut != NULL); + + // TODO: Implement me properly. + return mal_src_read_frames_passthrough(pSRC, frameCount, pFramesOut); +} + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -5747,10 +5908,11 @@ void mal_pcm_f32_to_s16(short* pOut, const float* pIn, unsigned int count); void mal_pcm_f32_to_s24(void* pOut, const float* pIn, unsigned int count); void mal_pcm_f32_to_s32(int* pOut, const float* pIn, unsigned int count); -static void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_format formatIn, unsigned int sampleCount) +void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_format formatIn, unsigned int sampleCount) { if (formatOut == formatIn) { mal_copy_memory(pOut, pIn, sampleCount * mal_get_sample_size_in_bytes(formatOut)); + return; } switch (formatIn) @@ -6014,102 +6176,37 @@ static void mal_dsp_mix_channels(float* pFramesOut, mal_uint32 channelsOut, cons } } -mal_uint32 mal_dsp_process_passthrough(mal_dsp* pDSP, mal_dsp_read_proc onRead, void* pUserData, void* pFramesOut, mal_uint32 frameCount); -mal_uint32 mal_dsp_process_generic(mal_dsp* pDSP, mal_dsp_read_proc onRead, void* pUserData, void* pFramesOut, mal_uint32 frameCount); -typedef enum +mal_uint32 mal_dsp__src_on_read(mal_uint32 frameCount, void* pFramesOut, void* pUserData) { - mal_dsp_opcode_pcm_convert, - mal_dsp_opcode_channel_mix, - mal_dsp_opcode_channel_shuffle, - mal_dsp_opcode_src, -} mal_dsp_opcode; + mal_dsp* pDSP = (mal_dsp*)pUserData; + mal_assert(pDSP != NULL); -#if defined(_MSC_VER) - #pragma warning(push) - #pragma warning(disable:4201) // nonstandard extension used: nameless struct/union -#endif -typedef struct -{ - mal_dsp_opcode opcode; - mal_uint8* pInputData; - mal_format inputFormat; - mal_uint32 inputChannels; - mal_uint32 inputFrameCount; - mal_uint8* pOutputData; - mal_format outputFormat; - mal_uint32 outputChannels; - mal_uint32 outputFrameCount; - union - { - struct - { - mal_channel_mix_mode mode; - } channel_mix; - - struct - { - mal_uint8 shuffleTable; - } channel_shuffle; - - struct - { - mal_uint32 inputSampleRate; - mal_uint32 outputSampleRate; - } src; - }; -} mal_dsp_op; -#if defined(_MSC_VER) - #pragma warning(pop) -#endif - -void mal_dsp_process_do_next_op(mal_dsp* pDSP, mal_dsp_op* pOP) -{ - (void)pDSP; - - switch (pOP->opcode) - { - case mal_dsp_opcode_pcm_convert: // Conversion from pOP->inputFormat to pOP->outputFormat - { - mal_pcm_convert(pOP->pOutputData, pOP->outputFormat, pOP->pInputData, pOP->inputFormat, pOP->inputFrameCount * pOP->inputChannels); - pOP->outputFrameCount = pOP->inputFrameCount; - } break; - - case mal_dsp_opcode_channel_mix: - { - mal_assert(pOP->inputFormat == mal_format_f32); - mal_dsp_mix_channels((float*)pOP->pOutputData, pOP->outputChannels, (float*)pOP->pInputData, pOP->inputChannels, pOP->inputFrameCount, mal_channel_mix_mode_blend); - pOP->outputFrameCount = pOP->inputFrameCount; - } break; - - case mal_dsp_opcode_channel_shuffle: - { - // TODO: Implement me. - mal_copy_memory(pOP->pOutputData, pOP->pInputData, pOP->inputFrameCount * pOP->inputChannels * mal_get_sample_size_in_bytes(pOP->inputFormat)); - pOP->outputFrameCount = pOP->inputFrameCount; - } break; - - case mal_dsp_opcode_src: - { - // TODO: Implement me. - mal_copy_memory(pOP->pOutputData, pOP->pInputData, pOP->inputFrameCount * pOP->inputChannels * mal_get_sample_size_in_bytes(pOP->inputFormat)); - pOP->outputFrameCount = pOP->inputFrameCount; - } break; - } - - // The outputs from this operation become the inputs of the next. - pOP->pInputData = pOP->pOutputData; - pOP->inputFormat = pOP->outputFormat; - pOP->inputChannels = pOP->outputChannels; + return pDSP->onRead(frameCount, pFramesOut, pDSP->pUserDataForOnRead); } -mal_result mal_dsp_init(mal_dsp_config* pConfig, mal_dsp* pDSP) +mal_result mal_dsp_init(mal_dsp_config* pConfig, mal_dsp_read_proc onRead, void* pUserData, mal_dsp* pDSP) { if (pDSP == NULL) return MAL_INVALID_ARGS; pDSP->config = *pConfig; - pDSP->onProcess = NULL; - pDSP->src.ratio = pDSP->config.sampleRateIn / (float)pDSP->config.sampleRateOut; - mal_zero_memory(pDSP->src.bin, sizeof(pDSP->src.bin)); + pDSP->onRead = onRead; + pDSP->pUserDataForOnRead = pUserData; + + if (pConfig->sampleRateIn != pConfig->sampleRateOut) { + pDSP->isSRCRequired = MAL_TRUE; + + mal_src_config srcConfig; + srcConfig.sampleRateIn = pConfig->sampleRateIn; + srcConfig.sampleRateOut = pConfig->sampleRateOut; + srcConfig.formatIn = pConfig->formatIn; + srcConfig.formatOut = mal_format_f32; + srcConfig.channels = pConfig->channelsIn; + srcConfig.algorithm = mal_src_algorithm_linear; + mal_result result = mal_src_init(&srcConfig, mal_dsp__src_on_read, pDSP, &pDSP->src); + if (result != MAL_SUCCESS) { + return result; + } + } pDSP->isUsingForeignChannelMap = MAL_FALSE; for (mal_uint32 i = 0; i < MAL_MAX_CHANNELS; ++i) { @@ -6120,132 +6217,79 @@ mal_result mal_dsp_init(mal_dsp_config* pConfig, mal_dsp* pDSP) } if (pConfig->formatIn == pConfig->formatOut && pConfig->channelsIn == pConfig->channelsOut && pConfig->sampleRateIn == pConfig->sampleRateOut && !pDSP->isUsingForeignChannelMap) { - pDSP->onProcess = mal_dsp_process_passthrough; + pDSP->isPassthrough = MAL_TRUE; } else { - pDSP->onProcess = mal_dsp_process_generic; + pDSP->isPassthrough = MAL_FALSE; } return MAL_SUCCESS; } - -mal_uint32 mal_dsp_process_passthrough(mal_dsp* pDSP, mal_dsp_read_proc onRead, void* pUserData, void* pFramesOut, mal_uint32 frameCount) +mal_uint32 mal_dsp_read_frames(mal_dsp* pDSP, mal_uint32 frameCount, void* pFramesOut) { - (void)pDSP; - return onRead(frameCount, pFramesOut, pUserData); -} + if (pDSP == NULL || pFramesOut == NULL) return 0; -mal_uint32 mal_dsp_process_generic(mal_dsp* pDSP, mal_dsp_read_proc onRead, void* pUserData, void* pFramesOut, mal_uint32 frameCount) -{ - // The DSP pipline is made up of stages where the output data from one stage becomes the input data of the next. - mal_uint8 pSampleData[2][256*MAL_MAX_CHANNELS*4]; - mal_uint32 totalFramesProcessed = 0; - - mal_bool32 requiresF32Conversion = MAL_FALSE; - if (pDSP->config.formatIn != mal_format_f32 && (pDSP->config.channelsIn != pDSP->config.channelsOut || pDSP->config.sampleRateIn != pDSP->config.sampleRateOut)) { - requiresF32Conversion = MAL_TRUE; + // Fast path. + if (pDSP->isPassthrough) { + return pDSP->onRead(frameCount, pFramesOut, pDSP->pUserDataForOnRead); } - mal_uint32 framesToRead = (mal_uint32)(frameCount * (1 / pDSP->src.ratio)); - while (totalFramesProcessed < frameCount) { - mal_dsp_op op; - op.pInputData = pSampleData[0]; - op.inputFormat = pDSP->config.formatIn; - op.inputChannels = pDSP->config.channelsIn; - op.pOutputData = pSampleData[1]; + // Slower path - where the real work is done. - mal_uint32 inputFrameCount = sizeof(pSampleData[0]) / mal_get_sample_size_in_bytes(pDSP->config.formatIn) / pDSP->config.channelsIn; - if (inputFrameCount > framesToRead) { - inputFrameCount = framesToRead; +#define MAL_DSP_SAMPLE_CHUNK_SIZE 512 + + float pFrames[2][MAL_MAX_CHANNELS * MAL_DSP_SAMPLE_CHUNK_SIZE]; + mal_format pFramesFormat[2]; + mal_uint32 iFrames = 0; // <-- Used as an index into pFrames and cycles between 0 and 1. + + mal_uint32 totalFramesRead = 0; + while (frameCount > 0) { + mal_uint32 framesToRead = mal_countof(pFrames[0]) / mal_max(pDSP->config.channelsIn, pDSP->config.channelsOut); + if (framesToRead > frameCount) { + framesToRead = frameCount; } - inputFrameCount = onRead(inputFrameCount, op.pInputData, pUserData); - if (inputFrameCount == 0) { + // The initial filling of sample data depends on whether or not we are using SRC. + mal_uint32 framesRead = 0; + if (pDSP->isSRCRequired) { + framesRead = mal_src_read_frames(&pDSP->src, framesToRead, pFrames[iFrames]); + pFramesFormat[iFrames] = pDSP->src.config.formatOut; // Should always be f32. + } else { + framesRead = pDSP->onRead(framesToRead, pFrames[iFrames], pDSP->pUserDataForOnRead); + pFramesFormat[iFrames] = pDSP->config.formatIn; + } + + if (framesRead == 0) { break; } + - op.inputFrameCount = inputFrameCount; - - if (requiresF32Conversion) { - op.opcode = mal_dsp_opcode_pcm_convert; - op.outputChannels = op.inputChannels; - op.outputFormat = mal_format_f32; - mal_dsp_process_do_next_op(pDSP, &op); - } - - if (pDSP->config.sampleRateIn == pDSP->config.sampleRateOut) { - // No SRC. - if (pDSP->config.channelsIn != pDSP->config.channelsOut) { - op.opcode = mal_dsp_opcode_channel_mix; - op.outputChannels = pDSP->config.channelsOut; - mal_dsp_process_do_next_op(pDSP, &op); + // Channel mixing. The input format must be in f32 which may require a conversion. + if (pDSP->config.channelsIn != pDSP->config.channelsOut) { + if (pFramesFormat[iFrames] != mal_format_f32) { + mal_pcm_convert(pFrames[(iFrames + 1) % 2], mal_format_f32, pFrames[iFrames], pDSP->config.formatIn, framesRead * pDSP->config.channelsIn); + iFrames = (iFrames + 1) % 2; + pFramesFormat[iFrames] = mal_format_f32; } - if (pDSP->isUsingForeignChannelMap) { - op.opcode = mal_dsp_opcode_channel_shuffle; - mal_dsp_process_do_next_op(pDSP, &op); - } - } else { - // SRC. - if (pDSP->config.sampleRateIn > pDSP->config.sampleRateOut) { - // Decimation. Do SRC first. - op.opcode = mal_dsp_opcode_src; - op.src.inputSampleRate = pDSP->config.sampleRateIn; - op.src.outputSampleRate = pDSP->config.sampleRateOut; - mal_dsp_process_do_next_op(pDSP, &op); - - if (pDSP->config.channelsIn != pDSP->config.channelsOut) { - op.opcode = mal_dsp_opcode_channel_mix; - op.outputChannels = pDSP->config.channelsOut; - mal_dsp_process_do_next_op(pDSP, &op); - } - - if (pDSP->isUsingForeignChannelMap) { - op.opcode = mal_dsp_opcode_channel_shuffle; - mal_dsp_process_do_next_op(pDSP, &op); - } - } else { - // Upsampling. Do SRC last. - if (pDSP->config.channelsIn != pDSP->config.channelsOut) { - op.opcode = mal_dsp_opcode_channel_mix; - op.outputChannels = pDSP->config.channelsOut; - mal_dsp_process_do_next_op(pDSP, &op); - } - - if (pDSP->isUsingForeignChannelMap) { - op.opcode = mal_dsp_opcode_channel_shuffle; - mal_dsp_process_do_next_op(pDSP, &op); - } - - op.opcode = mal_dsp_opcode_src; - op.src.inputSampleRate = pDSP->config.sampleRateIn; - op.src.outputSampleRate = pDSP->config.sampleRateOut; - mal_dsp_process_do_next_op(pDSP, &op); - } + mal_dsp_mix_channels(pFrames[(iFrames + 1) % 2], pDSP->config.channelsOut, pFrames[iFrames], pDSP->config.channelsIn, framesRead, mal_channel_mix_mode_blend); + iFrames = (iFrames + 1) % 2; + pFramesFormat[iFrames] = mal_format_f32; } - // Format conversion comes last. - if (op.inputFormat != pDSP->config.formatOut) { - op.opcode = mal_dsp_opcode_pcm_convert; - op.outputFormat = pDSP->config.formatOut; - mal_dsp_process_do_next_op(pDSP, &op); - } - mal_uint32 frameSizeInBytes = pDSP->config.channelsOut * mal_get_sample_size_in_bytes(pDSP->config.formatOut); - mal_copy_memory((mal_uint8*)pFramesOut + (totalFramesProcessed * frameSizeInBytes), op.pOutputData, op.outputFrameCount * frameSizeInBytes); + // TODO: Channel mapping. + - totalFramesProcessed += op.outputFrameCount; - framesToRead -= op.outputFrameCount; + // Final conversion to output format. + mal_pcm_convert(pFramesOut, pDSP->config.formatOut, pFrames[iFrames], pFramesFormat[iFrames], framesRead * pDSP->config.channelsOut); + + pFramesOut = (mal_uint8*)pFramesOut + (framesRead * pDSP->config.channelsOut * mal_get_sample_size_in_bytes(pDSP->config.formatOut)); + frameCount -= framesRead; + totalFramesRead += framesRead; } - return totalFramesProcessed; -} - - -mal_uint32 mal_dsp_process(mal_dsp* pDSP, mal_dsp_read_proc onRead, void* pUserData, void* pFramesOut, mal_uint32 frameCount) -{ - if (pDSP == NULL || pFramesOut == NULL || onRead == NULL || pDSP->onProcess == NULL) return 0; - return pDSP->onProcess(pDSP, onRead, pUserData, pFramesOut, frameCount); + return totalFramesRead; }