Commit ba3080d0 authored by David Reid's avatar David Reid

Add support for partial processing to nodes.

This includes a leading trimming node to act as a test.
parent b8d04bdd
#include "ma_ltrim_node.h"
MA_API ma_ltrim_node_config ma_ltrim_node_config_init(ma_uint32 channels, float threshold)
{
ma_ltrim_node_config config;
MA_ZERO_OBJECT(&config);
config.nodeConfig = ma_node_config_init(); /* Input and output channels will be set in ma_ltrim_node_init(). */
config.channels = channels;
config.threshold = threshold;
return config;
}
static void ma_ltrim_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut)
{
ma_ltrim_node* pTrimNode = (ma_ltrim_node*)pNode;
ma_uint32 framesProcessedIn = 0;
ma_uint32 framesProcessedOut = 0;
ma_uint32 channelCount = ma_node_get_input_channels(pNode, 0);
/*
If we haven't yet found the start, skip over every input sample until we find a frame outside
of the threshold.
*/
if (pTrimNode->foundStart == MA_FALSE) {
while (framesProcessedIn < *pFrameCountIn) {
ma_uint32 iChannel = 0;
for (iChannel = 0; iChannel < channelCount; iChannel += 1) {
float sample = ppFramesIn[0][framesProcessedIn*channelCount + iChannel];
if (sample < -pTrimNode->threshold || sample > pTrimNode->threshold) {
pTrimNode->foundStart = MA_TRUE;
break;
}
}
if (pTrimNode->foundStart) {
break; /* The start has been found. Get out of this loop and finish off processing. */
} else {
framesProcessedIn += 1;
}
}
}
/* If there's anything left, just copy it over. */
framesProcessedOut = ma_min(*pFrameCountOut, *pFrameCountIn - framesProcessedIn);
ma_copy_pcm_frames(ppFramesOut[0], &ppFramesIn[0][framesProcessedIn], framesProcessedOut, ma_format_f32, channelCount);
framesProcessedIn += framesProcessedOut;
/* We always "process" every input frame, but we may only done a partial output. */
*pFrameCountIn = framesProcessedIn;
*pFrameCountOut = framesProcessedOut;
}
static ma_node_vtable g_ma_ltrim_node_vtable =
{
ma_ltrim_node_process_pcm_frames,
NULL,
1, /* 1 input channel. */
1, /* 1 output channel. */
MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES
};
MA_API ma_result ma_ltrim_node_init(ma_node_graph* pNodeGraph, const ma_ltrim_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_ltrim_node* pTrimNode)
{
ma_result result;
ma_node_config baseConfig;
if (pTrimNode == NULL) {
return MA_INVALID_ARGS;
}
MA_ZERO_OBJECT(pTrimNode);
if (pConfig == NULL) {
return MA_INVALID_ARGS;
}
pTrimNode->threshold = pConfig->threshold;
pTrimNode->foundStart = MA_FALSE;
baseConfig = pConfig->nodeConfig;
baseConfig.vtable = &g_ma_ltrim_node_vtable;
baseConfig.pInputChannels = &pConfig->channels;
baseConfig.pOutputChannels = &pConfig->channels;
result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pTrimNode->baseNode);
if (result != MA_SUCCESS) {
return result;
}
return MA_SUCCESS;
}
MA_API void ma_ltrim_node_uninit(ma_ltrim_node* pTrimNode, const ma_allocation_callbacks* pAllocationCallbacks)
{
/* The base node is always uninitialized first. */
ma_node_uninit(pTrimNode, pAllocationCallbacks);
}
/* Include ma_ltrim_node.h after miniaudio.h */
#ifndef ma_ltrim_node_h
#define ma_ltrim_node_h
#ifdef __cplusplus
extern "C" {
#endif
/*
The trim node has one input and one output.
*/
typedef struct
{
ma_node_config nodeConfig;
ma_uint32 channels;
float threshold;
} ma_ltrim_node_config;
MA_API ma_ltrim_node_config ma_ltrim_node_config_init(ma_uint32 channels, float threshold);
typedef struct
{
ma_node_base baseNode;
float threshold;
ma_bool32 foundStart;
} ma_ltrim_node;
MA_API ma_result ma_ltrim_node_init(ma_node_graph* pNodeGraph, const ma_ltrim_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_ltrim_node* pTrimNode);
MA_API void ma_ltrim_node_uninit(ma_ltrim_node* pTrimNode, const ma_allocation_callbacks* pAllocationCallbacks);
#ifdef __cplusplus
}
#endif
#endif /* ma_ltrim_node_h */
#define MINIAUDIO_IMPLEMENTATION
#include "../../../miniaudio.h"
#include "ma_ltrim_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 0 /* The input file will determine the sample rate. */
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_ltrim_node g_trimNode; /* The trim 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_ltrim_node_config trimNodeConfig;
ma_data_source_node_config dataSupplyNodeConfig;
if (argc < 1) {
printf("No input file.\n");
return -1;
}
/* Decoder. */
decoderConfig = ma_decoder_config_init(DEVICE_FORMAT, DEVICE_CHANNELS, 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;
}
/* Trimmer. Attached straight to the endpoint. Input will be the data source node. */
trimNodeConfig = ma_ltrim_node_config_init(device.playback.channels, 0);
result = ma_ltrim_node_init(&g_nodeGraph, &trimNodeConfig, NULL, &g_trimNode);
if (result != MA_SUCCESS) {
printf("Failed to initialize ltrim node.");
goto done1;
}
ma_node_attach_output_bus(&g_trimNode, 0, ma_node_graph_get_endpoint(&g_nodeGraph), 0);
/* Data supply. */
dataSupplyNodeConfig = ma_data_source_node_config_init(&g_decoder);
result = ma_data_source_node_init(&g_nodeGraph, &dataSupplyNodeConfig, NULL, &g_dataSupplyNode);
if (result != MA_SUCCESS) {
printf("Failed to initialize data source node.");
goto done2;
}
ma_node_attach_output_bus(&g_dataSupplyNode, 0, &g_trimNode, 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);
/*done3:*/ ma_data_source_node_uninit(&g_dataSupplyNode, NULL);
done2: ma_ltrim_node_uninit(&g_trimNode, NULL);
done1: ma_node_graph_uninit(&g_nodeGraph, NULL);
done0: ma_device_uninit(&device);
return 0;
}
\ No newline at end of file
...@@ -28,7 +28,7 @@ audio device. The high level API is good for those who have complex mixing and e ...@@ -28,7 +28,7 @@ audio device. The high level API is good for those who have complex mixing and e
1.1. Low Level API 1.1. Low Level API
----------------- ------------------
The low level API gives you access to the raw audio data of an audio device. It supports playback, The low level API gives you access to the raw audio data of an audio device. It supports playback,
capture, full-duplex and loopback (WASAPI only). You can enumerate over devices to determine which capture, full-duplex and loopback (WASAPI only). You can enumerate over devices to determine which
physical device(s) you want to connect to. physical device(s) you want to connect to.
...@@ -67466,6 +67466,9 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde ...@@ -67466,6 +67466,9 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde
/* 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;
for (;;) {
frameCountOut = 0;
/* /*
We need to prepare our output frame pointers for processing. In the same iteration we need We need to prepare our output frame pointers for processing. In the same iteration we need
to mark every output bus as unread so that future calls to this function for different buses to mark every output bus as unread so that future calls to this function for different buses
...@@ -67528,12 +67531,12 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde ...@@ -67528,12 +67531,12 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde
that the final copy into the output buffer is done directly by onProcess(). that the final copy into the output buffer is done directly by onProcess().
*/ */
if (pFramesOut != NULL) { if (pFramesOut != NULL) {
ppFramesOut[outputBusIndex] = pFramesOut; ppFramesOut[outputBusIndex] = ma_offset_pcm_frames_ptr_f32(pFramesOut, pNodeBase->cachedFrameCountOut, ma_node_get_output_channels(pNode, outputBusIndex));
} }
/* Give the processing function the entire capacity of the output buffer. */ /* Give the processing function the entire capacity of the output buffer. */
frameCountOut = framesToProcessOut; frameCountOut = (framesToProcessOut - pNodeBase->cachedFrameCountOut);
/* /*
We need to treat nodes with continuous processing a little differently. For these ones, We need to treat nodes with continuous processing a little differently. For these ones,
...@@ -67608,7 +67611,13 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde ...@@ -67608,7 +67611,13 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde
} }
/* The cached output frame count is always equal to what we just read. */ /* The cached output frame count is always equal to what we just read. */
pNodeBase->cachedFrameCountOut = (ma_uint16)frameCountOut; pNodeBase->cachedFrameCountOut += (ma_uint16)frameCountOut;
/* If we couldn't process any data, we're done. The loop needs to be terminated here or else we'll get stuck in a loop. */
if (pNodeBase->cachedFrameCountOut == framesToProcessOut || (frameCountOut == 0 && frameCountIn == 0)) {
break;
}
}
} else { } else {
/* /*
We're not needing to read anything from the input buffer so just read directly from our We're not needing to read anything from the input buffer so just read directly from our
...@@ -87790,7 +87799,7 @@ There are many breaking API changes in this release. ...@@ -87790,7 +87799,7 @@ There are many breaking API changes in this release.
have been removed. Instead you should set the encodingFormat variable in the decoder config. have been removed. Instead you should set the encodingFormat variable in the decoder config.
- ma_decoder_get_length_in_pcm_frames() has been updated to return a result code and output the - ma_decoder_get_length_in_pcm_frames() has been updated to return a result code and output the
length via an output parameter. This makes it consistent with data sources. length via an output parameter. This makes it consistent with data sources.
- ma_decoder_read_pcm_frames() has been update to return a result code and output the number of - ma_decoder_read_pcm_frames() has been updated to return a result code and output the number of
frames read via an output parameter. frames read via an output parameter.
- Allocation callbacks must now implement the onRealloc() callback. Previously miniaudio would - Allocation callbacks must now implement the onRealloc() callback. Previously miniaudio would
emulate this in terms of malloc and free, but for simplicity it is now required that allocation emulate this in terms of malloc and free, but for simplicity it is now required that allocation
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