Commit 366aa434 authored by David Reid's avatar David Reid

Relax restrictions on the maximum input and output buses for nodes.

Previously this was restricted to 2 input buses and 2 output buses, but
this has been lifted to 254. When the number exceeds 2, internal data
structures will be allocated on the heap, otherwise they'll use a local
array contained within the ma_node structure.

This commit changes the node configuration. Previously there was a
fixed sized array for specifying the channel counts for each bus. This
array must now be defined outside of the config by the caller. The
following config variables have been renamed:

  * inputChannels > pInputChannels
  * outputChannels > pOutputChannels

This commit also adds the ability to configure input and output bus
counts on a per-instance basis rather than via the node vtable. To do
this, set the bus count in the vtable to MA_NODE_BUS_COUNT_UNKNOWN.
This will tell miniaudio to look at the node config to determine the
bus count rather than the vtable. It's an error to specify this in the
node config if the vtable specifies anything other than
MA_NODE_BUS_COUNT_UNKNOWN.
parent db7a3dfd
...@@ -267,7 +267,7 @@ int main(int argc, char** argv) ...@@ -267,7 +267,7 @@ int main(int argc, char** argv)
self-managed job threads you would set the internal job thread count to zero. We're doing both internal and self-managed job threads you would set the internal job thread count to zero. We're doing both internal and
self-managed job threads in this example just for demonstration purposes. self-managed job threads in this example just for demonstration purposes.
*/ */
ma_thread_create(&jobThread, ma_thread_priority_default, 0, custom_job_thread, &resourceManager); ma_thread_create(&jobThread, ma_thread_priority_default, 0, custom_job_thread, &resourceManager, NULL);
/* Create each data source from the resource manager. Note that the caller is the owner. */ /* Create each data source from the resource manager. Note that the caller is the owner. */
for (iFile = 0; iFile < ma_countof(g_dataSources) && iFile < argc-1; iFile += 1) { for (iFile = 0; iFile < ma_countof(g_dataSources) && iFile < argc-1; iFile += 1) {
......
#include "ma_channel_combiner_node.h"
MA_API ma_channel_combiner_node_config ma_channel_combiner_node_config_init(ma_uint32 channels)
{
ma_channel_combiner_node_config config;
MA_ZERO_OBJECT(&config);
config.nodeConfig = ma_node_config_init(); /* Input and output channels will be set in ma_channel_combiner_node_init(). */
config.channels = channels;
return config;
}
static void ma_channel_combiner_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut)
{
ma_channel_combiner_node* pCombinerNode = (ma_channel_combiner_node*)pNode;
(void)pFrameCountIn;
ma_interleave_pcm_frames(ma_format_f32, ma_node_get_output_channels(pCombinerNode, 0), *pFrameCountOut, (const void**)ppFramesIn, (void*)ppFramesOut[0]);
}
static ma_node_vtable g_ma_channel_combiner_node_vtable =
{
ma_channel_combiner_node_process_pcm_frames,
NULL,
MA_NODE_BUS_COUNT_UNKNOWN, /* Input bus count is determined by the channel count and is unknown until the node instance is initialized. */
1, /* 1 output bus. */
0 /* Default flags. */
};
MA_API ma_result ma_channel_combiner_node_init(ma_node_graph* pNodeGraph, const ma_channel_combiner_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_channel_combiner_node* pCombinerNode)
{
ma_result result;
ma_node_config baseConfig;
ma_uint32 inputChannels[MA_MAX_NODE_BUS_COUNT];
ma_uint32 outputChannels[1];
ma_uint32 iChannel;
if (pCombinerNode == NULL) {
return MA_INVALID_ARGS;
}
MA_ZERO_OBJECT(pCombinerNode);
if (pConfig == NULL) {
return MA_INVALID_ARGS;
}
/* All input channels are mono. */
for (iChannel = 0; iChannel < pConfig->channels; iChannel += 1) {
inputChannels[iChannel] = 1;
}
outputChannels[0] = pConfig->channels;
baseConfig = pConfig->nodeConfig;
baseConfig.vtable = &g_ma_channel_combiner_node_vtable;
baseConfig.inputBusCount = pConfig->channels; /* The vtable has an unknown channel count, so must specify it here. */
baseConfig.pInputChannels = inputChannels;
baseConfig.pOutputChannels = outputChannels;
result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pCombinerNode->baseNode);
if (result != MA_SUCCESS) {
return result;
}
return MA_SUCCESS;
}
MA_API void ma_channel_combiner_node_uninit(ma_channel_combiner_node* pCombinerNode, const ma_allocation_callbacks* pAllocationCallbacks)
{
/* The base node is always uninitialized first. */
ma_node_uninit(pCombinerNode, pAllocationCallbacks);
}
\ No newline at end of file
/* Include ma_reverb_node.h after miniaudio.h */
#ifndef ma_channel_combiner_node_h
#define ma_channel_combiner_node_h
#ifdef __cplusplus
extern "C" {
#endif
typedef struct
{
ma_node_config nodeConfig;
ma_uint32 channels; /* The number of channels of the source, which will be the same as the output. Must be 1 or 2. The excite bus must always have one channel. */
} ma_channel_combiner_node_config;
MA_API ma_channel_combiner_node_config ma_channel_combiner_node_config_init(ma_uint32 channels);
typedef struct
{
ma_node_base baseNode;
} ma_channel_combiner_node;
MA_API ma_result ma_channel_combiner_node_init(ma_node_graph* pNodeGraph, const ma_channel_combiner_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_channel_combiner_node* pSeparatorNode);
MA_API void ma_channel_combiner_node_uninit(ma_channel_combiner_node* pSeparatorNode, const ma_allocation_callbacks* pAllocationCallbacks);
#ifdef __cplusplus
}
#endif
#endif /* ma_reverb_node_h */
/* The channel separtor example also demonstrates how to use the combiner. */
#include "../ma_channel_separator_node/ma_channel_separator_node_example.c"
#include "ma_channel_separator_node.h"
MA_API ma_channel_separator_node_config ma_channel_separator_node_config_init(ma_uint32 channels)
{
ma_channel_separator_node_config config;
MA_ZERO_OBJECT(&config);
config.nodeConfig = ma_node_config_init(); /* Input and output channels will be set in ma_channel_separator_node_init(). */
config.channels = channels;
return config;
}
static void ma_channel_separator_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut)
{
ma_channel_separator_node* pSplitterNode = (ma_channel_separator_node*)pNode;
(void)pFrameCountIn;
ma_deinterleave_pcm_frames(ma_format_f32, ma_node_get_input_channels(pSplitterNode, 0), *pFrameCountOut, (const void*)ppFramesIn[0], (void**)ppFramesOut);
}
static ma_node_vtable g_ma_channel_separator_node_vtable =
{
ma_channel_separator_node_process_pcm_frames,
NULL,
1, /* 1 input bus. */
MA_NODE_BUS_COUNT_UNKNOWN, /* Output bus count is determined by the channel count and is unknown until the node instance is initialized. */
0 /* Default flags. */
};
MA_API ma_result ma_channel_separator_node_init(ma_node_graph* pNodeGraph, const ma_channel_separator_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_channel_separator_node* pSeparatorNode)
{
ma_result result;
ma_node_config baseConfig;
ma_uint32 inputChannels[1];
ma_uint32 outputChannels[MA_MAX_NODE_BUS_COUNT];
ma_uint32 iChannel;
if (pSeparatorNode == NULL) {
return MA_INVALID_ARGS;
}
MA_ZERO_OBJECT(pSeparatorNode);
if (pConfig == NULL) {
return MA_INVALID_ARGS;
}
if (pConfig->channels > MA_MAX_NODE_BUS_COUNT) {
return MA_INVALID_ARGS; /* Channel count cannot exceed the maximum number of buses. */
}
inputChannels[0] = pConfig->channels;
/* All output channels are mono. */
for (iChannel = 0; iChannel < pConfig->channels; iChannel += 1) {
outputChannels[iChannel] = 1;
}
baseConfig = pConfig->nodeConfig;
baseConfig.vtable = &g_ma_channel_separator_node_vtable;
baseConfig.outputBusCount = pConfig->channels; /* The vtable has an unknown channel count, so must specify it here. */
baseConfig.pInputChannels = inputChannels;
baseConfig.pOutputChannels = outputChannels;
result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pSeparatorNode->baseNode);
if (result != MA_SUCCESS) {
return result;
}
return MA_SUCCESS;
}
MA_API void ma_channel_separator_node_uninit(ma_channel_separator_node* pSeparatorNode, const ma_allocation_callbacks* pAllocationCallbacks)
{
/* The base node is always uninitialized first. */
ma_node_uninit(pSeparatorNode, pAllocationCallbacks);
}
/* Include ma_reverb_node.h after miniaudio.h */
#ifndef ma_channel_separator_node_h
#define ma_channel_separator_node_h
#ifdef __cplusplus
extern "C" {
#endif
typedef struct
{
ma_node_config nodeConfig;
ma_uint32 channels; /* The number of channels of the source, which will be the same as the output. Must be 1 or 2. The excite bus must always have one channel. */
} ma_channel_separator_node_config;
MA_API ma_channel_separator_node_config ma_channel_separator_node_config_init(ma_uint32 channels);
typedef struct
{
ma_node_base baseNode;
} ma_channel_separator_node;
MA_API ma_result ma_channel_separator_node_init(ma_node_graph* pNodeGraph, const ma_channel_separator_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_channel_separator_node* pSeparatorNode);
MA_API void ma_channel_separator_node_uninit(ma_channel_separator_node* pSeparatorNode, const ma_allocation_callbacks* pAllocationCallbacks);
#ifdef __cplusplus
}
#endif
#endif /* ma_reverb_node_h */
#define MINIAUDIO_IMPLEMENTATION
#include "../../../../miniaudio.h"
#include "../../../miniaudio_engine.h"
#include "ma_channel_separator_node.c"
#include "../ma_channel_combiner_node/ma_channel_combiner_node.c"
#include <stdio.h>
#define DEVICE_FORMAT ma_format_f32 /* Must always be f32 for this example because the node graph system only works with this. */
#define DEVICE_CHANNELS 0 /* The input file will determine the channel count. */
#define DEVICE_SAMPLE_RATE 48000
/*
In this example we're just separating out the channels with a `ma_channel_separator_node`, and then
combining them back together with a `ma_channel_combiner_node` before playing them back.
*/
static ma_decoder g_decoder; /* The decoder that we'll read data from. */
static ma_data_source_node g_dataSupplyNode; /* The node that will sit at the root level. Will be reading data from g_dataSupply. */
static ma_channel_separator_node g_separatorNode; /* The separator node. */
static ma_channel_combiner_node g_combinerNode; /* The combiner node. */
static ma_node_graph g_nodeGraph; /* The main node graph that we'll be feeding data through. */
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
{
(void)pInput;
(void)pDevice;
/* All we need to do is read from the node graph. */
ma_node_graph_read_pcm_frames(&g_nodeGraph, pOutput, frameCount, NULL);
}
int main(int argc, char** argv)
{
ma_result result;
ma_decoder_config decoderConfig;
ma_device_config deviceConfig;
ma_device device;
ma_node_graph_config nodeGraphConfig;
ma_channel_separator_node_config separatorNodeConfig;
ma_channel_combiner_node_config combinerNodeConfig;
ma_data_source_node_config dataSupplyNodeConfig;
ma_uint32 iChannel;
if (argc < 1) {
printf("No input file.\n");
return -1;
}
/* Decoder. */
decoderConfig = ma_decoder_config_init(DEVICE_FORMAT, 0, DEVICE_SAMPLE_RATE);
result = ma_decoder_init_file(argv[1], &decoderConfig, &g_decoder);
if (result != MA_SUCCESS) {
printf("Failed to load decoder.\n");
return -1;
}
/* Device. */
deviceConfig = ma_device_config_init(ma_device_type_playback);
deviceConfig.playback.pDeviceID = NULL;
deviceConfig.playback.format = g_decoder.outputFormat;
deviceConfig.playback.channels = g_decoder.outputChannels;
deviceConfig.sampleRate = g_decoder.outputSampleRate;
deviceConfig.dataCallback = data_callback;
result = ma_device_init(NULL, &deviceConfig, &device);
if (result != MA_SUCCESS) {
return result;
}
/* Node graph. */
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.");
goto done0;
}
/* Combiner. Attached straight to the endpoint. Input will be the separator node. */
combinerNodeConfig = ma_channel_combiner_node_config_init(device.playback.channels);
result = ma_channel_combiner_node_init(&g_nodeGraph, &combinerNodeConfig, NULL, &g_combinerNode);
if (result != MA_SUCCESS) {
printf("Failed to initialize channel combiner node.");
goto done1;
}
ma_node_attach_output_bus(&g_combinerNode, 0, ma_node_graph_get_endpoint(&g_nodeGraph), 0);
/*
Separator. Attached to the combiner. We need to attach each of the outputs of the
separator to each of the inputs of the combiner.
*/
separatorNodeConfig = ma_channel_separator_node_config_init(device.playback.channels);
result = ma_channel_separator_node_init(&g_nodeGraph, &separatorNodeConfig, NULL, &g_separatorNode);
if (result != MA_SUCCESS) {
printf("Failed to initialize channel separator node.");
goto done2;
}
/* The separator and combiner must have the same number of output and input buses respectively. */
MA_ASSERT(ma_node_get_output_bus_count(&g_separatorNode) == ma_node_get_input_bus_count(&g_combinerNode));
/* Each of the separator's outputs need to be attached to the corresponding input of the combiner. */
for (iChannel = 0; iChannel < ma_node_get_output_bus_count(&g_separatorNode); iChannel += 1) {
ma_node_attach_output_bus(&g_separatorNode, iChannel, &g_combinerNode, iChannel);
}
/* Data supply. Attached to input bus 0 of the reverb node. */
dataSupplyNodeConfig = ma_data_source_node_config_init(&g_decoder, MA_FALSE);
result = ma_data_source_node_init(&g_nodeGraph, &dataSupplyNodeConfig, NULL, &g_dataSupplyNode);
if (result != MA_SUCCESS) {
printf("Failed to initialize source node.");
goto done3;
}
ma_node_attach_output_bus(&g_dataSupplyNode, 0, &g_separatorNode, 0);
/* Now we just start the device and wait for the user to terminate the program. */
ma_device_start(&device);
printf("Press Enter to quit...\n");
getchar();
/* It's important that we stop the device first or else we'll uninitialize the graph from under the device. */
ma_device_stop(&device);
/*done4:*/ ma_data_source_node_uninit(&g_dataSupplyNode, NULL);
done3: ma_channel_separator_node_uninit(&g_separatorNode, NULL);
done2: ma_channel_combiner_node_uninit(&g_combinerNode, NULL);
done1: ma_node_graph_uninit(&g_nodeGraph, NULL);
done0: ma_device_uninit(&device);
(void)argc;
(void)argv;
return 0;
}
\ No newline at end of file
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
#include <stdio.h> #include <stdio.h>
#define DEVICE_FORMAT ma_format_f32; /* Must always be f32 for this example because the node graph system only works with this. */ #define DEVICE_FORMAT ma_format_f32 /* Must always be f32 for this example because the node graph system only works with this. */
#define DEVICE_CHANNELS 2 #define DEVICE_CHANNELS 2
#define DEVICE_SAMPLE_RATE 48000 #define DEVICE_SAMPLE_RATE 48000
...@@ -70,7 +70,7 @@ int main(int argc, char** argv) ...@@ -70,7 +70,7 @@ int main(int argc, char** argv)
result = ma_delay_node_init(&g_nodeGraph, &delayNodeConfig, NULL, &g_delayNode); result = ma_delay_node_init(&g_nodeGraph, &delayNodeConfig, NULL, &g_delayNode);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
printf("Failed to initialize vocoder node."); printf("Failed to initialize delay node.");
goto done1; goto done1;
} }
......
...@@ -60,10 +60,8 @@ MA_API ma_result ma_reverb_node_init(ma_node_graph* pNodeGraph, const ma_reverb_ ...@@ -60,10 +60,8 @@ MA_API ma_result ma_reverb_node_init(ma_node_graph* pNodeGraph, const ma_reverb_
baseConfig = pConfig->nodeConfig; baseConfig = pConfig->nodeConfig;
baseConfig.vtable = &g_ma_reverb_node_vtable; baseConfig.vtable = &g_ma_reverb_node_vtable;
baseConfig.inputChannels [0] = pConfig->channels; baseConfig.pInputChannels = &pConfig->channels;
baseConfig.inputChannels [1] = 0; /* Unused. */ baseConfig.pOutputChannels = &pConfig->channels;
baseConfig.outputChannels[0] = pConfig->channels;
baseConfig.outputChannels[1] = 0; /* Unused. */
result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pReverbNode->baseNode); result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pReverbNode->baseNode);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
#include <stdio.h> #include <stdio.h>
#define DEVICE_FORMAT ma_format_f32; /* Must always be f32 for this example because the node graph system only works with this. */ #define DEVICE_FORMAT ma_format_f32 /* Must always be f32 for this example because the node graph system only works with this. */
#define DEVICE_CHANNELS 1 /* For this example, always set to 1. */ #define DEVICE_CHANNELS 1 /* For this example, always set to 1. */
#define DEVICE_SAMPLE_RATE 48000 /* Cannot be less than 22050 for this example. */ #define DEVICE_SAMPLE_RATE 48000 /* Cannot be less than 22050 for this example. */
...@@ -71,7 +71,7 @@ int main(int argc, char** argv) ...@@ -71,7 +71,7 @@ int main(int argc, char** argv)
result = ma_reverb_node_init(&g_nodeGraph, &reverbNodeConfig, NULL, &g_reverbNode); result = ma_reverb_node_init(&g_nodeGraph, &reverbNodeConfig, NULL, &g_reverbNode);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
printf("Failed to initialize vocoder node."); printf("Failed to initialize reverb node.");
goto done1; goto done1;
} }
......
...@@ -39,6 +39,8 @@ MA_API ma_result ma_vocoder_node_init(ma_node_graph* pNodeGraph, const ma_vocode ...@@ -39,6 +39,8 @@ MA_API ma_result ma_vocoder_node_init(ma_node_graph* pNodeGraph, const ma_vocode
{ {
ma_result result; ma_result result;
ma_node_config baseConfig; ma_node_config baseConfig;
ma_uint32 inputChannels[2];
ma_uint32 outputChannels[1];
if (pVocoderNode == NULL) { if (pVocoderNode == NULL) {
return MA_INVALID_ARGS; return MA_INVALID_ARGS;
...@@ -54,12 +56,14 @@ MA_API ma_result ma_vocoder_node_init(ma_node_graph* pNodeGraph, const ma_vocode ...@@ -54,12 +56,14 @@ MA_API ma_result ma_vocoder_node_init(ma_node_graph* pNodeGraph, const ma_vocode
return MA_INVALID_ARGS; return MA_INVALID_ARGS;
} }
inputChannels [0] = pConfig->channels; /* Source/carrier. */
inputChannels [1] = 1; /* Excite/modulator. Must always be single channel. */
outputChannels[0] = pConfig->channels; /* Output channels is always the same as the source/carrier. */
baseConfig = pConfig->nodeConfig; baseConfig = pConfig->nodeConfig;
baseConfig.vtable = &g_ma_vocoder_node_vtable; baseConfig.vtable = &g_ma_vocoder_node_vtable;
baseConfig.inputChannels [0] = pConfig->channels; /* Source/carrier. */ baseConfig.pInputChannels = inputChannels;
baseConfig.inputChannels [1] = 1; /* Excite/modulator. Must always be single channel. */ baseConfig.pOutputChannels = outputChannels;
baseConfig.outputChannels[0] = pConfig->channels; /* Output channels is always the same as the source/carrier. */
baseConfig.outputChannels[1] = 0; /* Unused. */
result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pVocoderNode->baseNode); result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pVocoderNode->baseNode);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
......
...@@ -13,7 +13,7 @@ effect. ...@@ -13,7 +13,7 @@ effect.
#include <stdio.h> #include <stdio.h>
#define DEVICE_FORMAT ma_format_f32; /* Must always be f32 for this example because the node graph system only works with this. */ #define DEVICE_FORMAT ma_format_f32 /* Must always be f32 for this example because the node graph system only works with this. */
#define DEVICE_CHANNELS 1 /* For this example, always set to 1. */ #define DEVICE_CHANNELS 1 /* For this example, always set to 1. */
static ma_waveform g_sourceData; /* The underlying data source of the excite node. */ static ma_waveform g_sourceData; /* The underlying data source of the excite node. */
......
...@@ -160,7 +160,7 @@ int main(int argc, char** argv) ...@@ -160,7 +160,7 @@ int main(int argc, char** argv)
float posX = 0; float posX = 0;
float posZ = -1.0f; float posZ = -1.0f;
float step = 0.1f; float step = 0.1f;
float stepAngle = 0.002f; float stepAngle = 0.02f;
float angle = 0; float angle = 0;
float pitch = 1; float pitch = 1;
......
This diff is collapsed.
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