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;
} }
......
...@@ -59,11 +59,9 @@ MA_API ma_result ma_reverb_node_init(ma_node_graph* pNodeGraph, const ma_reverb_ ...@@ -59,11 +59,9 @@ 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;
......
...@@ -427,11 +427,9 @@ which is then fed through the chain. Each node in the graph can apply their own ...@@ -427,11 +427,9 @@ which is then fed through the chain. Each node in the graph can apply their own
the end of the graph is an endpoint which represents the end of the chain and is where the final the end of the graph is an endpoint which represents the end of the chain and is where the final
output is ultimately extracted from. output is ultimately extracted from.
Each node has a number of input buses and a number of output buses. Currently the maximum number Each node has a number of input buses and a number of output buses. An output bus from a node is
of input buses and output buses is 2 each (2 input buses, 2 output buses). This may change later attached to an input bus of another. Multiple nodes can connect their output buses to another
as new requirements come up. An output bus from a node is attached to an input bus of another. node's input bus, in which case their outputs will be mixed before processing by the node.
Multiple nodes can connect their output buses to another node's input bus, in which case their
outputs will be mixed before processing by the node.
Each input bus must be configured to accept the same number of channels, but input buses and output Each input bus must be configured to accept the same number of channels, but input buses and output
buses can each have different channel counts, in which case miniaudio will automatically convert buses can each have different channel counts, in which case miniaudio will automatically convert
...@@ -447,6 +445,7 @@ standard config/init system: ...@@ -447,6 +445,7 @@ standard config/init system:
```c ```c
ma_node_graph_config nodeGraphConfig = ma_node_graph_config_init(myChannelCount); ma_node_graph_config nodeGraphConfig = ma_node_graph_config_init(myChannelCount);
result = ma_node_graph_init(&nodeGraphConfig, NULL, &nodeGraph); // Second parameter is a pointer to allocation callbacks. result = ma_node_graph_init(&nodeGraphConfig, NULL, &nodeGraph); // Second parameter is a pointer to allocation callbacks.
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
// Failed to initialize node graph. // Failed to initialize node graph.
...@@ -455,7 +454,7 @@ standard config/init system: ...@@ -455,7 +454,7 @@ standard config/init system:
When you initialize the node graph, you're specifying the channel count of the endpoint. The When you initialize the node graph, you're specifying the channel count of the endpoint. The
endpoint is a special node which has one input bus and one output bus, both of which have the endpoint is a special node which has one input bus and one output bus, both of which have the
same channel count, which is specified in the config. Any nodes that ultimately connect to the same channel count, which is specified in the config. Any nodes that connect directly to the
endpoint must be configured such that their output buses have the same channel count. When you read endpoint must be configured such that their output buses have the same channel count. When you read
audio data from the node graph, it'll have the channel count you specified in the config. To read audio data from the node graph, it'll have the channel count you specified in the config. To read
data from the graph: data from the graph:
...@@ -468,19 +467,20 @@ data from the graph: ...@@ -468,19 +467,20 @@ data from the graph:
} }
``` ```
When you read audio data, miniaudio starts at the node graph's special internal endpoint node which When you read audio data, miniaudio starts at the node graph's endpoint node which then pulls in
then pulls in data from it's input attachments, which in turn recusively pull in data from their data from it's input attachments, which in turn recusively pull in data from their inputs, and so
inputs, and so on. At the very base of the graph there will be some kind of data source node which on. At the very base of the graph there will be some kind of data source node which will have zero
will have zero inputs and will instead read directly from a data source. The base nodes don't inputs and will instead read directly from a data source. The base nodes don't literally need to
literally need to read from a `ma_data_source` object, but they will always have some kind of read from a `ma_data_source` object, but they will always have some kind of underlying object that
underlying object that sources some kind of audio. The `ma_data_source_node` node can be used to sources some kind of audio. The `ma_data_source_node` node can be used to read from a
read from a `ma_data_source`. Data is always in floating-point format and in the number of channels `ma_data_source`. Data is always in floating-point format and in the number of channels you
you specified when the graph was initialized. The sample rate is defined by the underlying data specified when the graph was initialized. The sample rate is defined by the underlying data sources.
sources - it's up to you to ensure they use a consistent and appropraite sample rate. It's up to you to ensure they use a consistent and appropraite sample rate.
The `ma_node` API is designed to allow custom nodes to be implemented with relative ease. Most The `ma_node` API is designed to allow custom nodes to be implemented with relative ease, but
often you'll just use one of the stock node types. This is how you would initialize a node which miniaudio includes a few stock nodes for common functionality. This is how you would initialize a
reads directly from a data source (`ma_data_source_node`): node which reads directly from a data source (`ma_data_source_node`) which is an example of one
of the stock nodes that comes with miniaudio:
```c ```c
ma_data_source_node_config config = ma_data_source_node_config_init(pMyDataSource, isLooping); ma_data_source_node_config config = ma_data_source_node_config_init(pMyDataSource, isLooping);
...@@ -528,11 +528,11 @@ pointer to the processing function and the number of input and output buses. Exa ...@@ -528,11 +528,11 @@ pointer to the processing function and the number of input and output buses. Exa
const float* pFramesIn_1 = ppFramesIn[1]; // Input bus @ index 1. const float* pFramesIn_1 = ppFramesIn[1]; // Input bus @ index 1.
float* pFramesOut_0 = ppFramesOut[0]; // Output bus @ index 0. float* pFramesOut_0 = ppFramesOut[0]; // Output bus @ index 0.
// Do some processing. Process as many frames as you can. On input, `pFrameCountIn` will be // Do some processing. On input, `pFrameCountIn` will be the number of input frames in each
// the number of input frames in each buffer in `ppFramesIn` and `pFrameCountOut` will be // buffer in `ppFramesIn` and `pFrameCountOut` will be the capacity of each of the buffers
// the capacity of each of the buffers in `ppFramesOut`. On output, `pFrameCountIn` should // in `ppFramesOut`. On output, `pFrameCountIn` should be set to the number of input frames
// be set to the number of input frames your node consumed and `pFrameCountOut` should be // your node consumed and `pFrameCountOut` should be set the number of output frames that
// set the number of output frames that were produced. // were produced.
// //
// You should process as many frames as you can. If your effect consumes input frames at the // You should process as many frames as you can. If your effect consumes input frames at the
// same rate as output frames (always the case, unless you're doing resampling), you need // same rate as output frames (always the case, unless you're doing resampling), you need
...@@ -551,12 +551,20 @@ pointer to the processing function and the number of input and output buses. Exa ...@@ -551,12 +551,20 @@ pointer to the processing function and the number of input and output buses. Exa
}; };
... ...
// Each bus needs to have a channel count specified. To do this you need to specify the channel
// counts in an array and then pass that into the node config.
ma_uint32 inputChannels[2]; // Equal in size to the number of input channels specified in the vtable.
ma_uint32 outputChannels[1]; // Equal in size to the number of output channels specicied in the vtable.
inputChannels[0] = channelsIn;
inputChannels[1] = channelsIn;
outputChannels[0] = channelsOut;
ma_node_config nodeConfig = ma_node_config_init(); ma_node_config nodeConfig = ma_node_config_init();
nodeConfig.vtable = &my_custom_node_vtable; nodeConfig.vtable = &my_custom_node_vtable;
nodeConfig.inputChannels[0] = channelsIn; nodeConfig.pInputChannels = inputChannels;
nodeConfig.inputChannels[1] = channelsIn; nodeConfig.pOutputChannels = outputChannels;
nodeConfig.outputChannels[0] = channelsOut;
ma_node_base node; ma_node_base node;
result = ma_node_init(&nodeGraph, &nodeConfig, NULL, &node); result = ma_node_init(&nodeGraph, &nodeConfig, NULL, &node);
...@@ -566,9 +574,32 @@ pointer to the processing function and the number of input and output buses. Exa ...@@ -566,9 +574,32 @@ pointer to the processing function and the number of input and output buses. Exa
``` ```
When initializing a custom node, as in the code above, you'll normally just place your vtable in When initializing a custom node, as in the code above, you'll normally just place your vtable in
static space. The number of input and output buses are specified as part of the vtable. If you want static space. The number of input and output buses are specified as part of the vtable. If you need
to do some unusual stuff where the number of buses is dynamically configurable on a per-instance a variable number of buses on a per-node bases, the vtable should have the relevant bus count set
basis, you'll need to use a dynamic `ma_node_vtable` object. None of the stock node types do this. to `MA_NODE_BUS_COUNT_UNKNOWN`. In this case, the bus count should be set in the node config:
```c
static ma_node_vtable my_custom_node_vtable =
{
my_custom_node_process_pcm_frames, // The function that will be called process your custom node. This is where you'd implement your effect processing.
NULL, // Optional. A callback for calculating the number of input frames that are required to process a specified number of output frames.
MA_NODE_BUS_COUNT_UNKNOWN, // The number of input buses is determined on a per-node basis.
1, // 1 output bus.
0 // Default flags.
};
...
ma_node_config nodeConfig = ma_node_config_init();
nodeConfig.vtable = &my_custom_node_vtable;
nodeConfig.inputBusCount = myBusCount; // <-- Since the vtable specifies MA_NODE_BUS_COUNT_UNKNOWN, the input bus count should be set here.
nodeConfig.pInputChannels = inputChannels; // <-- Make sure there are nodeConfig.inputBusCount elements in this array.
nodeConfig.pOutputChannels = outputChannels; // <-- The vtable specifies 1 output bus, so there must be 1 element in this array.
```
In the above example it's important to never set the `inputBusCount` and `outputBusCount` members
to anything other than their defaults if the vtable specifies an explicit count. They can only be
set if the vtable specifies MA_NODE_BUS_COUNT_UNKNOWN in the relevant bus count.
Most often you'll want to create a structure to encapsulate your node with some extra data. You Most often you'll want to create a structure to encapsulate your node with some extra data. You
need to make sure the `ma_node_base` object is your first member of the structure: need to make sure the `ma_node_base` object is your first member of the structure:
...@@ -585,8 +616,8 @@ By doing this, your object will be compatible with all `ma_node` APIs and you ca ...@@ -585,8 +616,8 @@ By doing this, your object will be compatible with all `ma_node` APIs and you ca
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), the In the custom processing callback (`my_custom_node_process_pcm_frames()` in the example above), the
number of channels for each bus is what as specified by the config when the not was initialized number of channels for each bus is what was specified by the config when the node was initialized
with `ma_node_init()` In addition, all attachments to each of the input buses will have been with `ma_node_init()`. In addition, all attachments to each of the input buses will have been
pre-mixed by miniaudio. The config allows you to specify different channel counts for each pre-mixed by miniaudio. The config allows you to specify different channel counts for each
individual input and output bus. It's up to the effect to handle it appropriate, and if it can't, individual input and output bus. It's up to the effect to handle it appropriate, and if it can't,
return an error in it's initialization routine. return an error in it's initialization routine.
...@@ -665,9 +696,9 @@ You can start, stop and mute a node with the following: ...@@ -665,9 +696,9 @@ You can start, stop and mute a node with the following:
``` ```
By default the node is in a started state, but since it won't be connected to anything won't By default the node is in a started state, but since it won't be connected to anything won't
actually be invoked by the node graph until it's actually connected. When you stop a node, data actually be invoked by the node graph until it's connected. When you stop a node, data will not be
will not be read from any of it's input connections. You can use this property to stop a group of read from any of it's input connections. You can use this property to stop a group of sounds
sounds atomically. atomically.
You can configure the initial state of a node in it's config: You can configure the initial state of a node in it's config:
...@@ -789,12 +820,12 @@ before processing all input attachments which should be fairly simple. ...@@ -789,12 +820,12 @@ before processing all input attachments which should be fairly simple.
Another compromise, albeit less significant, is locking when attaching and detaching nodes. This Another compromise, albeit less significant, is locking when attaching and detaching nodes. This
locking is achieved by means of a spinlock in order to reduce memory overhead. A lock is present locking is achieved by means of a spinlock in order to reduce memory overhead. A lock is present
for each input bus and output bus, totaling 4 for each node. When an output bus is connected to an for each input bus and output bus. When an output bus is connected to an input bus, both the output
input bus, both the output bus and input bus is locked. This locking is specifically for attaching bus and input bus is locked. This locking is specifically for attaching and detaching across
and detaching across different threads and does not affect `ma_node_graph_read_pcm_frames()` in any different threads and does not affect `ma_node_graph_read_pcm_frames()` in any way. The locking and
way. The locking and unlocking is mostly self-explanatory, but slightly less intuitive part comes unlocking is mostly self-explanatory, but a slightly less intuitive aspect comes into it when
into it when considering that iterating over attachments must not break as a result of attaching or considering that iterating over attachments must not break as a result of attaching or detaching a
detaching a node while iteration is occuring. node while iteration is occuring.
Attaching and detaching are both quite simple. When an output bus of a node is attached to an input Attaching and detaching are both quite simple. When an output bus of a node is attached to an input
bus of another node, it's added to a linked list. Basically, an input bus is a linked list, where bus of another node, it's added to a linked list. Basically, an input bus is a linked list, where
...@@ -816,21 +847,22 @@ hasn't yet been set, from the perspective of iteration it's been attached becaus ...@@ -816,21 +847,22 @@ hasn't yet been set, from the perspective of iteration it's been attached becaus
only be happening in a forward direction which means the "previous" pointer won't actually ever get only be happening in a forward direction which means the "previous" pointer won't actually ever get
used. The same general process applies to detachment. See `ma_node_attach_output_bus()` and used. The same general process applies to detachment. See `ma_node_attach_output_bus()` and
`ma_node_detach_output_bus()` for the implementation of this mechanism. `ma_node_detach_output_bus()` for the implementation of this mechanism.
Loop detection is achieved through the use of a counter. At the ma_node_graph level there is a
counter which is updated after each read. There is also a counter for each node which is set to the
counter of the node graph plus 1 after each time it processes data. Before anything is processed, a
check is performed that the node's counter is lower or equal to the node graph. If so, it's fine to
proceed with processing. If not, MA_LOOP is returned and nothing is output. This represents a sort
of termination point.
*/ */
/* Must never exceed 255. */ /* Must never exceed 254. */
#ifndef MA_MAX_NODE_BUS_COUNT #ifndef MA_MAX_NODE_BUS_COUNT
#define MA_MAX_NODE_BUS_COUNT 2 #define MA_MAX_NODE_BUS_COUNT 254
#endif #endif
/* Used internally by miniaudio for memory management. Must never exceed MA_MAX_NODE_BUS_COUNT. */
#ifndef MA_MAX_NODE_LOCAL_BUS_COUNT
#define MA_MAX_NODE_LOCAL_BUS_COUNT 2
#endif
/* Use this when the bus count is determined by the node instance rather than the vtable. */
#define MA_NODE_BUS_COUNT_UNKNOWN 255
typedef struct ma_node_graph ma_node_graph; typedef struct ma_node_graph ma_node_graph;
typedef void ma_node; typedef void ma_node;
...@@ -895,10 +927,12 @@ typedef struct ...@@ -895,10 +927,12 @@ typedef struct
typedef struct typedef struct
{ {
const ma_node_vtable* vtable; /* Should never be null. Initialization of the node will fail if so. */ const ma_node_vtable* vtable; /* Should never be null. Initialization of the node will fail if so. */
ma_uint32 inputChannels[MA_MAX_NODE_BUS_COUNT]; ma_node_state initialState; /* Defaults to ma_node_state_started. */
ma_uint32 outputChannels[MA_MAX_NODE_BUS_COUNT]; ma_uint32 inputBusCount; /* Only used if the vtable specifies an input bus count of `MA_NODE_BUS_COUNT_UNKNOWN`, otherwise must be set to `MA_NODE_BUS_COUNT_UNKNOWN` (default). */
ma_node_state initialState; /* Defaults to ma_node_state_started. */ ma_uint32 outputBusCount; /* Only used if the vtable specifies an output bus count of `MA_NODE_BUS_COUNT_UNKNOWN`, otherwise be set to `MA_NODE_BUS_COUNT_UNKNOWN` (default). */
const ma_uint32* pInputChannels; /* The number of elements are determined by the input bus count as determined by the vtable, or `inputBusCount` if the vtable specifies `MA_NODE_BUS_COUNT_UNKNOWN`. */
const ma_uint32* pOutputChannels; /* The number of elements are determined by the output bus count as determined by the vtable, or `outputBusCount` if the vtable specifies `MA_NODE_BUS_COUNT_UNKNOWN`. */
} ma_node_config; } ma_node_config;
MA_API ma_node_config ma_node_config_init(void); MA_API ma_node_config ma_node_config_init(void);
...@@ -965,8 +999,15 @@ struct ma_node_base ...@@ -965,8 +999,15 @@ struct ma_node_base
MA_ATOMIC ma_node_state state; /* When set to stopped, nothing will be read, regardless of the times in stateTimes. */ MA_ATOMIC ma_node_state state; /* When set to stopped, nothing will be read, regardless of the times in stateTimes. */
MA_ATOMIC ma_uint64 stateTimes[2]; /* Indexed by ma_node_state. Specifies the time based on the global clock that a node should be considered to be in the relevant state. */ MA_ATOMIC ma_uint64 stateTimes[2]; /* Indexed by ma_node_state. Specifies the time based on the global clock that a node should be considered to be in the relevant state. */
MA_ATOMIC ma_uint64 localTime; /* The node's local clock. This is just a running sum of the number of output frames that have been processed. Can be modified by any thread with `ma_node_set_time()`. */ MA_ATOMIC ma_uint64 localTime; /* The node's local clock. This is just a running sum of the number of output frames that have been processed. Can be modified by any thread with `ma_node_set_time()`. */
ma_node_input_bus inputBuses[MA_MAX_NODE_BUS_COUNT]; ma_uint32 inputBusCount;
ma_node_output_bus outputBuses[MA_MAX_NODE_BUS_COUNT]; ma_uint32 outputBusCount;
ma_node_input_bus* pInputBuses;
ma_node_output_bus* pOutputBuses;
/* Memory management. */
ma_node_input_bus _inputBuses[MA_MAX_NODE_LOCAL_BUS_COUNT];
ma_node_output_bus _outputBuses[MA_MAX_NODE_LOCAL_BUS_COUNT];
void* _pHeap; /* A heap allocation for internal use only. pInputBuses and/or pOutputBuses will point to this if the bus count exceeds MA_MAX_NODE_LOCAL_BUS_COUNT. */
}; };
MA_API ma_result ma_node_init(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node* pNode); MA_API ma_result ma_node_init(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node* pNode);
...@@ -3344,9 +3385,9 @@ MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const m ...@@ -3344,9 +3385,9 @@ MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const m
MA_ZERO_OBJECT(pNodeGraph); MA_ZERO_OBJECT(pNodeGraph);
endpointConfig = ma_node_config_init(); endpointConfig = ma_node_config_init();
endpointConfig.vtable = &g_node_graph_endpoint_vtable; endpointConfig.vtable = &g_node_graph_endpoint_vtable;
endpointConfig.inputChannels[0] = pConfig->channels; endpointConfig.pInputChannels = &pConfig->channels;
endpointConfig.outputChannels[0] = pConfig->channels; endpointConfig.pOutputChannels = &pConfig->channels;
result = ma_node_init(pNodeGraph, &endpointConfig, pAllocationCallbacks, &pNodeGraph->endpoint); result = ma_node_init(pNodeGraph, &endpointConfig, pAllocationCallbacks, &pNodeGraph->endpoint);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
...@@ -3900,7 +3941,9 @@ MA_API ma_node_config ma_node_config_init(void) ...@@ -3900,7 +3941,9 @@ MA_API ma_node_config ma_node_config_init(void)
ma_node_config config; ma_node_config config;
MA_ZERO_OBJECT(&config); MA_ZERO_OBJECT(&config);
config.initialState = ma_node_state_started; /* Nodes are started by default. */ config.initialState = ma_node_state_started; /* Nodes are started by default. */
config.inputBusCount = MA_NODE_BUS_COUNT_UNKNOWN;
config.outputBusCount = MA_NODE_BUS_COUNT_UNKNOWN;
return config; return config;
} }
...@@ -3920,7 +3963,7 @@ static float* ma_node_get_cached_input_ptr(ma_node* pNode, ma_uint32 inputBusInd ...@@ -3920,7 +3963,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;
for (iInputBus = 0; iInputBus < inputBusIndex; iInputBus += 1) { for (iInputBus = 0; iInputBus < inputBusIndex; iInputBus += 1) {
pBasePtr += pNodeBase->cachedDataCapInFramesPerBus * ma_node_input_bus_get_channels(&pNodeBase->inputBuses[iInputBus]); pBasePtr += pNodeBase->cachedDataCapInFramesPerBus * ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[iInputBus]);
} }
return pBasePtr; return pBasePtr;
...@@ -3938,11 +3981,11 @@ static float* ma_node_get_cached_output_ptr(ma_node* pNode, ma_uint32 outputBusI ...@@ -3938,11 +3981,11 @@ static float* ma_node_get_cached_output_ptr(ma_node* pNode, ma_uint32 outputBusI
/* Cached output data starts after the input data. */ /* Cached output data starts after the input data. */
pBasePtr = pNodeBase->pCachedData; pBasePtr = pNodeBase->pCachedData;
for (iInputBus = 0; iInputBus < ma_node_get_input_bus_count(pNodeBase); iInputBus += 1) { for (iInputBus = 0; iInputBus < ma_node_get_input_bus_count(pNodeBase); iInputBus += 1) {
pBasePtr += pNodeBase->cachedDataCapInFramesPerBus * ma_node_input_bus_get_channels(&pNodeBase->inputBuses[iInputBus]); pBasePtr += pNodeBase->cachedDataCapInFramesPerBus * ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[iInputBus]);
} }
for (iOutputBus = 0; iOutputBus < outputBusIndex; iOutputBus += 1) { for (iOutputBus = 0; iOutputBus < outputBusIndex; iOutputBus += 1) {
pBasePtr += pNodeBase->cachedDataCapInFramesPerBus * ma_node_output_bus_get_channels(&pNodeBase->outputBuses[iOutputBus]); pBasePtr += pNodeBase->cachedDataCapInFramesPerBus * ma_node_output_bus_get_channels(&pNodeBase->pOutputBuses[iOutputBus]);
} }
return pBasePtr; return pBasePtr;
...@@ -3954,6 +3997,10 @@ MA_API ma_result ma_node_init(ma_node_graph* pNodeGraph, const ma_node_config* p ...@@ -3954,6 +3997,10 @@ MA_API ma_result ma_node_init(ma_node_graph* pNodeGraph, const ma_node_config* p
ma_node_base* pNodeBase = (ma_node_base*)pNode; ma_node_base* pNodeBase = (ma_node_base*)pNode;
ma_uint32 iInputBus; ma_uint32 iInputBus;
ma_uint32 iOutputBus; ma_uint32 iOutputBus;
size_t heapSizeInBytes = 0;
size_t inputBusHeapOffsetInBytes = MA_SIZE_MAX;
size_t outputBusHeapOffsetInBytes = MA_SIZE_MAX;
size_t cachedDataHeapOffsetInBytes = MA_SIZE_MAX;
if (pNodeBase == NULL) { if (pNodeBase == NULL) {
return MA_INVALID_ARGS; return MA_INVALID_ARGS;
...@@ -3965,51 +4012,85 @@ MA_API ma_result ma_node_init(ma_node_graph* pNodeGraph, const ma_node_config* p ...@@ -3965,51 +4012,85 @@ MA_API ma_result ma_node_init(ma_node_graph* pNodeGraph, const ma_node_config* p
return MA_INVALID_ARGS; /* Config is invalid. */ return MA_INVALID_ARGS; /* Config is invalid. */
} }
if (pConfig->vtable->inputBusCount > MA_MAX_NODE_BUS_COUNT || pConfig->vtable->outputBusCount > MA_MAX_NODE_BUS_COUNT) {
return MA_INVALID_ARGS; /* Invalid bus count. */ pNodeBase->pNodeGraph = pNodeGraph;
pNodeBase->vtable = pConfig->vtable;
pNodeBase->state = pConfig->initialState;
pNodeBase->stateTimes[ma_node_state_started] = 0;
pNodeBase->stateTimes[ma_node_state_stopped] = (ma_uint64)(ma_int64)-1; /* Weird casting for VC6 compatibility. */
/* Bus counts are determined by the vtable, unless they're set to `MA_NODE_BUS_COUNT_UNKNWON`, in which case they're taken from the config. */
if (pNodeBase->vtable->inputBusCount == MA_NODE_BUS_COUNT_UNKNOWN) {
pNodeBase->inputBusCount = pConfig->inputBusCount;
} else {
pNodeBase->inputBusCount = pNodeBase->vtable->inputBusCount;
if (pConfig->inputBusCount != MA_NODE_BUS_COUNT_UNKNOWN && pConfig->inputBusCount != pNodeBase->vtable->inputBusCount) {
return MA_INVALID_ARGS; /* Invalid configuration. You must not specify a conflicting bus count between the node's config and the vtable. */
}
}
if (pNodeBase->vtable->outputBusCount == MA_NODE_BUS_COUNT_UNKNOWN) {
pNodeBase->outputBusCount = pConfig->outputBusCount;
} else {
pNodeBase->outputBusCount = pNodeBase->vtable->outputBusCount;
if (pConfig->outputBusCount != MA_NODE_BUS_COUNT_UNKNOWN && pConfig->outputBusCount != pNodeBase->vtable->outputBusCount) {
return MA_INVALID_ARGS; /* Invalid configuration. You must not specify a conflicting bus count between the node's config and the vtable. */
}
} }
/* Bus counts must be within limits. */
if (ma_node_get_input_bus_count(pNodeBase) > MA_MAX_NODE_BUS_COUNT || ma_node_get_output_bus_count(pNodeBase) > MA_MAX_NODE_BUS_COUNT) {
return MA_INVALID_ARGS;
}
/* We must have channel counts for each bus. */
if ((pNodeBase->inputBusCount > 0 && pConfig->pInputChannels == NULL) || (pNodeBase->outputBusCount > 0 && pConfig->pOutputChannels == NULL)) {
return MA_INVALID_ARGS; /* You must specify channel counts for each input and output bus. */
}
/* Some special rules for passthrough nodes. */ /* Some special rules for passthrough nodes. */
if ((pConfig->vtable->flags & MA_NODE_FLAG_PASSTHROUGH) != 0) { if ((pConfig->vtable->flags & MA_NODE_FLAG_PASSTHROUGH) != 0) {
if (pConfig->vtable->inputBusCount != 1 || pConfig->vtable->outputBusCount != 1) { if (pConfig->vtable->inputBusCount != 1 || pConfig->vtable->outputBusCount != 1) {
return MA_INVALID_ARGS; /* Passthrough nodes must have exactly 1 input bus and 1 output bus. */ return MA_INVALID_ARGS; /* Passthrough nodes must have exactly 1 input bus and 1 output bus. */
} }
if (pConfig->inputChannels[0] != pConfig->outputChannels[0]) { if (pConfig->pInputChannels[0] != pConfig->pOutputChannels[0]) {
return MA_INVALID_ARGS; /* Passthrough nodes must have the same number of channels between input and output nodes. */ return MA_INVALID_ARGS; /* Passthrough nodes must have the same number of channels between input and output nodes. */
} }
} }
pNodeBase->pNodeGraph = pNodeGraph;
pNodeBase->vtable = pConfig->vtable;
pNodeBase->state = pConfig->initialState;
pNodeBase->stateTimes[ma_node_state_started] = 0;
pNodeBase->stateTimes[ma_node_state_stopped] = (ma_uint64)(ma_int64)-1; /* Weird casting for VC6 compatibility. */
/* We need to run an initialization step for each input bus. */ /*
for (iInputBus = 0; iInputBus < ma_node_get_input_bus_count(pNodeBase); iInputBus += 1) { We may need to allocate memory on the heap. These are what we may need to put on the heap:
if (pConfig->inputChannels[iInputBus] < MA_MIN_CHANNELS || pConfig->inputChannels[iInputBus] > MA_MAX_CHANNELS) {
return MA_INVALID_ARGS; /* Invalid channel count. */
}
ma_node_input_bus_init(pConfig->inputChannels[iInputBus], &pNodeBase->inputBuses[iInputBus]); +------------------------------------------------+
} | Input Buses | Output Buses | Cached Audio Data |
+------------------------------------------------+
/* We'll need to first count how much space we'll need to allocate. If it exceeds 0, we'll
The read flag on all output buses needs to default to 1. This ensures fresh input data is read allocate a buffer on the heap.
on the first call to ma_node_read_pcm_frames(). Not doing this will result in the first call
having garbage data returned.
*/ */
for (iOutputBus = 0; iOutputBus < ma_node_get_output_bus_count(pNodeBase); iOutputBus += 1) { heapSizeInBytes = 0; /* Always start at zero. */
if (pConfig->outputChannels[iOutputBus] < MA_MIN_CHANNELS || pConfig->outputChannels[iOutputBus] > MA_MAX_CHANNELS) {
return MA_INVALID_ARGS; /* Invalid channel count. */
}
ma_node_output_bus_init(pNodeBase, iOutputBus, pConfig->outputChannels[iOutputBus], &pNodeBase->outputBuses[iOutputBus]); /* Input buses. */
if (ma_node_get_input_bus_count(pNodeBase) > ma_countof(pNodeBase->_inputBuses)) {
inputBusHeapOffsetInBytes = heapSizeInBytes;
heapSizeInBytes += ma_node_get_input_bus_count(pNodeBase) * sizeof(*pNodeBase->pInputBuses);
} }
/* Output buses. */
if (ma_node_get_output_bus_count(pNodeBase) > ma_countof(pNodeBase->_outputBuses)) {
outputBusHeapOffsetInBytes = heapSizeInBytes;
heapSizeInBytes += ma_node_get_output_bus_count(pNodeBase) * sizeof(*pNodeBase->pOutputBuses);
}
/* /*
Cached audio data.
We need to allocate memory for a caching both input and output data. We have an optimization We need to allocate memory for a caching both input and output data. We have an optimization
where no caching is necessary for specific conditions: where no caching is necessary for specific conditions:
...@@ -4030,41 +4111,82 @@ MA_API ma_result ma_node_init(ma_node_graph* pNodeGraph, const ma_node_config* p ...@@ -4030,41 +4111,82 @@ MA_API ma_result ma_node_init(ma_node_graph* pNodeGraph, const ma_node_config* p
size_t cachedDataSizeInBytes = 0; size_t cachedDataSizeInBytes = 0;
ma_uint32 iBus; ma_uint32 iBus;
cachedDataHeapOffsetInBytes = heapSizeInBytes;
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. */
for (iBus = 0; iBus < ma_node_get_input_bus_count(pNodeBase); iBus += 1) { for (iBus = 0; iBus < ma_node_get_input_bus_count(pNodeBase); iBus += 1) {
cachedDataSizeInBytes += pNodeBase->cachedDataCapInFramesPerBus * ma_get_bytes_per_frame(ma_format_f32, ma_node_get_input_channels(pNodeBase, iBus)); cachedDataSizeInBytes += pNodeBase->cachedDataCapInFramesPerBus * ma_get_bytes_per_frame(ma_format_f32, pConfig->pInputChannels[iBus]);
} }
for (iBus = 0; iBus < ma_node_get_output_bus_count(pNodeBase); iBus += 1) { for (iBus = 0; iBus < ma_node_get_output_bus_count(pNodeBase); iBus += 1) {
cachedDataSizeInBytes += pNodeBase->cachedDataCapInFramesPerBus * ma_get_bytes_per_frame(ma_format_f32, ma_node_get_output_channels(pNodeBase, iBus)); cachedDataSizeInBytes += pNodeBase->cachedDataCapInFramesPerBus * ma_get_bytes_per_frame(ma_format_f32, pConfig->pOutputChannels[iBus]);
} }
pNodeBase->pCachedData = (float*)ma_malloc(cachedDataSizeInBytes, pAllocationCallbacks); heapSizeInBytes += cachedDataSizeInBytes;
if (pNodeBase->pCachedData == NULL) { }
/* Now that we know the size of the heap we can allocate memory and assign offsets. */
if (heapSizeInBytes > 0) {
pNodeBase->_pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks);
if (pNodeBase->_pHeap == NULL) {
return MA_OUT_OF_MEMORY; return MA_OUT_OF_MEMORY;
} }
}
/* Input Buses. */
pNodeBase->pInputBuses = pNodeBase->_inputBuses;
if (inputBusHeapOffsetInBytes != MA_SIZE_MAX) {
pNodeBase->pInputBuses = (ma_node_input_bus*)ma_offset_ptr(pNodeBase->_pHeap, inputBusHeapOffsetInBytes);
}
/* Output Buses. */
pNodeBase->pOutputBuses = pNodeBase->_outputBuses;
if (outputBusHeapOffsetInBytes != MA_SIZE_MAX) {
pNodeBase->pOutputBuses = (ma_node_output_bus*)ma_offset_ptr(pNodeBase->_pHeap, outputBusHeapOffsetInBytes);
}
/* Cached Audio Data. */
pNodeBase->pCachedData = NULL;
if (cachedDataHeapOffsetInBytes != MA_SIZE_MAX) {
pNodeBase->pCachedData = (float*)ma_offset_ptr(pNodeBase->_pHeap, cachedDataHeapOffsetInBytes);
}
/* We need to run an initialization step for each input and output bus. */
for (iInputBus = 0; iInputBus < ma_node_get_input_bus_count(pNodeBase); iInputBus += 1) {
ma_node_input_bus_init(pConfig->pInputChannels[iInputBus], &pNodeBase->pInputBuses[iInputBus]);
}
for (iOutputBus = 0; iOutputBus < ma_node_get_output_bus_count(pNodeBase); iOutputBus += 1) {
ma_node_output_bus_init(pNodeBase, iOutputBus, pConfig->pOutputChannels[iOutputBus], &pNodeBase->pOutputBuses[iOutputBus]);
}
/* The cached data needs to be initialized to silence (or a sine wave tone if we're debugging). */
if (pNodeBase->pCachedData != NULL) {
ma_uint32 iBus;
#if 1 /* Toggle this between 0 and 1 to turn debugging on or off. 1 = fill with a sine wave for debugging; 0 = fill with silence. */ #if 1 /* Toggle this between 0 and 1 to turn debugging on or off. 1 = fill with a sine wave for debugging; 0 = fill with silence. */
/* For safety we'll go ahead and default the buffer to silence. */ /* For safety we'll go ahead and default the buffer to silence. */
for (iBus = 0; iBus < ma_node_get_input_bus_count(pNodeBase); iBus += 1) { for (iBus = 0; iBus < ma_node_get_input_bus_count(pNodeBase); iBus += 1) {
ma_silence_pcm_frames(ma_node_get_cached_input_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_input_bus_get_channels(&pNodeBase->inputBuses[iBus])); ma_silence_pcm_frames(ma_node_get_cached_input_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[iBus]));
} }
for (iBus = 0; iBus < ma_node_get_output_bus_count(pNodeBase); iBus += 1) { for (iBus = 0; iBus < ma_node_get_output_bus_count(pNodeBase); iBus += 1) {
ma_silence_pcm_frames(ma_node_get_cached_output_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_output_bus_get_channels(&pNodeBase->outputBuses[iBus])); ma_silence_pcm_frames(ma_node_get_cached_output_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_output_bus_get_channels(&pNodeBase->pOutputBuses[iBus]));
} }
#else #else
/* For debugging. Default to a sine wave. */ /* For debugging. Default to a sine wave. */
for (iBus = 0; iBus < ma_node_get_input_bus_count(pNodeBase); iBus += 1) { for (iBus = 0; iBus < ma_node_get_input_bus_count(pNodeBase); iBus += 1) {
ma_debug_fill_pcm_frames_with_sine_wave(ma_node_get_cached_input_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_input_bus_get_channels(&pNodeBase->inputBuses[iBus]), 48000); ma_debug_fill_pcm_frames_with_sine_wave(ma_node_get_cached_input_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[iBus]), 48000);
} }
for (iBus = 0; iBus < ma_node_get_output_bus_count(pNodeBase); iBus += 1) { for (iBus = 0; iBus < ma_node_get_output_bus_count(pNodeBase); iBus += 1) {
ma_debug_fill_pcm_frames_with_sine_wave(ma_node_get_cached_output_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_output_bus_get_channels(&pNodeBase->outputBuses[iBus]), 48000); ma_debug_fill_pcm_frames_with_sine_wave(ma_node_get_cached_output_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_output_bus_get_channels(&pNodeBase->pOutputBuses[iBus]), 48000);
} }
#endif #endif
} }
return MA_SUCCESS; return MA_SUCCESS;
} }
...@@ -4089,9 +4211,9 @@ MA_API void ma_node_uninit(ma_node* pNode, const ma_allocation_callbacks* pAlloc ...@@ -4089,9 +4211,9 @@ MA_API void ma_node_uninit(ma_node* pNode, const ma_allocation_callbacks* pAlloc
At this point the node should be completely unreferenced by the node graph and we can finish up At this point the node should be completely unreferenced by the node graph and we can finish up
the uninitialization process without needing to worry about thread-safety. the uninitialization process without needing to worry about thread-safety.
*/ */
if (pNodeBase->pCachedData != NULL) { if (pNodeBase->_pHeap != NULL) {
ma_free(pNodeBase->pCachedData, pAllocationCallbacks); ma_free(pNodeBase->_pHeap, pAllocationCallbacks);
pNodeBase->pCachedData = NULL; pNodeBase->_pHeap;
} }
} }
...@@ -4110,7 +4232,7 @@ MA_API ma_uint32 ma_node_get_input_bus_count(const ma_node* pNode) ...@@ -4110,7 +4232,7 @@ MA_API ma_uint32 ma_node_get_input_bus_count(const ma_node* pNode)
return 0; return 0;
} }
return ((ma_node_base*)pNode)->vtable->inputBusCount; return ((ma_node_base*)pNode)->inputBusCount;
} }
MA_API ma_uint32 ma_node_get_output_bus_count(const ma_node* pNode) MA_API ma_uint32 ma_node_get_output_bus_count(const ma_node* pNode)
...@@ -4119,7 +4241,7 @@ MA_API ma_uint32 ma_node_get_output_bus_count(const ma_node* pNode) ...@@ -4119,7 +4241,7 @@ MA_API ma_uint32 ma_node_get_output_bus_count(const ma_node* pNode)
return 0; return 0;
} }
return ((ma_node_base*)pNode)->vtable->outputBusCount; return ((ma_node_base*)pNode)->outputBusCount;
} }
...@@ -4135,7 +4257,7 @@ MA_API ma_uint32 ma_node_get_input_channels(const ma_node* pNode, ma_uint32 inpu ...@@ -4135,7 +4257,7 @@ MA_API ma_uint32 ma_node_get_input_channels(const ma_node* pNode, ma_uint32 inpu
return 0; /* Invalid bus index. */ return 0; /* Invalid bus index. */
} }
return ma_node_input_bus_get_channels(&pNodeBase->inputBuses[inputBusIndex]); return ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[inputBusIndex]);
} }
MA_API ma_uint32 ma_node_get_output_channels(const ma_node* pNode, ma_uint32 outputBusIndex) MA_API ma_uint32 ma_node_get_output_channels(const ma_node* pNode, ma_uint32 outputBusIndex)
...@@ -4150,7 +4272,7 @@ MA_API ma_uint32 ma_node_get_output_channels(const ma_node* pNode, ma_uint32 out ...@@ -4150,7 +4272,7 @@ MA_API ma_uint32 ma_node_get_output_channels(const ma_node* pNode, ma_uint32 out
return 0; /* Invalid bus index. */ return 0; /* Invalid bus index. */
} }
return ma_node_output_bus_get_channels(&pNodeBase->outputBuses[outputBusIndex]); return ma_node_output_bus_get_channels(&pNodeBase->pOutputBuses[outputBusIndex]);
} }
...@@ -4178,7 +4300,7 @@ static ma_result ma_node_detach_full(ma_node* pNode) ...@@ -4178,7 +4300,7 @@ static ma_result ma_node_detach_full(ma_node* pNode)
ma_node_input_bus* pInputBus; ma_node_input_bus* pInputBus;
ma_node_output_bus* pOutputBus; ma_node_output_bus* pOutputBus;
pInputBus = &pNodeBase->inputBuses[iInputBus]; pInputBus = &pNodeBase->pInputBuses[iInputBus];
/* /*
This is important. We cannot be using ma_node_input_bus_first() or ma_node_input_bus_next(). Those This is important. We cannot be using ma_node_input_bus_first() or ma_node_input_bus_next(). Those
...@@ -4209,14 +4331,14 @@ MA_API ma_result ma_node_detach_output_bus(ma_node* pNode, ma_uint32 outputBusIn ...@@ -4209,14 +4331,14 @@ MA_API ma_result ma_node_detach_output_bus(ma_node* pNode, ma_uint32 outputBusIn
} }
/* We need to lock the output bus because we need to inspect the input node and grab it's input bus. */ /* We need to lock the output bus because we need to inspect the input node and grab it's input bus. */
ma_node_output_bus_lock(&pNodeBase->outputBuses[outputBusIndex]); ma_node_output_bus_lock(&pNodeBase->pOutputBuses[outputBusIndex]);
{ {
pInputNodeBase = (ma_node_base*)pNodeBase->outputBuses[outputBusIndex].pInputNode; pInputNodeBase = (ma_node_base*)pNodeBase->pOutputBuses[outputBusIndex].pInputNode;
if (pInputNodeBase != NULL) { if (pInputNodeBase != NULL) {
ma_node_input_bus_detach__no_output_bus_lock(&pInputNodeBase->inputBuses[pNodeBase->outputBuses[outputBusIndex].inputNodeInputBusIndex], &pNodeBase->outputBuses[outputBusIndex]); ma_node_input_bus_detach__no_output_bus_lock(&pInputNodeBase->pInputBuses[pNodeBase->pOutputBuses[outputBusIndex].inputNodeInputBusIndex], &pNodeBase->pOutputBuses[outputBusIndex]);
} }
} }
ma_node_output_bus_unlock(&pNodeBase->outputBuses[outputBusIndex]); ma_node_output_bus_unlock(&pNodeBase->pOutputBuses[outputBusIndex]);
return result; return result;
} }
...@@ -4259,7 +4381,7 @@ MA_API ma_result ma_node_attach_output_bus(ma_node* pNode, ma_uint32 outputBusIn ...@@ -4259,7 +4381,7 @@ MA_API ma_result ma_node_attach_output_bus(ma_node* pNode, ma_uint32 outputBusIn
} }
/* This will deal with detaching if the output bus is already attached to something. */ /* This will deal with detaching if the output bus is already attached to something. */
ma_node_input_bus_attach(&pOtherNodeBase->inputBuses[otherNodeInputBusIndex], &pNodeBase->outputBuses[outputBusIndex], pOtherNode, otherNodeInputBusIndex); ma_node_input_bus_attach(&pOtherNodeBase->pInputBuses[otherNodeInputBusIndex], &pNodeBase->pOutputBuses[outputBusIndex], pOtherNode, otherNodeInputBusIndex);
return MA_SUCCESS; return MA_SUCCESS;
} }
...@@ -4276,7 +4398,7 @@ MA_API ma_result ma_node_set_output_bus_volume(ma_node* pNode, ma_uint32 outputB ...@@ -4276,7 +4398,7 @@ MA_API ma_result ma_node_set_output_bus_volume(ma_node* pNode, ma_uint32 outputB
return MA_INVALID_ARGS; /* Invalid bus index. */ return MA_INVALID_ARGS; /* Invalid bus index. */
} }
return ma_node_output_bus_set_volume(&pNodeBase->outputBuses[outputBusIndex], volume); return ma_node_output_bus_set_volume(&pNodeBase->pOutputBuses[outputBusIndex], volume);
} }
MA_API float ma_node_get_output_bus_volume(const ma_node* pNode, ma_uint32 outputBusIndex) MA_API float ma_node_get_output_bus_volume(const ma_node* pNode, ma_uint32 outputBusIndex)
...@@ -4291,7 +4413,7 @@ MA_API float ma_node_get_output_bus_volume(const ma_node* pNode, ma_uint32 outpu ...@@ -4291,7 +4413,7 @@ MA_API float ma_node_get_output_bus_volume(const ma_node* pNode, ma_uint32 outpu
return 0; /* Invalid bus index. */ return 0; /* Invalid bus index. */
} }
return ma_node_output_bus_get_volume(&pNodeBase->outputBuses[outputBusIndex]); return ma_node_output_bus_get_volume(&pNodeBase->pOutputBuses[outputBusIndex]);
} }
MA_API ma_result ma_node_set_state(ma_node* pNode, ma_node_state state) MA_API ma_result ma_node_set_state(ma_node* pNode, ma_node_state state)
...@@ -4532,7 +4654,7 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde ...@@ -4532,7 +4654,7 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde
ppFramesOut[0] = pFramesOut; ppFramesOut[0] = pFramesOut;
ppFramesIn[0] = ppFramesOut[0]; ppFramesIn[0] = ppFramesOut[0];
result = ma_node_input_bus_read_pcm_frames(pNodeBase, &pNodeBase->inputBuses[0], ppFramesIn[0], frameCount, &totalFramesRead, globalTime); result = ma_node_input_bus_read_pcm_frames(pNodeBase, &pNodeBase->pInputBuses[0], ppFramesIn[0], frameCount, &totalFramesRead, globalTime);
if (result == MA_SUCCESS) { if (result == MA_SUCCESS) {
/* Even though it's a passthrough, we still need to fire the callback. */ /* Even though it's a passthrough, we still need to fire the callback. */
frameCountIn = totalFramesRead; frameCountIn = totalFramesRead;
...@@ -4591,7 +4713,7 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde ...@@ -4591,7 +4713,7 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde
MA_ASSERT(framesToProcessIn <= 0xFFFF); MA_ASSERT(framesToProcessIn <= 0xFFFF);
MA_ASSERT(framesToProcessOut <= 0xFFFF); MA_ASSERT(framesToProcessOut <= 0xFFFF);
if (ma_node_output_bus_has_read(&pNodeBase->outputBuses[outputBusIndex])) { if (ma_node_output_bus_has_read(&pNodeBase->pOutputBuses[outputBusIndex])) {
/* Getting here means we need to do another round of processing. */ /* Getting here means we need to do another round of processing. */
pNodeBase->cachedFrameCountOut = 0; pNodeBase->cachedFrameCountOut = 0;
...@@ -4601,7 +4723,7 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde ...@@ -4601,7 +4723,7 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde
for the current time period don't pull in data when they should instead be reading from cache. for the current time period don't pull in data when they should instead be reading from cache.
*/ */
for (iOutputBus = 0; iOutputBus < outputBusCount; iOutputBus += 1) { for (iOutputBus = 0; iOutputBus < outputBusCount; iOutputBus += 1) {
ma_node_output_bus_set_has_read(&pNodeBase->outputBuses[iOutputBus], MA_FALSE); /* <-- This is what tells the next calls to this function for other output buses for this time period to read from cache instead of pulling in more data. */ ma_node_output_bus_set_has_read(&pNodeBase->pOutputBuses[iOutputBus], MA_FALSE); /* <-- This is what tells the next calls to this function for other output buses for this time period to read from cache instead of pulling in more data. */
ppFramesOut[iOutputBus] = ma_node_get_cached_output_ptr(pNode, iOutputBus); ppFramesOut[iOutputBus] = ma_node_get_cached_output_ptr(pNode, iOutputBus);
} }
...@@ -4617,7 +4739,7 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde ...@@ -4617,7 +4739,7 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde
ppFramesIn[iInputBus] = ma_node_get_cached_input_ptr(pNode, iInputBus); ppFramesIn[iInputBus] = ma_node_get_cached_input_ptr(pNode, iInputBus);
/* Once we've determined our destination pointer we can read. Note that we must inspect the number of frames read and fill any leftovers with silence for safety. */ /* Once we've determined our destination pointer we can read. Note that we must inspect the number of frames read and fill any leftovers with silence for safety. */
result = ma_node_input_bus_read_pcm_frames(pNodeBase, &pNodeBase->inputBuses[iInputBus], ppFramesIn[iInputBus], framesToProcessIn, &framesRead, globalTime); result = ma_node_input_bus_read_pcm_frames(pNodeBase, &pNodeBase->pInputBuses[iInputBus], ppFramesIn[iInputBus], framesToProcessIn, &framesRead, globalTime);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
/* It doesn't really matter if we fail because we'll just fill with silence. */ /* It doesn't really matter if we fail because we'll just fill with silence. */
framesRead = 0; /* Just for safety, but I don't think it's really needed. */ framesRead = 0; /* Just for safety, but I don't think it's really needed. */
...@@ -4736,7 +4858,7 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde ...@@ -4736,7 +4858,7 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde
totalFramesRead = pNodeBase->cachedFrameCountOut; totalFramesRead = pNodeBase->cachedFrameCountOut;
/* Now that we've read the data, make sure our read flag is set. */ /* Now that we've read the data, make sure our read flag is set. */
ma_node_output_bus_set_has_read(&pNodeBase->outputBuses[outputBusIndex], MA_TRUE); ma_node_output_bus_set_has_read(&pNodeBase->pOutputBuses[outputBusIndex], MA_TRUE);
} }
} }
...@@ -4837,15 +4959,15 @@ MA_API ma_result ma_data_source_node_init(ma_node_graph* pNodeGraph, const ma_da ...@@ -4837,15 +4959,15 @@ MA_API ma_result ma_data_source_node_init(ma_node_graph* pNodeGraph, const ma_da
/* /*
The channel count is defined by the data source. It is invalid for the caller to manually set The channel count is defined by the data source. It is invalid for the caller to manually set
the channel counts in the config. `ma_data_source_node_config_init()` will have defaulted the the channel counts in the config. `ma_data_source_node_config_init()` will have defaulted the
channel count to 0 which is how it must remain. If you trigger any of these asserts, it means channel count pointer to NULL which is how it must remain. If you trigger any of these asserts
you're explicitly setting the channel count. Instead, configure the output channel count of it means you're explicitly setting the channel count. Instead, configure the output channel
your data source to be the necessary channel count. count of your data source to be the necessary channel count.
*/ */
if (baseConfig.outputChannels[0] != 0) { if (baseConfig.pOutputChannels != NULL) {
return MA_INVALID_ARGS; return MA_INVALID_ARGS;
} }
baseConfig.outputChannels[0] = channels; baseConfig.pOutputChannels = &channels;
result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pDataSourceNode->base); result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pDataSourceNode->base);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
...@@ -4889,13 +5011,18 @@ MA_API ma_bool32 ma_data_source_node_is_looping(ma_data_source_node* pDataSource ...@@ -4889,13 +5011,18 @@ MA_API ma_bool32 ma_data_source_node_is_looping(ma_data_source_node* pDataSource
MA_API ma_splitter_node_config ma_splitter_node_config_init(ma_uint32 channels) 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_uint32 inputChannels[1];
ma_uint32 outputChannels[2];
/* Same channel count between inputs and outputs are required for splitters. */ /* Same channel count between inputs and outputs are required for splitters. */
inputChannels[0] = channels;
outputChannels[0] = channels;
outputChannels[1] = channels;
MA_ZERO_OBJECT(&config); MA_ZERO_OBJECT(&config);
config.nodeConfig = ma_node_config_init(); config.nodeConfig = ma_node_config_init();
config.nodeConfig.inputChannels[0] = channels; config.nodeConfig.pInputChannels = inputChannels;
config.nodeConfig.outputChannels[0] = channels; config.nodeConfig.pOutputChannels = outputChannels;
config.nodeConfig.outputChannels[1] = channels;
return config; return config;
} }
...@@ -4947,8 +5074,12 @@ MA_API ma_result ma_splitter_node_init(ma_node_graph* pNodeGraph, const ma_split ...@@ -4947,8 +5074,12 @@ MA_API ma_result ma_splitter_node_init(ma_node_graph* pNodeGraph, const ma_split
return MA_INVALID_ARGS; return MA_INVALID_ARGS;
} }
if (pConfig->nodeConfig.pInputChannels == NULL || pConfig->nodeConfig.pOutputChannels == NULL) {
return MA_INVALID_ARGS; /* No channel counts specified. */
}
/* Splitters require the same number of channels between inputs and outputs. */ /* Splitters require the same number of channels between inputs and outputs. */
if (pConfig->nodeConfig.inputChannels[0] != pConfig->nodeConfig.outputChannels[0]) { if (pConfig->nodeConfig.pInputChannels[0] != pConfig->nodeConfig.pOutputChannels[0]) {
return MA_INVALID_ARGS; return MA_INVALID_ARGS;
} }
...@@ -11861,16 +11992,16 @@ MA_API ma_result ma_engine_node_init(const ma_engine_node_config* pConfig, const ...@@ -11861,16 +11992,16 @@ MA_API ma_result ma_engine_node_init(const ma_engine_node_config* pConfig, const
if (pConfig->type == ma_engine_node_type_sound) { if (pConfig->type == ma_engine_node_type_sound) {
/* Sound. */ /* Sound. */
baseNodeConfig = ma_node_config_init(); baseNodeConfig = ma_node_config_init();
baseNodeConfig.vtable = &g_ma_engine_node_vtable__sound; baseNodeConfig.vtable = &g_ma_engine_node_vtable__sound;
baseNodeConfig.inputChannels[0] = channelsIn; baseNodeConfig.pInputChannels = &channelsIn;
baseNodeConfig.outputChannels[0] = channelsOut; baseNodeConfig.pOutputChannels = &channelsOut;
baseNodeConfig.initialState = ma_node_state_stopped; /* Sounds are stopped by default. */ baseNodeConfig.initialState = ma_node_state_stopped; /* Sounds are stopped by default. */
} else { } else {
/* Group. */ /* Group. */
baseNodeConfig = ma_node_config_init(); baseNodeConfig = ma_node_config_init();
baseNodeConfig.vtable = &g_ma_engine_node_vtable__group; baseNodeConfig.vtable = &g_ma_engine_node_vtable__group;
baseNodeConfig.inputChannels[0] = channelsIn; baseNodeConfig.pInputChannels = &channelsIn;
baseNodeConfig.outputChannels[0] = channelsOut; baseNodeConfig.pOutputChannels = &channelsOut;
baseNodeConfig.initialState = ma_node_state_started; /* Groups are started by default. */ baseNodeConfig.initialState = ma_node_state_started; /* Groups are started by default. */
} }
...@@ -11897,7 +12028,7 @@ MA_API ma_result ma_engine_node_init(const ma_engine_node_config* pConfig, const ...@@ -11897,7 +12028,7 @@ MA_API ma_result ma_engine_node_init(const ma_engine_node_config* pConfig, const
*/ */
/* We'll always do resampling first. */ /* We'll always do resampling first. */
resamplerConfig = ma_resampler_config_init(ma_format_f32, baseNodeConfig.inputChannels[0], pEngineNode->sampleRate, ma_engine_get_sample_rate(pEngineNode->pEngine), ma_resample_algorithm_linear); resamplerConfig = ma_resampler_config_init(ma_format_f32, baseNodeConfig.pInputChannels[0], pEngineNode->sampleRate, ma_engine_get_sample_rate(pEngineNode->pEngine), ma_resample_algorithm_linear);
resamplerConfig.linear.lpfOrder = 0; /* <-- Need to disable low-pass filtering for pitch shifting for now because there's cases where the biquads are becoming unstable. Need to figure out a better fix for this. */ resamplerConfig.linear.lpfOrder = 0; /* <-- Need to disable low-pass filtering for pitch shifting for now because there's cases where the biquads are becoming unstable. Need to figure out a better fix for this. */
result = ma_resampler_init(&resamplerConfig, &pEngineNode->resampler); result = ma_resampler_init(&resamplerConfig, &pEngineNode->resampler);
...@@ -11907,7 +12038,7 @@ MA_API ma_result ma_engine_node_init(const ma_engine_node_config* pConfig, const ...@@ -11907,7 +12038,7 @@ MA_API ma_result ma_engine_node_init(const ma_engine_node_config* pConfig, const
/* After resampling will come the fader. */ /* After resampling will come the fader. */
faderConfig = ma_fader_config_init(ma_format_f32, baseNodeConfig.inputChannels[0], ma_engine_get_sample_rate(pEngineNode->pEngine)); faderConfig = ma_fader_config_init(ma_format_f32, baseNodeConfig.pInputChannels[0], ma_engine_get_sample_rate(pEngineNode->pEngine));
result = ma_fader_init(&faderConfig, &pEngineNode->fader); result = ma_fader_init(&faderConfig, &pEngineNode->fader);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
...@@ -11919,7 +12050,7 @@ MA_API ma_result ma_engine_node_init(const ma_engine_node_config* pConfig, const ...@@ -11919,7 +12050,7 @@ MA_API ma_result ma_engine_node_init(const ma_engine_node_config* pConfig, const
Spatialization comes next. We spatialize based ont he node's output channel count. It's up the caller to Spatialization comes next. We spatialize based ont he node's output channel count. It's up the caller to
ensure channels counts link up correctly in the node graph. ensure channels counts link up correctly in the node graph.
*/ */
spatializerConfig = ma_spatializer_config_init(baseNodeConfig.inputChannels[0], baseNodeConfig.outputChannels[0]); spatializerConfig = ma_spatializer_config_init(baseNodeConfig.pInputChannels[0], baseNodeConfig.pOutputChannels[0]);
spatializerConfig.gainSmoothTimeInFrames = pEngineNode->pEngine->gainSmoothTimeInFrames; spatializerConfig.gainSmoothTimeInFrames = pEngineNode->pEngine->gainSmoothTimeInFrames;
result = ma_spatializer_init(&spatializerConfig, &pEngineNode->spatializer); result = ma_spatializer_init(&spatializerConfig, &pEngineNode->spatializer);
...@@ -11932,7 +12063,7 @@ MA_API ma_result ma_engine_node_init(const ma_engine_node_config* pConfig, const ...@@ -11932,7 +12063,7 @@ MA_API ma_result ma_engine_node_init(const ma_engine_node_config* pConfig, const
After spatialization comes panning. We need to do this after spatialization because otherwise we wouldn't After spatialization comes panning. We need to do this after spatialization because otherwise we wouldn't
be able to pan mono sounds. be able to pan mono sounds.
*/ */
pannerConfig = ma_panner_config_init(ma_format_f32, baseNodeConfig.outputChannels[0]); pannerConfig = ma_panner_config_init(ma_format_f32, baseNodeConfig.pOutputChannels[0]);
result = ma_panner_init(&pannerConfig, &pEngineNode->panner); result = ma_panner_init(&pannerConfig, &pEngineNode->panner);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
...@@ -13455,7 +13586,7 @@ MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, ...@@ -13455,7 +13586,7 @@ MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat,
} }
if (pChannels != NULL) { if (pChannels != NULL) {
*pChannels = pSound->engineNode.baseNode.inputBuses[0].channels; *pChannels = ma_node_get_input_channels(&pSound->engineNode, 0);
} }
if (pSampleRate != NULL) { if (pSampleRate != NULL) {
...@@ -13821,9 +13952,9 @@ MA_API ma_result ma_biquad_node_init(ma_node_graph* pNodeGraph, const ma_biquad_ ...@@ -13821,9 +13952,9 @@ MA_API ma_result ma_biquad_node_init(ma_node_graph* pNodeGraph, const ma_biquad_
} }
baseNodeConfig = ma_node_config_init(); baseNodeConfig = ma_node_config_init();
baseNodeConfig.vtable = &g_ma_biquad_node_vtable; baseNodeConfig.vtable = &g_ma_biquad_node_vtable;
baseNodeConfig.inputChannels[0] = pConfig->biquad.channels; baseNodeConfig.pInputChannels = &pConfig->biquad.channels;
baseNodeConfig.outputChannels[0] = pConfig->biquad.channels; baseNodeConfig.pOutputChannels = &pConfig->biquad.channels;
result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
...@@ -13906,9 +14037,9 @@ MA_API ma_result ma_lpf_node_init(ma_node_graph* pNodeGraph, const ma_lpf_node_c ...@@ -13906,9 +14037,9 @@ MA_API ma_result ma_lpf_node_init(ma_node_graph* pNodeGraph, const ma_lpf_node_c
} }
baseNodeConfig = ma_node_config_init(); baseNodeConfig = ma_node_config_init();
baseNodeConfig.vtable = &g_ma_lpf_node_vtable; baseNodeConfig.vtable = &g_ma_lpf_node_vtable;
baseNodeConfig.inputChannels[0] = pConfig->lpf.channels; baseNodeConfig.pInputChannels = &pConfig->lpf.channels;
baseNodeConfig.outputChannels[0] = pConfig->lpf.channels; baseNodeConfig.pOutputChannels = &pConfig->lpf.channels;
result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
...@@ -13991,9 +14122,9 @@ MA_API ma_result ma_hpf_node_init(ma_node_graph* pNodeGraph, const ma_hpf_node_c ...@@ -13991,9 +14122,9 @@ MA_API ma_result ma_hpf_node_init(ma_node_graph* pNodeGraph, const ma_hpf_node_c
} }
baseNodeConfig = ma_node_config_init(); baseNodeConfig = ma_node_config_init();
baseNodeConfig.vtable = &g_ma_hpf_node_vtable; baseNodeConfig.vtable = &g_ma_hpf_node_vtable;
baseNodeConfig.inputChannels[0] = pConfig->hpf.channels; baseNodeConfig.pInputChannels = &pConfig->hpf.channels;
baseNodeConfig.outputChannels[0] = pConfig->hpf.channels; baseNodeConfig.pOutputChannels = &pConfig->hpf.channels;
result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
...@@ -14077,9 +14208,9 @@ MA_API ma_result ma_bpf_node_init(ma_node_graph* pNodeGraph, const ma_bpf_node_c ...@@ -14077,9 +14208,9 @@ MA_API ma_result ma_bpf_node_init(ma_node_graph* pNodeGraph, const ma_bpf_node_c
} }
baseNodeConfig = ma_node_config_init(); baseNodeConfig = ma_node_config_init();
baseNodeConfig.vtable = &g_ma_bpf_node_vtable; baseNodeConfig.vtable = &g_ma_bpf_node_vtable;
baseNodeConfig.inputChannels[0] = pConfig->bpf.channels; baseNodeConfig.pInputChannels = &pConfig->bpf.channels;
baseNodeConfig.outputChannels[0] = pConfig->bpf.channels; baseNodeConfig.pOutputChannels = &pConfig->bpf.channels;
result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
...@@ -14162,9 +14293,9 @@ MA_API ma_result ma_notch_node_init(ma_node_graph* pNodeGraph, const ma_notch_no ...@@ -14162,9 +14293,9 @@ MA_API ma_result ma_notch_node_init(ma_node_graph* pNodeGraph, const ma_notch_no
} }
baseNodeConfig = ma_node_config_init(); baseNodeConfig = ma_node_config_init();
baseNodeConfig.vtable = &g_ma_notch_node_vtable; baseNodeConfig.vtable = &g_ma_notch_node_vtable;
baseNodeConfig.inputChannels[0] = pConfig->notch.channels; baseNodeConfig.pInputChannels = &pConfig->notch.channels;
baseNodeConfig.outputChannels[0] = pConfig->notch.channels; baseNodeConfig.pOutputChannels = &pConfig->notch.channels;
result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
...@@ -14248,9 +14379,9 @@ MA_API ma_result ma_peak_node_init(ma_node_graph* pNodeGraph, const ma_peak_node ...@@ -14248,9 +14379,9 @@ MA_API ma_result ma_peak_node_init(ma_node_graph* pNodeGraph, const ma_peak_node
} }
baseNodeConfig = ma_node_config_init(); baseNodeConfig = ma_node_config_init();
baseNodeConfig.vtable = &g_ma_peak_node_vtable; baseNodeConfig.vtable = &g_ma_peak_node_vtable;
baseNodeConfig.inputChannels[0] = pConfig->peak.channels; baseNodeConfig.pInputChannels = &pConfig->peak.channels;
baseNodeConfig.outputChannels[0] = pConfig->peak.channels; baseNodeConfig.pOutputChannels = &pConfig->peak.channels;
result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
...@@ -14333,9 +14464,9 @@ MA_API ma_result ma_loshelf_node_init(ma_node_graph* pNodeGraph, const ma_loshel ...@@ -14333,9 +14464,9 @@ MA_API ma_result ma_loshelf_node_init(ma_node_graph* pNodeGraph, const ma_loshel
} }
baseNodeConfig = ma_node_config_init(); baseNodeConfig = ma_node_config_init();
baseNodeConfig.vtable = &g_ma_loshelf_node_vtable; baseNodeConfig.vtable = &g_ma_loshelf_node_vtable;
baseNodeConfig.inputChannels[0] = pConfig->loshelf.channels; baseNodeConfig.pInputChannels = &pConfig->loshelf.channels;
baseNodeConfig.outputChannels[0] = pConfig->loshelf.channels; baseNodeConfig.pOutputChannels = &pConfig->loshelf.channels;
result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
...@@ -14418,9 +14549,9 @@ MA_API ma_result ma_hishelf_node_init(ma_node_graph* pNodeGraph, const ma_hishel ...@@ -14418,9 +14549,9 @@ MA_API ma_result ma_hishelf_node_init(ma_node_graph* pNodeGraph, const ma_hishel
} }
baseNodeConfig = ma_node_config_init(); baseNodeConfig = ma_node_config_init();
baseNodeConfig.vtable = &g_ma_hishelf_node_vtable; baseNodeConfig.vtable = &g_ma_hishelf_node_vtable;
baseNodeConfig.inputChannels[0] = pConfig->hishelf.channels; baseNodeConfig.pInputChannels = &pConfig->hishelf.channels;
baseNodeConfig.outputChannels[0] = pConfig->hishelf.channels; baseNodeConfig.pOutputChannels = &pConfig->hishelf.channels;
result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
...@@ -14651,11 +14782,9 @@ MA_API ma_result ma_delay_node_init(ma_node_graph* pNodeGraph, const ma_delay_no ...@@ -14651,11 +14782,9 @@ MA_API ma_result ma_delay_node_init(ma_node_graph* pNodeGraph, const ma_delay_no
} }
baseConfig = pConfig->nodeConfig; baseConfig = pConfig->nodeConfig;
baseConfig.vtable = &g_ma_delay_node_vtable; baseConfig.vtable = &g_ma_delay_node_vtable;
baseConfig.inputChannels [0] = pConfig->delay.channels; baseConfig.pInputChannels = &pConfig->delay.channels;
baseConfig.inputChannels [1] = 0; /* Unused. */ baseConfig.pOutputChannels = &pConfig->delay.channels;
baseConfig.outputChannels[0] = pConfig->delay.channels;
baseConfig.outputChannels[1] = 0; /* Unused. */
result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pDelayNode->baseNode); result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pDelayNode->baseNode);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
......
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