mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-22 00:06:59 +02:00
Stop pre-converting input channel counts to output channel counts.
This commit is contained in:
+43
-59
@@ -489,10 +489,10 @@ need to make sure the `ma_node_base` object is your first member of the structur
|
|||||||
By doing this, your object will be compatible with all `ma_node` APIs and you can attach it to the
|
By doing this, your object will be compatible with all `ma_node` APIs and you can attach it to the
|
||||||
graph just like any other node.
|
graph just like any other node.
|
||||||
|
|
||||||
In the custom processing callback (`my_custom_node_process_pcm_frames()` in the example above), all
|
In the custom processing callback (`my_custom_node_process_pcm_frames()` in the example above), the
|
||||||
data will be in the output channel count, including data in the `ppFramesIn` buffers. This will be
|
number of channels for each bus is what as specified by the config when the not was initialized
|
||||||
pre-converted for you by miniaudio. In addition, all attachments to each of the input buses will
|
with `ma_node_init()` In addition, all attachments to each of the input buses will have been
|
||||||
have been pre-mixed by miniaudio.
|
pre-mixed by miniaudio.
|
||||||
|
|
||||||
The example above uses the simple version of the callback. There's an extended version of the
|
The example above uses the simple version of the callback. There's an extended version of the
|
||||||
callback that is more complicated, but necessary for effects that do resampling. This version takes
|
callback that is more complicated, but necessary for effects that do resampling. This version takes
|
||||||
@@ -869,7 +869,7 @@ typedef struct
|
|||||||
ma_node_config nodeConfig;
|
ma_node_config nodeConfig;
|
||||||
} ma_splitter_node_config;
|
} ma_splitter_node_config;
|
||||||
|
|
||||||
MA_API ma_splitter_node_config ma_splitter_node_config_init(ma_uint32 inputChannels, ma_uint32 outputChannels);
|
MA_API ma_splitter_node_config ma_splitter_node_config_init(ma_uint32 channels);
|
||||||
|
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
@@ -2628,8 +2628,11 @@ static void ma_node_graph_endpoint_process_pcm_frames(ma_node* pNode, float** pp
|
|||||||
ma_node_base* pNodeBase = (ma_node_base*)pNode;
|
ma_node_base* pNodeBase = (ma_node_base*)pNode;
|
||||||
|
|
||||||
MA_ASSERT(pNodeBase != NULL);
|
MA_ASSERT(pNodeBase != NULL);
|
||||||
|
MA_ASSERT(ma_node_get_input_bus_count(pNodeBase) == 1);
|
||||||
MA_ASSERT(ma_node_get_output_bus_count(pNodeBase) == 1);
|
MA_ASSERT(ma_node_get_output_bus_count(pNodeBase) == 1);
|
||||||
MA_ASSERT(ma_node_get_output_bus_count(pNodeBase) == 1);
|
|
||||||
|
/* Input channel count needs to be the same as the output channel count. */
|
||||||
|
MA_ASSERT(ma_node_get_input_channels(pNodeBase) == ma_node_get_output_channels(pNodeBase));
|
||||||
|
|
||||||
/* The data has already been mixed. We just need to move it to the output buffer. */
|
/* The data has already been mixed. We just need to move it to the output buffer. */
|
||||||
ma_copy_pcm_frames(ppFramesOut[0], ppFramesIn[0], *pFrameCount, ma_format_f32, ma_node_get_output_channels(pNodeBase));
|
ma_copy_pcm_frames(ppFramesOut[0], ppFramesIn[0], *pFrameCount, ma_format_f32, ma_node_get_output_channels(pNodeBase));
|
||||||
@@ -2942,6 +2945,7 @@ static ma_result ma_node_input_bus_read_pcm_frames(ma_node* pInputNode, ma_node_
|
|||||||
{
|
{
|
||||||
ma_result result = MA_SUCCESS;
|
ma_result result = MA_SUCCESS;
|
||||||
ma_node_output_bus* pOutputBus;
|
ma_node_output_bus* pOutputBus;
|
||||||
|
ma_uint32 inputChannels;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This will be called from the audio thread which means we can't be doing any locking. Basically,
|
This will be called from the audio thread which means we can't be doing any locking. Basically,
|
||||||
@@ -2960,14 +2964,16 @@ static ma_result ma_node_input_bus_read_pcm_frames(ma_node* pInputNode, ma_node_
|
|||||||
|
|
||||||
*pFramesRead = 0; /* Safety. */
|
*pFramesRead = 0; /* Safety. */
|
||||||
|
|
||||||
|
inputChannels = ma_node_get_input_channels(pInputNode);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
A few optimizations here:
|
A few optimizations here:
|
||||||
|
|
||||||
1) If there's only a single attachment we just write directly to the output buffer.
|
1) If there's only a single attachment we just write directly to the output buffer.
|
||||||
2) For the first attachment, we do not mix. Instead we just overwrite.
|
2) For the first attachment, we do not mix. Instead we just overwrite.
|
||||||
*/
|
*/
|
||||||
if (ma_node_input_bus_next(pInputBus, ma_node_input_bus_first(pInputBus)) == NULL && ma_node_get_input_channels(pInputNode) == ma_node_get_output_channels(pInputNode)) {
|
if (ma_node_input_bus_next(pInputBus, ma_node_input_bus_first(pInputBus)) == NULL) {
|
||||||
/* Fast path. There's only one attachment and no channel conversion. Read straight into the output buffer. */
|
/* Fast path. There's only one attachment Read straight into the output buffer. */
|
||||||
pOutputBus = ma_node_input_bus_first(pInputBus);
|
pOutputBus = ma_node_input_bus_first(pInputBus);
|
||||||
if (pOutputBus != NULL) {
|
if (pOutputBus != NULL) {
|
||||||
/* Fast path. Input and output channel counts are the same. No conversion necessary. */
|
/* Fast path. Input and output channel counts are the same. No conversion necessary. */
|
||||||
@@ -2983,28 +2989,19 @@ static ma_result ma_node_input_bus_read_pcm_frames(ma_node* pInputNode, ma_node_
|
|||||||
if (result == MA_SUCCESS) {
|
if (result == MA_SUCCESS) {
|
||||||
/* Any frames that were not read need to be filled with silence. */
|
/* Any frames that were not read need to be filled with silence. */
|
||||||
if (framesRead < frameCount) {
|
if (framesRead < frameCount) {
|
||||||
ma_silence_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, framesRead, ma_format_f32, ma_node_get_output_channels(pInputNode)), (frameCount - framesRead), ma_format_f32, ma_node_get_output_channels(pInputNode));
|
ma_silence_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, framesRead, ma_format_f32, inputChannels), (frameCount - framesRead), ma_format_f32, inputChannels);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Apply volume, if necessary. */
|
/* Apply volume, if necessary. */
|
||||||
if (volume > 0 && volume != 1) {
|
if (volume > 0 && volume != 1) {
|
||||||
ma_apply_volume_factor_f32(pFramesOut, framesRead * ma_node_get_output_channels(pInputNode), volume);
|
ma_apply_volume_factor_f32(pFramesOut, framesRead * inputChannels, volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
*pFramesRead = framesRead;
|
*pFramesRead = framesRead;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* Slow path. There's multiple output buses or channel conversion is required. We need to mix before outputting. pFramesOut is the accumulation buffer. */
|
/* Slow path. There's multiple output buses or channel conversion is required. We need to mix before outputting. pFramesOut is the accumulation buffer. */
|
||||||
|
|
||||||
/*
|
|
||||||
In order to reduce the amount of computation we'll be doing for volume control, we're going
|
|
||||||
to be smart about when we apply the volume. If we're increasing the number of channels,
|
|
||||||
we'll want to apply the volume factor before channel conversion. Otherwise we'll want to do
|
|
||||||
it after.
|
|
||||||
*/
|
|
||||||
ma_bool32 applyVolumeInPost = ma_node_get_input_channels(pInputNode) >= ma_node_get_output_channels(pInputNode);
|
|
||||||
|
|
||||||
for (pOutputBus = ma_node_input_bus_first(pInputBus); pOutputBus != NULL; pOutputBus = ma_node_input_bus_next(pInputBus, pOutputBus)) {
|
for (pOutputBus = ma_node_input_bus_first(pInputBus); pOutputBus != NULL; pOutputBus = ma_node_input_bus_next(pInputBus, pOutputBus)) {
|
||||||
ma_uint32 framesProcessed = 0;
|
ma_uint32 framesProcessed = 0;
|
||||||
|
|
||||||
@@ -3013,7 +3010,7 @@ static ma_result ma_node_input_bus_read_pcm_frames(ma_node* pInputNode, ma_node_
|
|||||||
if (pFramesOut != NULL) {
|
if (pFramesOut != NULL) {
|
||||||
/* Read. */
|
/* Read. */
|
||||||
float temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE / sizeof(float)];
|
float temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE / sizeof(float)];
|
||||||
ma_uint32 tempCapInFrames = ma_countof(temp) / ma_node_get_input_channels(pInputNode);
|
ma_uint32 tempCapInFrames = ma_countof(temp) / inputChannels;
|
||||||
float volume = ma_node_output_bus_get_volume(pOutputBus);
|
float volume = ma_node_output_bus_get_volume(pOutputBus);
|
||||||
|
|
||||||
while (framesProcessed < frameCount) {
|
while (framesProcessed < frameCount) {
|
||||||
@@ -3026,36 +3023,16 @@ static ma_result ma_node_input_bus_read_pcm_frames(ma_node* pInputNode, ma_node_
|
|||||||
framesToRead = tempCapInFrames;
|
framesToRead = tempCapInFrames;
|
||||||
}
|
}
|
||||||
|
|
||||||
pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(pFramesOut, framesProcessed, ma_node_get_output_channels(pInputNode));
|
pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(pFramesOut, framesProcessed, inputChannels);
|
||||||
|
|
||||||
/* If we're not doing channel conversion we can run on a slightly faster path. */
|
|
||||||
if (ma_node_get_input_channels(pInputNode) == ma_node_get_output_channels(pInputNode)) {
|
|
||||||
/* Fast path. No channel conversion. */
|
|
||||||
if (pOutputBus == ma_node_input_bus_first(pInputBus)) {
|
if (pOutputBus == ma_node_input_bus_first(pInputBus)) {
|
||||||
/* Fast path. First attachment. We just read straight into the output buffer (no mixing or channel conversion required). */
|
/* Fast path. First attachment. We just read straight into the output buffer (no mixing required). */
|
||||||
result = ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, pRunningFramesOut, framesToRead, &framesJustRead);
|
result = ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, pRunningFramesOut, framesToRead, &framesJustRead);
|
||||||
} else {
|
} else {
|
||||||
/* Slow path. Not the first attachment. Mixing required, but no channel conversion. */
|
/* Slow path. Not the first attachment. Mixing required. */
|
||||||
result = ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, temp, framesToRead, &framesJustRead);
|
result = ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, temp, framesToRead, &framesJustRead);
|
||||||
if (result == MA_SUCCESS || result == MA_AT_END) {
|
if (result == MA_SUCCESS || result == MA_AT_END) {
|
||||||
ma_mix_pcm_frames_f32(pRunningFramesOut, temp, framesJustRead, ma_node_get_output_channels(pInputNode), /*volume*/1);
|
ma_mix_pcm_frames_f32(pRunningFramesOut, temp, framesJustRead, inputChannels, /*volume*/1);
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* Slow path. Channel conversion required. */
|
|
||||||
result = ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, temp, framesToRead, &framesJustRead);
|
|
||||||
if (result == MA_SUCCESS || result == MA_AT_END) {
|
|
||||||
if (pOutputBus == ma_node_input_bus_first(pInputBus)) {
|
|
||||||
/* Fast path. First attachment. Channel conversion required, but no mixing. */
|
|
||||||
ma_convert_pcm_frames_channels_f32(pRunningFramesOut, ma_node_get_output_channels(pInputNode), temp, ma_node_get_input_channels(pInputNode), framesJustRead);
|
|
||||||
|
|
||||||
if (volume != 0 && !applyVolumeInPost) {
|
|
||||||
ma_apply_volume_factor_f32(pRunningFramesOut, framesJustRead * ma_node_get_output_channels(pInputNode), volume);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* Slow(est) path. Not the first attachment. Both channel conversion and mixing required. */
|
|
||||||
ma_convert_pcm_frames_channels_and_mix_f32(pRunningFramesOut, ma_node_get_output_channels(pInputNode), temp, ma_node_get_input_channels(pInputNode), framesJustRead, (volume != 1 && !applyVolumeInPost) ? volume : 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3069,12 +3046,12 @@ static ma_result ma_node_input_bus_read_pcm_frames(ma_node* pInputNode, ma_node_
|
|||||||
|
|
||||||
/* If it's the first attachment we didn't do any mixing. Any leftover samples need to be silenced. */
|
/* If it's the first attachment we didn't do any mixing. Any leftover samples need to be silenced. */
|
||||||
if (pOutputBus == ma_node_input_bus_first(pInputBus) && framesProcessed < frameCount) {
|
if (pOutputBus == ma_node_input_bus_first(pInputBus) && framesProcessed < frameCount) {
|
||||||
ma_silence_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, framesProcessed, ma_format_f32, ma_node_get_output_channels(pInputNode)), (frameCount - framesProcessed), ma_format_f32, ma_node_get_output_channels(pInputNode));
|
ma_silence_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, framesProcessed, ma_format_f32, inputChannels), (frameCount - framesProcessed), ma_format_f32, inputChannels);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Apply volume, if necessary. */
|
/* Apply volume, if necessary. */
|
||||||
if (volume != 1 && applyVolumeInPost) {
|
if (volume != 1) {
|
||||||
ma_apply_volume_factor_f32(pFramesOut, framesProcessed * ma_node_get_output_channels(pInputNode), volume);
|
ma_apply_volume_factor_f32(pFramesOut, framesProcessed * inputChannels, volume);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* Seek. */
|
/* Seek. */
|
||||||
@@ -3106,11 +3083,6 @@ MA_API ma_node_config ma_node_config_init(ma_node_vtable* vtable, ma_uint32 inpu
|
|||||||
|
|
||||||
static float* ma_node_get_cached_input_ptr(ma_node* pNode, ma_uint32 inputBusIndex)
|
static float* ma_node_get_cached_input_ptr(ma_node* pNode, ma_uint32 inputBusIndex)
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
NOTE: We store cached input data *after* it's been converted to the output channel count. If
|
|
||||||
you're reading this in the future and confused that we're using the output channel count here
|
|
||||||
instead of the input channel count, this is why. It's intentional.
|
|
||||||
*/
|
|
||||||
ma_node_base* pNodeBase = (ma_node_base*)pNode;
|
ma_node_base* pNodeBase = (ma_node_base*)pNode;
|
||||||
float* pBasePtr;
|
float* pBasePtr;
|
||||||
|
|
||||||
@@ -3118,7 +3090,7 @@ static float* ma_node_get_cached_input_ptr(ma_node* pNode, ma_uint32 inputBusInd
|
|||||||
|
|
||||||
/* Input data is stored at the front of the buffer. */
|
/* Input data is stored at the front of the buffer. */
|
||||||
pBasePtr = pNodeBase->pCachedData;
|
pBasePtr = pNodeBase->pCachedData;
|
||||||
return pBasePtr + (inputBusIndex * (pNodeBase->cachedDataCapInFramesPerBus * ma_node_get_output_channels(pNodeBase))); /* <-- Intentional use of output channel count here. Not a bug. */
|
return pBasePtr + (inputBusIndex * (pNodeBase->cachedDataCapInFramesPerBus * ma_node_get_input_channels(pNodeBase)));
|
||||||
}
|
}
|
||||||
|
|
||||||
static float* ma_node_get_cached_output_ptr(ma_node* pNode, ma_uint32 outputBusIndex)
|
static float* ma_node_get_cached_output_ptr(ma_node* pNode, ma_uint32 outputBusIndex)
|
||||||
@@ -3127,7 +3099,7 @@ static float* ma_node_get_cached_output_ptr(ma_node* pNode, ma_uint32 outputBusI
|
|||||||
float* pBasePtr;
|
float* pBasePtr;
|
||||||
|
|
||||||
/* Cached output data starts after the input data. */
|
/* Cached output data starts after the input data. */
|
||||||
pBasePtr = pNodeBase->pCachedData + (ma_node_get_input_channels(pNodeBase) * (pNodeBase->cachedDataCapInFramesPerBus * ma_node_get_output_channels(pNodeBase)));
|
pBasePtr = pNodeBase->pCachedData + (ma_node_get_input_bus_count(pNodeBase) * (pNodeBase->cachedDataCapInFramesPerBus * ma_node_get_input_channels(pNodeBase)));
|
||||||
return pBasePtr + (outputBusIndex * (pNodeBase->cachedDataCapInFramesPerBus * ma_node_get_output_channels(pNodeBase)));
|
return pBasePtr + (outputBusIndex * (pNodeBase->cachedDataCapInFramesPerBus * ma_node_get_output_channels(pNodeBase)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3195,10 +3167,15 @@ MA_API ma_result ma_node_init(ma_node_graph* pNodeGraph, const ma_node_config* p
|
|||||||
/* Fast path. No cache needed. */
|
/* Fast path. No cache needed. */
|
||||||
} else {
|
} else {
|
||||||
/* Slow path. Cache needed. */
|
/* Slow path. Cache needed. */
|
||||||
|
size_t cachedDataSizeInBytes = 0;
|
||||||
|
|
||||||
pNodeBase->cachedDataCapInFramesPerBus = MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS;
|
pNodeBase->cachedDataCapInFramesPerBus = MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS;
|
||||||
MA_ASSERT(pNodeBase->cachedDataCapInFramesPerBus <= 0xFFFF); /* Clamped to 16 bits. */
|
MA_ASSERT(pNodeBase->cachedDataCapInFramesPerBus <= 0xFFFF); /* Clamped to 16 bits. */
|
||||||
|
|
||||||
pNodeBase->pCachedData = (float*)ma_malloc(pNodeBase->cachedDataCapInFramesPerBus * (ma_node_get_input_bus_count(pNodeBase) + ma_node_get_output_bus_count(pNodeBase)) * ma_get_bytes_per_frame(ma_format_f32, ma_node_get_output_channels(pNodeBase)), pAllocationCallbacks);
|
cachedDataSizeInBytes += pNodeBase->cachedDataCapInFramesPerBus * ma_node_get_input_bus_count( pNodeBase) * ma_get_bytes_per_frame(ma_format_f32, ma_node_get_input_channels( pNodeBase));
|
||||||
|
cachedDataSizeInBytes += pNodeBase->cachedDataCapInFramesPerBus * ma_node_get_output_bus_count(pNodeBase) * ma_get_bytes_per_frame(ma_format_f32, ma_node_get_output_channels(pNodeBase));
|
||||||
|
|
||||||
|
pNodeBase->pCachedData = (float*)ma_malloc(cachedDataSizeInBytes, pAllocationCallbacks);
|
||||||
if (pNodeBase->pCachedData == NULL) {
|
if (pNodeBase->pCachedData == NULL) {
|
||||||
return MA_OUT_OF_MEMORY;
|
return MA_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
@@ -3611,7 +3588,7 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde
|
|||||||
|
|
||||||
/* Any leftover frames need to silenced for safety. */
|
/* Any leftover frames need to silenced for safety. */
|
||||||
if (framesRead < framesToRead) {
|
if (framesRead < framesToRead) {
|
||||||
ma_silence_pcm_frames(ppFramesIn[iInputBus] + (framesRead * ma_node_get_output_channels(pNodeBase)), (framesToRead - framesRead), ma_format_f32, ma_node_get_output_channels(pNodeBase));
|
ma_silence_pcm_frames(ppFramesIn[iInputBus] + (framesRead * ma_node_get_input_channels(pNodeBase)), (framesToRead - framesRead), ma_format_f32, ma_node_get_input_channels(pNodeBase));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3620,7 +3597,7 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde
|
|||||||
} else {
|
} else {
|
||||||
/* We don't need to read anything, but we do need to prepare our input frame pointers. */
|
/* We don't need to read anything, but we do need to prepare our input frame pointers. */
|
||||||
for (iInputBus = 0; iInputBus < inputBusCount; iInputBus += 1) {
|
for (iInputBus = 0; iInputBus < inputBusCount; iInputBus += 1) {
|
||||||
ppFramesIn[iInputBus] = ma_node_get_cached_input_ptr(pNode, iInputBus) + (pNodeBase->consumedFrameCountIn * ma_node_get_output_channels(pNodeBase)); /* This is the correct use of output channels. Not a bug. */
|
ppFramesIn[iInputBus] = ma_node_get_cached_input_ptr(pNode, iInputBus) + (pNodeBase->consumedFrameCountIn * ma_node_get_input_channels(pNodeBase));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3806,12 +3783,12 @@ MA_API ma_bool32 ma_data_source_node_is_looping(ma_data_source_node* pDataSource
|
|||||||
|
|
||||||
|
|
||||||
/* Splitter Node. */
|
/* Splitter Node. */
|
||||||
MA_API ma_splitter_node_config ma_splitter_node_config_init(ma_uint32 inputChannels, ma_uint32 outputChannels)
|
MA_API ma_splitter_node_config ma_splitter_node_config_init(ma_uint32 channels)
|
||||||
{
|
{
|
||||||
ma_splitter_node_config config;
|
ma_splitter_node_config config;
|
||||||
|
|
||||||
MA_ZERO_OBJECT(&config);
|
MA_ZERO_OBJECT(&config);
|
||||||
config.nodeConfig = ma_node_config_init(NULL, inputChannels, outputChannels);
|
config.nodeConfig = ma_node_config_init(NULL, channels, channels); /* Same channel count between inputs and outputs are required for splitters. */
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
@@ -3826,6 +3803,8 @@ static void ma_splitter_node_process_pcm_frames(ma_node* pNode, float** ppFrames
|
|||||||
MA_ASSERT(ma_node_get_input_bus_count(pNodeBase) == 1);
|
MA_ASSERT(ma_node_get_input_bus_count(pNodeBase) == 1);
|
||||||
MA_ASSERT(ma_node_get_output_bus_count(pNodeBase) >= 2);
|
MA_ASSERT(ma_node_get_output_bus_count(pNodeBase) >= 2);
|
||||||
|
|
||||||
|
/* NOTE: This assumes the same number of channels for all inputs and outputs. This was checked in ma_splitter_node_init(). */
|
||||||
|
|
||||||
/* Splitting is just copying the first input bus and copying it over to each output bus. */
|
/* Splitting is just copying the first input bus and copying it over to each output bus. */
|
||||||
for (iOutputBus = 0; iOutputBus < ma_node_get_output_bus_count(pNodeBase); iOutputBus += 1) {
|
for (iOutputBus = 0; iOutputBus < ma_node_get_output_bus_count(pNodeBase); iOutputBus += 1) {
|
||||||
ma_copy_pcm_frames(ppFramesOut[iOutputBus], ppFramesIn[0], *pFrameCount, ma_format_f32, ma_node_get_output_channels(pNodeBase));
|
ma_copy_pcm_frames(ppFramesOut[iOutputBus], ppFramesIn[0], *pFrameCount, ma_format_f32, ma_node_get_output_channels(pNodeBase));
|
||||||
@@ -3856,6 +3835,11 @@ MA_API ma_result ma_splitter_node_init(ma_node_graph* pNodeGraph, const ma_split
|
|||||||
return MA_INVALID_ARGS;
|
return MA_INVALID_ARGS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Splitters require the same number of channels between inputs and outputs. */
|
||||||
|
if (pConfig->nodeConfig.inputChannels != pConfig->nodeConfig.outputChannels) {
|
||||||
|
return MA_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
baseConfig = pConfig->nodeConfig;
|
baseConfig = pConfig->nodeConfig;
|
||||||
baseConfig.vtable = &g_ma_splitter_node_vtable;
|
baseConfig.vtable = &g_ma_splitter_node_vtable;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,146 @@
|
|||||||
|
#define MINIAUDIO_IMPLEMENTATION
|
||||||
|
#include "../miniaudio.h"
|
||||||
|
#include "miniaudio_engine.h"
|
||||||
|
|
||||||
|
ma_node_graph g_nodeGraph;
|
||||||
|
ma_data_source_node g_dataSourceNode;
|
||||||
|
ma_splitter_node g_splitterNode;
|
||||||
|
|
||||||
|
void data_callback(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount)
|
||||||
|
{
|
||||||
|
/* Read straight from our node graph. */
|
||||||
|
ma_node_graph_read_pcm_frames(&g_nodeGraph, pFramesOut, frameCount, NULL);
|
||||||
|
|
||||||
|
(void)pDevice; /* Unused. */
|
||||||
|
(void)pFramesIn; /* Unused. */
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
ma_result result;
|
||||||
|
ma_device_config deviceConfig;
|
||||||
|
ma_device device;
|
||||||
|
ma_decoder_config decoderConfig;
|
||||||
|
ma_decoder decoder;
|
||||||
|
ma_node_graph_config nodeGraphConfig;
|
||||||
|
ma_data_source_node_config dataSourceNodeConfig;
|
||||||
|
ma_splitter_node_config splitterNodeConfig;
|
||||||
|
|
||||||
|
if (argc <= 1) {
|
||||||
|
printf("No input file.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceConfig = ma_device_config_init(ma_device_type_playback);
|
||||||
|
deviceConfig.playback.format = ma_format_f32; /* The node graph API only supports f32. */
|
||||||
|
deviceConfig.playback.channels = 2;
|
||||||
|
deviceConfig.sampleRate = 48000;
|
||||||
|
deviceConfig.dataCallback = data_callback;
|
||||||
|
deviceConfig.pUserData = NULL;
|
||||||
|
|
||||||
|
result = ma_device_init(NULL, &deviceConfig, &device);
|
||||||
|
if (result != MA_SUCCESS) {
|
||||||
|
printf("Failed to initialize device.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set up the new graph before starting the device so that we have something to read from as soon
|
||||||
|
as the device requests data. It doesn't matter what order we do this, but I'm starting with the
|
||||||
|
data source node since it makes more logical sense to me to start with the start of the chain.
|
||||||
|
*/
|
||||||
|
nodeGraphConfig = ma_node_graph_config_init(device.playback.channels);
|
||||||
|
|
||||||
|
result = ma_node_graph_init(&nodeGraphConfig, NULL, &g_nodeGraph);
|
||||||
|
if (result != MA_SUCCESS) {
|
||||||
|
printf("Failed to initialize node graph.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
We want the decoder to use the same format as the device. This way we can keep the entire node
|
||||||
|
graph using the same format/channels/rate to avoid the need to do data conversion.
|
||||||
|
*/
|
||||||
|
decoderConfig = ma_decoder_config_init(device.playback.format, device.playback.channels, device.sampleRate);
|
||||||
|
|
||||||
|
result = ma_decoder_init_file(argv[1], &decoderConfig, &decoder);
|
||||||
|
if (result != MA_SUCCESS) {
|
||||||
|
printf("Failed to initialize decoder.");
|
||||||
|
ma_device_uninit(&device);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
dataSourceNodeConfig = ma_data_source_node_config_init(&decoder, MA_TRUE);
|
||||||
|
|
||||||
|
result = ma_data_source_node_init(&g_nodeGraph, &dataSourceNodeConfig, NULL, &g_dataSourceNode);
|
||||||
|
if (result != MA_SUCCESS) {
|
||||||
|
printf("Failed to initialize data source node.");
|
||||||
|
ma_decoder_uninit(&decoder);
|
||||||
|
ma_device_uninit(&device);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = ma_node_attach_to_output_node(&g_dataSourceNode, 0, ma_node_graph_get_endpoint(&g_nodeGraph), 0);
|
||||||
|
if (result != MA_SUCCESS) {
|
||||||
|
printf("Failed to attach node.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Splitter node. Note that we've already attached the data source node to another, so this section
|
||||||
|
will test that changing of attachments works as expected.
|
||||||
|
*/
|
||||||
|
splitterNodeConfig = ma_splitter_node_config_init(device.playback.channels);
|
||||||
|
|
||||||
|
result = ma_splitter_node_init(&g_nodeGraph, &splitterNodeConfig, NULL, &g_splitterNode);
|
||||||
|
if (result != MA_SUCCESS) {
|
||||||
|
printf("Failed to initialize splitter node.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Connect both outputs of the splitter to the endpoint for now. Later on we'll test effects and whatnot. */
|
||||||
|
ma_node_attach_to_output_node(&g_splitterNode, 0, ma_node_graph_get_endpoint(&g_nodeGraph), 0);
|
||||||
|
ma_node_attach_to_output_node(&g_splitterNode, 1, ma_node_graph_get_endpoint(&g_nodeGraph), 0);
|
||||||
|
|
||||||
|
/* Adjust the volume of the splitter node's endpoints. We'll just do it 50/50 so that both of them combine to reproduce the original signal at the endpoint. */
|
||||||
|
ma_node_set_output_bus_volume(&g_splitterNode, 0, 0.5f);
|
||||||
|
ma_node_set_output_bus_volume(&g_splitterNode, 1, 0.5f);
|
||||||
|
|
||||||
|
/* The data source needs to have it's connection changed from the endpoint to the splitter. */
|
||||||
|
ma_node_attach_to_output_node(&g_dataSourceNode, 0, &g_splitterNode, 0);
|
||||||
|
|
||||||
|
|
||||||
|
/* Stop the splitter node for testing. */
|
||||||
|
/*ma_node_set_state(&g_splitterNode, ma_node_state_stopped);*/
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Only start the device after our nodes have been set up. We passed in `deviceNode` as the user
|
||||||
|
data to the data callback so we need to make sure it's initialized before we start the device.
|
||||||
|
*/
|
||||||
|
result = ma_device_start(&device);
|
||||||
|
if (result != MA_SUCCESS) {
|
||||||
|
ma_device_uninit(&device);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Press Enter to quit...");
|
||||||
|
getchar();
|
||||||
|
|
||||||
|
|
||||||
|
/* Teardown. These are uninitialized in a weird order here just for demonstration. */
|
||||||
|
|
||||||
|
/* We should be able to safely destroy the node while the device is still running. */
|
||||||
|
ma_data_source_node_uninit(&g_dataSourceNode, NULL);
|
||||||
|
|
||||||
|
/* The device needs to be stopped before we uninitialize the node graph or else the device's callback will try referencing the node graph. */
|
||||||
|
ma_device_uninit(&device);
|
||||||
|
|
||||||
|
/* The node graph will be referenced by the device's data called so it needs to be uninitialized after the device has stopped. */
|
||||||
|
ma_node_graph_uninit(&g_nodeGraph, NULL);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user