Commit 205e1b87 authored by David Reid's avatar David Reid

API CHANGE: Remove ma_pcm_converter.

This has been replaced with ma_data_converter.
parent 64707cf4
...@@ -1550,53 +1550,6 @@ struct ma_src ...@@ -1550,53 +1550,6 @@ struct ma_src
ma_bool32 useNEON : 1; ma_bool32 useNEON : 1;
}; };
typedef struct ma_pcm_converter ma_pcm_converter;
typedef ma_uint32 (* ma_pcm_converter_read_proc)(ma_pcm_converter* pDSP, void* pFramesOut, ma_uint32 frameCount, void* pUserData);
typedef struct
{
ma_format formatIn;
ma_uint32 channelsIn;
ma_uint32 sampleRateIn;
ma_channel channelMapIn[MA_MAX_CHANNELS];
ma_format formatOut;
ma_uint32 channelsOut;
ma_uint32 sampleRateOut;
ma_channel channelMapOut[MA_MAX_CHANNELS];
ma_channel_mix_mode channelMixMode;
ma_dither_mode ditherMode;
ma_src_algorithm srcAlgorithm;
ma_bool32 allowDynamicSampleRate;
ma_bool32 neverConsumeEndOfInput : 1; /* <-- For SRC. */
ma_bool32 noSSE2 : 1;
ma_bool32 noAVX2 : 1;
ma_bool32 noAVX512 : 1;
ma_bool32 noNEON : 1;
ma_pcm_converter_read_proc onRead;
void* pUserData;
union
{
ma_src_config_sinc sinc;
};
} ma_pcm_converter_config;
struct ma_pcm_converter
{
ma_pcm_converter_read_proc onRead;
void* pUserData;
ma_format_converter formatConverterIn; /* For converting data to f32 in preparation for further processing. */
ma_format_converter formatConverterOut; /* For converting data to the requested output format. Used as the final step in the processing pipeline. */
ma_channel_router channelRouter; /* For channel conversion. */
ma_src src; /* For sample rate conversion. */
ma_bool32 isDynamicSampleRateAllowed : 1; /* ma_pcm_converter_set_input_sample_rate() and ma_pcm_converter_set_output_sample_rate() will fail if this is set to false. */
ma_bool32 isPreFormatConversionRequired : 1;
ma_bool32 isPostFormatConversionRequired : 1;
ma_bool32 isChannelRoutingRequired : 1;
ma_bool32 isSRCRequired : 1;
ma_bool32 isChannelRoutingAtStart : 1;
ma_bool32 isPassthrough : 1; /* <-- Will be set to true when the conversion pipeline is an optimized passthrough. */
};
/************************************************************************************************************************************************************ /************************************************************************************************************************************************************
************************************************************************************************************************************************************* *************************************************************************************************************************************************************
...@@ -1895,54 +1848,6 @@ Conversion ...@@ -1895,54 +1848,6 @@ Conversion
************************************************************************************************************************************************************/ ************************************************************************************************************************************************************/
/*
Initializes a DSP object.
*/
ma_result ma_pcm_converter_init(const ma_pcm_converter_config* pConfig, ma_pcm_converter* pDSP);
/*
Dynamically adjusts the input sample rate.
This will fail is the DSP was not initialized with allowDynamicSampleRate.
DEPRECATED. Use ma_pcm_converter_set_sample_rate() instead.
*/
ma_result ma_pcm_converter_set_input_sample_rate(ma_pcm_converter* pDSP, ma_uint32 sampleRateOut);
/*
Dynamically adjusts the output sample rate.
This is useful for dynamically adjust pitch. Keep in mind, however, that this will speed up or slow down the sound. If this
is not acceptable you will need to use your own algorithm.
This will fail is the DSP was not initialized with allowDynamicSampleRate.
DEPRECATED. Use ma_pcm_converter_set_sample_rate() instead.
*/
ma_result ma_pcm_converter_set_output_sample_rate(ma_pcm_converter* pDSP, ma_uint32 sampleRateOut);
/*
Dynamically adjusts the output sample rate.
This is useful for dynamically adjust pitch. Keep in mind, however, that this will speed up or slow down the sound. If this
is not acceptable you will need to use your own algorithm.
This will fail if the DSP was not initialized with allowDynamicSampleRate.
*/
ma_result ma_pcm_converter_set_sample_rate(ma_pcm_converter* pDSP, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut);
/*
Reads a number of frames and runs them through the DSP processor.
*/
ma_uint64 ma_pcm_converter_read(ma_pcm_converter* pDSP, void* pFramesOut, ma_uint64 frameCount);
/*
Helper for initializing a ma_pcm_converter_config object.
*/
ma_pcm_converter_config ma_pcm_converter_config_init_new(void);
ma_pcm_converter_config ma_pcm_converter_config_init(ma_format formatIn, ma_uint32 channelsIn, ma_uint32 sampleRateIn, ma_format formatOut, ma_uint32 channelsOut, ma_uint32 sampleRateOut, ma_pcm_converter_read_proc onRead, void* pUserData);
ma_pcm_converter_config ma_pcm_converter_config_init_ex(ma_format formatIn, ma_uint32 channelsIn, ma_uint32 sampleRateIn, ma_channel channelMapIn[MA_MAX_CHANNELS], ma_format formatOut, ma_uint32 channelsOut, ma_uint32 sampleRateOut, ma_channel channelMapOut[MA_MAX_CHANNELS], ma_pcm_converter_read_proc onRead, void* pUserData);
/* /*
High-level helper for doing a full format conversion in one go. Returns the number of output frames. Call this with pOut set to NULL to High-level helper for doing a full format conversion in one go. Returns the number of output frames. Call this with pOut set to NULL to
determine the required size of the output buffer. frameCountOut should be set to the capacity of pOut. If pOut is NULL, frameCountOut is determine the required size of the output buffer. frameCountOut should be set to the capacity of pOut. If pOut is NULL, frameCountOut is
...@@ -36122,471 +36027,6 @@ void ma_interleave_pcm_frames(ma_format format, ma_uint32 channels, ma_uint64 fr ...@@ -36122,471 +36027,6 @@ void ma_interleave_pcm_frames(ma_format format, ma_uint32 channels, ma_uint64 fr
} }
typedef struct
{
ma_pcm_converter* pDSP;
void* pUserDataForClient;
} ma_pcm_converter_callback_data;
ma_uint32 ma_pcm_converter__pre_format_converter_on_read(ma_format_converter* pConverter, ma_uint32 frameCount, void* pFramesOut, void* pUserData)
{
ma_pcm_converter_callback_data* pData;
ma_pcm_converter* pDSP;
(void)pConverter;
pData = (ma_pcm_converter_callback_data*)pUserData;
ma_assert(pData != NULL);
pDSP = pData->pDSP;
ma_assert(pDSP != NULL);
return pDSP->onRead(pDSP, pFramesOut, frameCount, pData->pUserDataForClient);
}
ma_uint32 ma_pcm_converter__post_format_converter_on_read(ma_format_converter* pConverter, ma_uint32 frameCount, void* pFramesOut, void* pUserData)
{
ma_pcm_converter_callback_data* pData;
ma_pcm_converter* pDSP;
(void)pConverter;
pData = (ma_pcm_converter_callback_data*)pUserData;
ma_assert(pData != NULL);
pDSP = pData->pDSP;
ma_assert(pDSP != NULL);
/* When this version of this callback is used it means we're reading directly from the client. */
ma_assert(pDSP->isPreFormatConversionRequired == MA_FALSE);
ma_assert(pDSP->isChannelRoutingRequired == MA_FALSE);
ma_assert(pDSP->isSRCRequired == MA_FALSE);
return pDSP->onRead(pDSP, pFramesOut, frameCount, pData->pUserDataForClient);
}
ma_uint32 ma_pcm_converter__post_format_converter_on_read_deinterleaved(ma_format_converter* pConverter, ma_uint32 frameCount, void** ppSamplesOut, void* pUserData)
{
ma_pcm_converter_callback_data* pData;
ma_pcm_converter* pDSP;
(void)pConverter;
pData = (ma_pcm_converter_callback_data*)pUserData;
ma_assert(pData != NULL);
pDSP = pData->pDSP;
ma_assert(pDSP != NULL);
if (!pDSP->isChannelRoutingAtStart) {
return (ma_uint32)ma_channel_router_read_deinterleaved(&pDSP->channelRouter, frameCount, ppSamplesOut, pUserData);
} else {
if (pDSP->isSRCRequired) {
return (ma_uint32)ma_src_read_deinterleaved(&pDSP->src, frameCount, ppSamplesOut, pUserData);
} else {
return (ma_uint32)ma_channel_router_read_deinterleaved(&pDSP->channelRouter, frameCount, ppSamplesOut, pUserData);
}
}
}
ma_uint32 ma_pcm_converter__src_on_read_deinterleaved(ma_src* pSRC, ma_uint32 frameCount, void** ppSamplesOut, void* pUserData)
{
ma_pcm_converter_callback_data* pData;
ma_pcm_converter* pDSP;
(void)pSRC;
pData = (ma_pcm_converter_callback_data*)pUserData;
ma_assert(pData != NULL);
pDSP = pData->pDSP;
ma_assert(pDSP != NULL);
/* If the channel routing stage is at the front we need to read from that. Otherwise we read from the pre format converter. */
if (pDSP->isChannelRoutingAtStart) {
return (ma_uint32)ma_channel_router_read_deinterleaved(&pDSP->channelRouter, frameCount, ppSamplesOut, pUserData);
} else {
return (ma_uint32)ma_format_converter_read_deinterleaved(&pDSP->formatConverterIn, frameCount, ppSamplesOut, pUserData);
}
}
ma_uint32 ma_pcm_converter__channel_router_on_read_deinterleaved(ma_channel_router* pRouter, ma_uint32 frameCount, void** ppSamplesOut, void* pUserData)
{
ma_pcm_converter_callback_data* pData;
ma_pcm_converter* pDSP;
(void)pRouter;
pData = (ma_pcm_converter_callback_data*)pUserData;
ma_assert(pData != NULL);
pDSP = pData->pDSP;
ma_assert(pDSP != NULL);
/* If the channel routing stage is at the front of the pipeline we read from the pre format converter. Otherwise we read from the sample rate converter. */
if (pDSP->isChannelRoutingAtStart) {
return (ma_uint32)ma_format_converter_read_deinterleaved(&pDSP->formatConverterIn, frameCount, ppSamplesOut, pUserData);
} else {
if (pDSP->isSRCRequired) {
return (ma_uint32)ma_src_read_deinterleaved(&pDSP->src, frameCount, ppSamplesOut, pUserData);
} else {
return (ma_uint32)ma_format_converter_read_deinterleaved(&pDSP->formatConverterIn, frameCount, ppSamplesOut, pUserData);
}
}
}
ma_result ma_pcm_converter_init(const ma_pcm_converter_config* pConfig, ma_pcm_converter* pDSP)
{
ma_result result;
if (pDSP == NULL) {
return MA_INVALID_ARGS;
}
ma_zero_object(pDSP);
pDSP->onRead = pConfig->onRead;
pDSP->pUserData = pConfig->pUserData;
pDSP->isDynamicSampleRateAllowed = pConfig->allowDynamicSampleRate;
/*
In general, this is the pipeline used for data conversion. Note that this can actually change which is explained later.
Pre Format Conversion -> Sample Rate Conversion -> Channel Routing -> Post Format Conversion
Pre Format Conversion
---------------------
This is where the sample data is converted to a format that's usable by the later stages in the pipeline. Input data
is converted to deinterleaved floating-point.
Channel Routing
---------------
Channel routing is where stereo is converted to 5.1, mono is converted to stereo, etc. This stage depends on the
pre format conversion stage.
Sample Rate Conversion
----------------------
Sample rate conversion depends on the pre format conversion stage and as the name implies performs sample rate conversion.
Post Format Conversion
----------------------
This stage is where our deinterleaved floating-point data from the previous stages are converted to the requested output
format.
Optimizations
-------------
Sometimes the conversion pipeline is rearranged for efficiency. The first obvious optimization is to eliminate unnecessary
stages in the pipeline. When no channel routing nor sample rate conversion is necessary, the entire pipeline is optimized
down to just this:
Post Format Conversion
When sample rate conversion is not unnecessary:
Pre Format Conversion -> Channel Routing -> Post Format Conversion
When channel routing is unnecessary:
Pre Format Conversion -> Sample Rate Conversion -> Post Format Conversion
A slightly less obvious optimization is used depending on whether or not we are increasing or decreasing the number of
channels. Because everything in the pipeline works on a per-channel basis, the efficiency of the pipeline is directly
proportionate to the number of channels that need to be processed. Therefore, it's can be more efficient to move the
channel conversion stage to an earlier or later stage. When the channel count is being reduced, we move the channel
conversion stage to the start of the pipeline so that later stages can work on a smaller number of channels at a time.
Otherwise, we move the channel conversion stage to the end of the pipeline. When reducing the channel count, the pipeline
will look like this:
Pre Format Conversion -> Channel Routing -> Sample Rate Conversion -> Post Format Conversion
Notice how the Channel Routing and Sample Rate Conversion stages are swapped so that the SRC stage has less data to process.
*/
/* First we need to determine what's required and what's not. */
if (pConfig->sampleRateIn != pConfig->sampleRateOut || pConfig->allowDynamicSampleRate) {
pDSP->isSRCRequired = MA_TRUE;
}
if (pConfig->channelsIn != pConfig->channelsOut || !ma_channel_map_equal(pConfig->channelsIn, pConfig->channelMapIn, pConfig->channelMapOut)) {
pDSP->isChannelRoutingRequired = MA_TRUE;
}
/* If neither a sample rate conversion nor channel conversion is necessary we can skip the pre format conversion. */
if (!pDSP->isSRCRequired && !pDSP->isChannelRoutingRequired) {
/* We don't need a pre format conversion stage, but we may still need a post format conversion stage. */
if (pConfig->formatIn != pConfig->formatOut) {
pDSP->isPostFormatConversionRequired = MA_TRUE;
}
} else {
pDSP->isPreFormatConversionRequired = MA_TRUE;
pDSP->isPostFormatConversionRequired = MA_TRUE;
}
/* Use a passthrough if none of the stages are being used. */
if (!pDSP->isPreFormatConversionRequired && !pDSP->isPostFormatConversionRequired && !pDSP->isChannelRoutingRequired && !pDSP->isSRCRequired) {
pDSP->isPassthrough = MA_TRUE;
}
/* Move the channel conversion stage to the start of the pipeline if we are reducing the channel count. */
if (pConfig->channelsOut < pConfig->channelsIn) {
pDSP->isChannelRoutingAtStart = MA_TRUE;
}
/*
We always initialize every stage of the pipeline regardless of whether or not the stage is used because it simplifies
a few things when it comes to dynamically changing properties post-initialization.
*/
result = MA_SUCCESS;
/* Pre format conversion. */
{
ma_format_converter_config preFormatConverterConfig = ma_format_converter_config_init(
pConfig->formatIn,
ma_format_f32,
pConfig->channelsIn,
ma_pcm_converter__pre_format_converter_on_read,
pDSP
);
preFormatConverterConfig.ditherMode = pConfig->ditherMode;
preFormatConverterConfig.noSSE2 = pConfig->noSSE2;
preFormatConverterConfig.noAVX2 = pConfig->noAVX2;
preFormatConverterConfig.noAVX512 = pConfig->noAVX512;
preFormatConverterConfig.noNEON = pConfig->noNEON;
result = ma_format_converter_init(&preFormatConverterConfig, &pDSP->formatConverterIn);
if (result != MA_SUCCESS) {
return result;
}
}
/*
Post format conversion. The exact configuration for this depends on whether or not we are reading data directly from the client
or from an earlier stage in the pipeline.
*/
{
ma_format_converter_config postFormatConverterConfig = ma_format_converter_config_init_new();
postFormatConverterConfig.formatIn = pConfig->formatIn;
postFormatConverterConfig.formatOut = pConfig->formatOut;
postFormatConverterConfig.channels = pConfig->channelsOut;
postFormatConverterConfig.ditherMode = pConfig->ditherMode;
postFormatConverterConfig.noSSE2 = pConfig->noSSE2;
postFormatConverterConfig.noAVX2 = pConfig->noAVX2;
postFormatConverterConfig.noAVX512 = pConfig->noAVX512;
postFormatConverterConfig.noNEON = pConfig->noNEON;
if (pDSP->isPreFormatConversionRequired) {
postFormatConverterConfig.onReadDeinterleaved = ma_pcm_converter__post_format_converter_on_read_deinterleaved;
postFormatConverterConfig.formatIn = ma_format_f32;
} else {
postFormatConverterConfig.onRead = ma_pcm_converter__post_format_converter_on_read;
}
result = ma_format_converter_init(&postFormatConverterConfig, &pDSP->formatConverterOut);
if (result != MA_SUCCESS) {
return result;
}
}
/* SRC */
{
ma_src_config srcConfig = ma_src_config_init(
pConfig->sampleRateIn,
pConfig->sampleRateOut,
((pConfig->channelsIn < pConfig->channelsOut) ? pConfig->channelsIn : pConfig->channelsOut),
ma_pcm_converter__src_on_read_deinterleaved,
pDSP
);
srcConfig.algorithm = pConfig->srcAlgorithm;
srcConfig.neverConsumeEndOfInput = pConfig->neverConsumeEndOfInput;
srcConfig.noSSE2 = pConfig->noSSE2;
srcConfig.noAVX2 = pConfig->noAVX2;
srcConfig.noAVX512 = pConfig->noAVX512;
srcConfig.noNEON = pConfig->noNEON;
ma_copy_memory(&srcConfig.sinc, &pConfig->sinc, sizeof(pConfig->sinc));
result = ma_src_init(&srcConfig, &pDSP->src);
if (result != MA_SUCCESS) {
return result;
}
}
/* Channel conversion */
{
ma_channel_router_config routerConfig = ma_channel_router_config_init(
pConfig->channelsIn,
pConfig->channelMapIn,
pConfig->channelsOut,
pConfig->channelMapOut,
pConfig->channelMixMode,
ma_pcm_converter__channel_router_on_read_deinterleaved,
pDSP);
routerConfig.noSSE2 = pConfig->noSSE2;
routerConfig.noAVX2 = pConfig->noAVX2;
routerConfig.noAVX512 = pConfig->noAVX512;
routerConfig.noNEON = pConfig->noNEON;
result = ma_channel_router_init(&routerConfig, &pDSP->channelRouter);
if (result != MA_SUCCESS) {
return result;
}
}
return MA_SUCCESS;
}
ma_result ma_pcm_converter_refresh_sample_rate(ma_pcm_converter* pDSP)
{
/* The SRC stage will already have been initialized so we can just set it there. */
ma_src_set_sample_rate(&pDSP->src, pDSP->src.config.sampleRateIn, pDSP->src.config.sampleRateOut);
return MA_SUCCESS;
}
ma_result ma_pcm_converter_set_input_sample_rate(ma_pcm_converter* pDSP, ma_uint32 sampleRateIn)
{
if (pDSP == NULL) {
return MA_INVALID_ARGS;
}
/* Must have a sample rate of > 0. */
if (sampleRateIn == 0) {
return MA_INVALID_ARGS;
}
/* Must have been initialized with allowDynamicSampleRate. */
if (!pDSP->isDynamicSampleRateAllowed) {
return MA_INVALID_OPERATION;
}
ma_atomic_exchange_32(&pDSP->src.config.sampleRateIn, sampleRateIn);
return ma_pcm_converter_refresh_sample_rate(pDSP);
}
ma_result ma_pcm_converter_set_output_sample_rate(ma_pcm_converter* pDSP, ma_uint32 sampleRateOut)
{
if (pDSP == NULL) {
return MA_INVALID_ARGS;
}
/* Must have a sample rate of > 0. */
if (sampleRateOut == 0) {
return MA_INVALID_ARGS;
}
/* Must have been initialized with allowDynamicSampleRate. */
if (!pDSP->isDynamicSampleRateAllowed) {
return MA_INVALID_OPERATION;
}
ma_atomic_exchange_32(&pDSP->src.config.sampleRateOut, sampleRateOut);
return ma_pcm_converter_refresh_sample_rate(pDSP);
}
ma_result ma_pcm_converter_set_sample_rate(ma_pcm_converter* pDSP, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut)
{
if (pDSP == NULL) {
return MA_INVALID_ARGS;
}
/* Must have a sample rate of > 0. */
if (sampleRateIn == 0 || sampleRateOut == 0) {
return MA_INVALID_ARGS;
}
/* Must have been initialized with allowDynamicSampleRate. */
if (!pDSP->isDynamicSampleRateAllowed) {
return MA_INVALID_OPERATION;
}
ma_atomic_exchange_32(&pDSP->src.config.sampleRateIn, sampleRateIn);
ma_atomic_exchange_32(&pDSP->src.config.sampleRateOut, sampleRateOut);
return ma_pcm_converter_refresh_sample_rate(pDSP);
}
ma_uint64 ma_pcm_converter_read(ma_pcm_converter* pDSP, void* pFramesOut, ma_uint64 frameCount)
{
ma_pcm_converter_callback_data data;
if (pDSP == NULL || pFramesOut == NULL) {
return 0;
}
/* Fast path. */
if (pDSP->isPassthrough) {
if (frameCount <= 0xFFFFFFFF) {
return (ma_uint32)pDSP->onRead(pDSP, pFramesOut, (ma_uint32)frameCount, pDSP->pUserData);
} else {
ma_uint8* pNextFramesOut = (ma_uint8*)pFramesOut;
ma_uint64 totalFramesRead = 0;
while (totalFramesRead < frameCount) {
ma_uint32 framesRead;
ma_uint64 framesRemaining = (frameCount - totalFramesRead);
ma_uint64 framesToReadRightNow = framesRemaining;
if (framesToReadRightNow > 0xFFFFFFFF) {
framesToReadRightNow = 0xFFFFFFFF;
}
framesRead = pDSP->onRead(pDSP, pNextFramesOut, (ma_uint32)framesToReadRightNow, pDSP->pUserData);
if (framesRead == 0) {
break;
}
pNextFramesOut += framesRead * pDSP->channelRouter.config.channelsOut * ma_get_bytes_per_sample(pDSP->formatConverterOut.config.formatOut);
totalFramesRead += framesRead;
}
return totalFramesRead;
}
}
/* Slower path. The real work is done here. To do this all we need to do is read from the last stage in the pipeline. */
ma_assert(pDSP->isPostFormatConversionRequired == MA_TRUE);
data.pDSP = pDSP;
data.pUserDataForClient = pDSP->pUserData;
return ma_format_converter_read(&pDSP->formatConverterOut, frameCount, pFramesOut, &data);
}
ma_pcm_converter_config ma_pcm_converter_config_init_new()
{
ma_pcm_converter_config config;
ma_zero_object(&config);
return config;
}
ma_pcm_converter_config ma_pcm_converter_config_init(ma_format formatIn, ma_uint32 channelsIn, ma_uint32 sampleRateIn, ma_format formatOut, ma_uint32 channelsOut, ma_uint32 sampleRateOut, ma_pcm_converter_read_proc onRead, void* pUserData)
{
return ma_pcm_converter_config_init_ex(formatIn, channelsIn, sampleRateIn, NULL, formatOut, channelsOut, sampleRateOut, NULL, onRead, pUserData);
}
ma_pcm_converter_config ma_pcm_converter_config_init_ex(ma_format formatIn, ma_uint32 channelsIn, ma_uint32 sampleRateIn, ma_channel channelMapIn[MA_MAX_CHANNELS], ma_format formatOut, ma_uint32 channelsOut, ma_uint32 sampleRateOut, ma_channel channelMapOut[MA_MAX_CHANNELS], ma_pcm_converter_read_proc onRead, void* pUserData)
{
ma_pcm_converter_config config;
ma_zero_object(&config);
config.formatIn = formatIn;
config.channelsIn = channelsIn;
config.sampleRateIn = sampleRateIn;
config.formatOut = formatOut;
config.channelsOut = channelsOut;
config.sampleRateOut = sampleRateOut;
if (channelMapIn != NULL) {
ma_copy_memory(config.channelMapIn, channelMapIn, sizeof(config.channelMapIn));
}
if (channelMapOut != NULL) {
ma_copy_memory(config.channelMapOut, channelMapOut, sizeof(config.channelMapOut));
}
config.onRead = onRead;
config.pUserData = pUserData;
return config;
}
ma_uint64 ma_convert_frames(void* pOut, ma_uint64 frameCountOut, ma_format formatOut, ma_uint32 channelsOut, ma_uint32 sampleRateOut, const void* pIn, ma_uint64 frameCountIn, ma_format formatIn, ma_uint32 channelsIn, ma_uint32 sampleRateIn) ma_uint64 ma_convert_frames(void* pOut, ma_uint64 frameCountOut, ma_format formatOut, ma_uint32 channelsOut, ma_uint32 sampleRateOut, const void* pIn, ma_uint64 frameCountIn, ma_format formatIn, ma_uint32 channelsIn, ma_uint32 sampleRateIn)
{ {
ma_data_converter_config config; ma_data_converter_config config;
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