mirror of https://github.com/mackron/miniaudio.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
251 lines
10 KiB
C
251 lines
10 KiB
C
/*
|
|
This example shows how to use the node graph system.
|
|
|
|
The node graph system can be used for doing complex mixing and effect processing. The idea is that
|
|
you have a number of nodes that are connected to each other to form a graph. At the end of the
|
|
graph is an endpoint which all nodes eventually connect to.
|
|
|
|
A node is used to do some kind of processing on zero or more input streams and produce one or more
|
|
output streams. Each node can have a number of inputs and outputs. Each of these is called a bus in
|
|
miniaudio. Some nodes, particularly data source nodes, have no inputs and instead generate their
|
|
outputs dynamically. All nodes will have at least one output or else it'll be disconnected from the
|
|
graph and will never get processed. Each output bus of a node will be connected to an input bus of
|
|
another node, but they don't all need to connect to the same input node. For example, a splitter
|
|
node has 1 input bus and 2 output buses and is used to duplicate a signal. You could then branch
|
|
off and have one output bus connected to one input node and the other connected to a different
|
|
input node, and then have two different effects process for each of the duplicated branches.
|
|
|
|
Any number of output buses can be connected to an input bus in which case the output buses will be
|
|
mixed before processing by the input node. This is how you would achieve the mixing part of the
|
|
node graph.
|
|
|
|
This example will be using the following node graph set up:
|
|
|
|
```
|
|
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Data flows left to right >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
|
|
+---------------+ +-----------------+
|
|
| Data Source 1 =----+ +----------+ +----= Low Pass Filter =----+
|
|
+---------------+ | | =----+ +-----------------+ | +----------+
|
|
+----= Splitter | +----= ENDPOINT |
|
|
+---------------+ | | =----+ +-----------------+ | +----------+
|
|
| Data Source 2 =----+ +----------+ +----= Echo / Delay =----+
|
|
+---------------+ +-----------------+
|
|
```
|
|
|
|
This does not represent a realistic real-world scenario, but it demonstrates how to make use of
|
|
mixing, multiple outputs and multiple effects.
|
|
|
|
The data source nodes are connected to the input of the splitter. They'll be mixed before being
|
|
processed by the splitter. The splitter has two output buses. In the graph above, one bus will be
|
|
routed to a low pass filter, whereas the other bus will be routed to an echo effect. Then, the
|
|
outputs of these two effects will be connected to the input bus of the endpoint. Because both of
|
|
the outputs are connected to the same input bus, they'll be mixed at that point.
|
|
|
|
The two data sources at the start of the graph have no inputs. They'll instead generate their
|
|
output by reading from a data source. The data source in this case will be one `ma_decoder` for
|
|
each input file specified on the command line.
|
|
|
|
You can also control the volume of an output bus. In this example, we set the volumes of the low
|
|
pass and echo effects so that one of them becomes more obvious than the other.
|
|
|
|
When you want to read from the graph, you simply call `ma_node_graph_read_pcm_frames()`.
|
|
*/
|
|
#define MINIAUDIO_IMPLEMENTATION
|
|
#include "../miniaudio.h"
|
|
|
|
/* Data Format */
|
|
#define FORMAT ma_format_f32 /* Must always be f32. */
|
|
#define CHANNELS 2
|
|
#define SAMPLE_RATE 48000
|
|
|
|
/* Effect Properties */
|
|
#define LPF_BIAS 0.9f /* Higher values means more bias towards the low pass filter (the low pass filter will be more audible). Lower values means more bias towards the echo. Must be between 0 and 1. */
|
|
#define LPF_CUTOFF_FACTOR 80 /* High values = more filter. */
|
|
#define LPF_ORDER 8
|
|
#define DELAY_IN_SECONDS 0.2f
|
|
#define DECAY 0.5f /* Volume falloff for each echo. */
|
|
|
|
typedef struct
|
|
{
|
|
ma_data_source_node node; /* If you make this the first member, you can pass a pointer to this struct into any `ma_node_*` API and it will "Just Work". */
|
|
ma_decoder decoder;
|
|
} sound_node;
|
|
|
|
static ma_node_graph g_nodeGraph;
|
|
static ma_lpf_node g_lpfNode;
|
|
static ma_delay_node g_delayNode;
|
|
static ma_splitter_node g_splitterNode;
|
|
static sound_node* g_pSoundNodes;
|
|
static int g_soundNodeCount;
|
|
|
|
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
|
{
|
|
MA_ASSERT(pDevice->playback.channels == CHANNELS);
|
|
|
|
/*
|
|
Hearing the output of the node graph is as easy as reading straight into the output buffer. You just need to
|
|
make sure you use a consistent data format or else you'll need to do your own conversion.
|
|
*/
|
|
ma_node_graph_read_pcm_frames(&g_nodeGraph, pOutput, frameCount, NULL);
|
|
|
|
(void)pInput; /* Unused. */
|
|
}
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
int iarg;
|
|
ma_result result;
|
|
|
|
/* We'll set up our nodes starting from the end and working our way back to the start. We'll need to set up the graph first. */
|
|
{
|
|
ma_node_graph_config nodeGraphConfig = ma_node_graph_config_init(CHANNELS);
|
|
|
|
result = ma_node_graph_init(&nodeGraphConfig, NULL, &g_nodeGraph);
|
|
if (result != MA_SUCCESS) {
|
|
printf("ERROR: Failed to initialize node graph.");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
/* Low Pass Filter. */
|
|
{
|
|
ma_lpf_node_config lpfNodeConfig = ma_lpf_node_config_init(CHANNELS, SAMPLE_RATE, SAMPLE_RATE / LPF_CUTOFF_FACTOR, LPF_ORDER);
|
|
|
|
result = ma_lpf_node_init(&g_nodeGraph, &lpfNodeConfig, NULL, &g_lpfNode);
|
|
if (result != MA_SUCCESS) {
|
|
printf("ERROR: Failed to initialize low pass filter node.");
|
|
return -1;
|
|
}
|
|
|
|
/* Connect the output bus of the low pass filter node to the input bus of the endpoint. */
|
|
ma_node_attach_output_bus(&g_lpfNode, 0, ma_node_graph_get_endpoint(&g_nodeGraph), 0);
|
|
|
|
/* Set the volume of the low pass filter to make it more of less impactful. */
|
|
ma_node_set_output_bus_volume(&g_lpfNode, 0, LPF_BIAS);
|
|
}
|
|
|
|
|
|
/* Echo / Delay. */
|
|
{
|
|
ma_delay_node_config delayNodeConfig = ma_delay_node_config_init(CHANNELS, SAMPLE_RATE, (ma_uint32)(SAMPLE_RATE * DELAY_IN_SECONDS), DECAY);
|
|
|
|
result = ma_delay_node_init(&g_nodeGraph, &delayNodeConfig, NULL, &g_delayNode);
|
|
if (result != MA_SUCCESS) {
|
|
printf("ERROR: Failed to initialize delay node.");
|
|
return -1;
|
|
}
|
|
|
|
/* Connect the output bus of the delay node to the input bus of the endpoint. */
|
|
ma_node_attach_output_bus(&g_delayNode, 0, ma_node_graph_get_endpoint(&g_nodeGraph), 0);
|
|
|
|
/* Set the volume of the delay filter to make it more of less impactful. */
|
|
ma_node_set_output_bus_volume(&g_delayNode, 0, 1 - LPF_BIAS);
|
|
}
|
|
|
|
|
|
/* Splitter. */
|
|
{
|
|
ma_splitter_node_config splitterNodeConfig = ma_splitter_node_config_init(CHANNELS);
|
|
|
|
result = ma_splitter_node_init(&g_nodeGraph, &splitterNodeConfig, NULL, &g_splitterNode);
|
|
if (result != MA_SUCCESS) {
|
|
printf("ERROR: Failed to initialize splitter node.");
|
|
return -1;
|
|
}
|
|
|
|
/* Connect output bus 0 to the input bus of the low pass filter node, and output bus 1 to the input bus of the delay node. */
|
|
ma_node_attach_output_bus(&g_splitterNode, 0, &g_lpfNode, 0);
|
|
ma_node_attach_output_bus(&g_splitterNode, 1, &g_delayNode, 0);
|
|
}
|
|
|
|
|
|
/* Data sources. Ignore any that cannot be loaded. */
|
|
g_pSoundNodes = (sound_node*)ma_malloc(sizeof(*g_pSoundNodes) * argc-1, NULL);
|
|
if (g_pSoundNodes == NULL) {
|
|
printf("Failed to allocate memory for sounds.");
|
|
return -1;
|
|
}
|
|
|
|
g_soundNodeCount = 0;
|
|
for (iarg = 1; iarg < argc; iarg += 1) {
|
|
ma_decoder_config decoderConfig = ma_decoder_config_init(FORMAT, CHANNELS, SAMPLE_RATE);
|
|
|
|
result = ma_decoder_init_file(argv[iarg], &decoderConfig, &g_pSoundNodes[g_soundNodeCount].decoder);
|
|
if (result == MA_SUCCESS) {
|
|
ma_data_source_node_config dataSourceNodeConfig = ma_data_source_node_config_init(&g_pSoundNodes[g_soundNodeCount].decoder);
|
|
|
|
result = ma_data_source_node_init(&g_nodeGraph, &dataSourceNodeConfig, NULL, &g_pSoundNodes[g_soundNodeCount].node);
|
|
if (result == MA_SUCCESS) {
|
|
/* The data source node has been created successfully. Attach it to the splitter. */
|
|
ma_node_attach_output_bus(&g_pSoundNodes[g_soundNodeCount].node, 0, &g_splitterNode, 0);
|
|
g_soundNodeCount += 1;
|
|
} else {
|
|
printf("WARNING: Failed to init data source node for sound \"%s\". Ignoring.", argv[iarg]);
|
|
ma_decoder_uninit(&g_pSoundNodes[g_soundNodeCount].decoder);
|
|
}
|
|
} else {
|
|
printf("WARNING: Failed to load sound \"%s\". Ignoring.", argv[iarg]);
|
|
}
|
|
}
|
|
|
|
/* Everything has been initialized successfully so now we can set up a playback device so we can listen to the result. */
|
|
{
|
|
ma_device_config deviceConfig;
|
|
ma_device device;
|
|
|
|
deviceConfig = ma_device_config_init(ma_device_type_playback);
|
|
deviceConfig.playback.format = FORMAT;
|
|
deviceConfig.playback.channels = CHANNELS;
|
|
deviceConfig.sampleRate = SAMPLE_RATE;
|
|
deviceConfig.dataCallback = data_callback;
|
|
deviceConfig.pUserData = NULL;
|
|
|
|
result = ma_device_init(NULL, &deviceConfig, &device);
|
|
if (result != MA_SUCCESS) {
|
|
printf("ERROR: Failed to initialize device.");
|
|
goto cleanup_graph;
|
|
}
|
|
|
|
result = ma_device_start(&device);
|
|
if (result != MA_SUCCESS) {
|
|
ma_device_uninit(&device);
|
|
goto cleanup_graph;
|
|
}
|
|
|
|
printf("Press Enter to quit...\n");
|
|
getchar();
|
|
|
|
/* We're done. Clean up the device. */
|
|
ma_device_uninit(&device);
|
|
}
|
|
|
|
|
|
cleanup_graph:
|
|
{
|
|
/* It's good practice to tear down the graph from the lowest level nodes first. */
|
|
int iSound;
|
|
|
|
/* Sounds. */
|
|
for (iSound = 0; iSound < g_soundNodeCount; iSound += 1) {
|
|
ma_data_source_node_uninit(&g_pSoundNodes[iSound].node, NULL);
|
|
ma_decoder_uninit(&g_pSoundNodes[iSound].decoder);
|
|
}
|
|
|
|
/* Splitter. */
|
|
ma_splitter_node_uninit(&g_splitterNode, NULL);
|
|
|
|
/* Echo / Delay */
|
|
ma_delay_node_uninit(&g_delayNode, NULL);
|
|
|
|
/* Low Pass Filter */
|
|
ma_lpf_node_uninit(&g_lpfNode, NULL);
|
|
|
|
/* Node Graph */
|
|
ma_node_graph_uninit(&g_nodeGraph, NULL);
|
|
}
|
|
|
|
return 0;
|
|
}
|