From 9c7431ab912030d4a2b451d1f43d842037934f0b Mon Sep 17 00:00:00 2001 From: David Reid Date: Sat, 16 Jan 2021 14:06:24 +1000 Subject: [PATCH] Add support for metadata for nodes. This is useful for retrieving information about some aspect of the node. A good example is human readable names associated with the node and it's input and output buses. This is useful for user interfaces where a brief description of the node such as "Low Pass Filter" can be drawn on the screen. It's also useful for buses to be named, such as the source/carrier and excite/modulator on a vocoder effect which would also need to be visible on a UI. --- research/miniaudio_engine.h | 208 +++++++++++++++++++++++++++++++++--- 1 file changed, 196 insertions(+), 12 deletions(-) diff --git a/research/miniaudio_engine.h b/research/miniaudio_engine.h index ea2f9c7a..108289f4 100644 --- a/research/miniaudio_engine.h +++ b/research/miniaudio_engine.h @@ -747,6 +747,40 @@ typedef void ma_node; #define MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES 0x00000008 +/* +Metadata codes. + +You can retrieve metadata about a node as a whole, or individual input and output buses. The codes +used to retrieve that data is encoded. To retrieve the name of a node, do this: + + ma_node_get_metadata(&myNode, MA_NODE_METADATA_NAME, &metadata); + +To retrieve the name of the first input bus, do this: + + ma_node_get_metadata(&myNode, MA_NODE_METADATA_NAME | MA_NODE_METADATA_INPUT_BUS, &metadata); + +The same applies for the output bus, only used use MA_NODE_METADATA_OUTPUT_BUS instead. To retrieve +the name of the second input bus, encode the index as well: + + ma_node_get_metadata(&myNode, MA_NODE_METADATA_NAME | MA_NODE_METADATA_INPUT_BUS | 1, &metadata); + +You can mix an match. To do the same, but for the second output bus: + + ma_node_get_metadata(&myNode, MA_NODE_METADATA_NAME | MA_NODE_METADATA_OUTPUT_BUS | 1, &metadata); + +If some type of metadata does not make sense or is not supported, MA_NO_DATA_AVAILABLE is returned. +*/ +#define MA_NODE_METADATA_NAME 0x00001000 +#define MA_NODE_METADATA_INPUT_BUS 0x00000100 +#define MA_NODE_METADATA_OUTPUT_BUS 0x00000200 + +/* Masks for selecting sections of the metadata code. */ +#define MA_NODE_METADATA_PROPERTY_MASK 0xFFFFF000 +#define MA_NODE_METADATA_BUS_TYPE_MASK 0x00000F00 +#define MA_NODE_METADATA_BUS_INDEX_MASK 0x000000FF + + + /* The playback state of a node. Either started or stopped. */ typedef enum { @@ -755,6 +789,23 @@ typedef enum } ma_node_state; +typedef enum +{ + ma_node_metadata_type_integer, + ma_node_metadata_type_string +} ma_node_metadata_type; + +typedef struct +{ + ma_node_metadata_type type; + union + { + int i; + const char* str; /* Some constant string. */ + } value; +} ma_node_metadata; + + typedef struct { /* @@ -770,6 +821,21 @@ typedef struct */ void (* onProcess)(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut); + /* + A callback for retrieving the number of a input frames that are required to output the + specified number of output frames. You would only want to implement this when the node performs + resampling. This is optional, even for nodes that perform resampling, but it does offer a + small reduction in latency as it allows miniaudio to calculate the exact number of input frames + to read at a time instead of having to estimate. + */ + ma_uint32 (* onGetRequiredInputFrameCount)(ma_node* pNode, ma_uint32 outputFrameCount); + + /* + Retrieves some metadata about a node. Returns MA_NO_DATA_AVAILABLE if the metadata code is + invalid. This is optional. + */ + ma_result (* onGetMetadata)(ma_node* pNode, ma_uint32 metadataCode, ma_node_metadata* pMetadata); + /* The number of input buses. This is how many sub-buffers will be contained in the `ppFramesIn` parameters of the callbacks above. @@ -886,7 +952,10 @@ MA_API ma_uint64 ma_node_get_state_time(const ma_node* pNode, ma_node_state stat MA_API ma_node_state ma_node_get_state_by_time_range(const ma_node* pNode, ma_uint64 globalTimeBeg, ma_uint64 globalTimeEnd); MA_API ma_uint64 ma_node_get_time(const ma_node* pNode); MA_API ma_result ma_node_set_time(ma_node* pNode, ma_uint64 localTime); - +MA_API ma_result ma_node_get_metadata(ma_node* pNode, ma_uint32 metadataCode, ma_node_metadata* pMetadata); +MA_API const char* ma_node_get_name(ma_node* pNode); +MA_API const char* ma_node_get_input_bus_name(ma_node* pNode, ma_uint32 inputBusIndex); +MA_API const char* ma_node_get_output_bus_name(ma_node* pNode, ma_uint32 outputBusIndex); typedef struct @@ -2118,8 +2187,6 @@ static void ma_node_graph_endpoint_process_pcm_frames(ma_node* pNode, const floa (void)ppFramesOut; (void)pFrameCountOut; - printf("TESTING\n"); - #if 0 /* The data has already been mixed. We just need to move it to the output buffer. */ if (ppFramesIn != NULL) { @@ -2131,8 +2198,10 @@ static void ma_node_graph_endpoint_process_pcm_frames(ma_node* pNode, const floa static ma_node_vtable g_node_graph_endpoint_vtable = { ma_node_graph_endpoint_process_pcm_frames, - 1, /* 1 input bus. */ - 1, /* 1 output bus. */ + NULL, /* onGetRequiredInputFrameCount */ + NULL, /* onGetMetadata */ + 1, /* 1 input bus. */ + 1, /* 1 output bus. */ MA_NODE_FLAG_PASSTHROUGH /* Flags. The endpoint is a passthrough. */ }; @@ -3244,6 +3313,103 @@ MA_API ma_result ma_node_set_time(ma_node* pNode, ma_uint64 localTime) return MA_SUCCESS; } +MA_API ma_result ma_node_get_metadata(ma_node* pNode, ma_uint32 metadataCode, ma_node_metadata* pMetadata) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_uint32 propertyCode; + ma_uint32 busType; + ma_uint32 busIndex; + + if (pMetadata == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pMetadata); + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + propertyCode = (metadataCode & MA_NODE_METADATA_PROPERTY_MASK); + busType = (metadataCode & MA_NODE_METADATA_BUS_TYPE_MASK); + busIndex = (metadataCode & MA_NODE_METADATA_BUS_INDEX_MASK); + + if (busType == MA_NODE_METADATA_INPUT_BUS && busIndex >= ma_node_get_input_bus_count(pNode)) { + return MA_INVALID_ARGS; /* Invalid input bus index. */ + } + + if (busType == MA_NODE_METADATA_OUTPUT_BUS && busIndex >= ma_node_get_output_bus_count(pNode)) { + return MA_INVALID_ARGS; /* Invalid output bus index. */ + } + + if (pNodeBase->vtable->onGetMetadata) { + return pNodeBase->vtable->onGetMetadata(pNode, metadataCode, pMetadata); + } + + /* Getting here means we need to fall back to defaults. */ + if (propertyCode == MA_NODE_METADATA_NAME) { + pMetadata->type = ma_node_metadata_type_string; + + if (busType == MA_NODE_METADATA_INPUT_BUS) { + switch (busIndex) { + case 0: pMetadata->value.str = "Input Bus 0"; break; + case 1: pMetadata->value.str = "Input Bus 1"; break; + } + } else if (busType == MA_NODE_METADATA_OUTPUT_BUS) { + switch (busIndex) { + case 0: pMetadata->value.str = "Output Bus 0"; break; + case 1: pMetadata->value.str = "Output Bus 1"; break; + } + } else { + pMetadata->value.str = "Node"; + } + + return MA_SUCCESS; + } + + /* Getting here means we don't know how to handle defaults. */ + return MA_NO_DATA_AVAILABLE; +} + +MA_API const char* ma_node_get_name(ma_node* pNode) +{ + ma_result result; + ma_node_metadata metadata; + + result = ma_node_get_metadata(pNode, MA_NODE_METADATA_NAME, &metadata); + if (result != MA_SUCCESS) { + return "Node"; + } + + return metadata.value.str; +} + +MA_API const char* ma_node_get_input_bus_name(ma_node* pNode, ma_uint32 inputBusIndex) +{ + ma_result result; + ma_node_metadata metadata; + + result = ma_node_get_metadata(pNode, MA_NODE_METADATA_NAME | MA_NODE_METADATA_INPUT_BUS | inputBusIndex, &metadata); + if (result != MA_SUCCESS) { + return "Input Bus"; + } + + return metadata.value.str; +} + +MA_API const char* ma_node_get_output_bus_name(ma_node* pNode, ma_uint32 outputBusIndex) +{ + ma_result result; + ma_node_metadata metadata; + + result = ma_node_get_metadata(pNode, MA_NODE_METADATA_NAME | MA_NODE_METADATA_OUTPUT_BUS | outputBusIndex, &metadata); + if (result != MA_SUCCESS) { + return "Output Bus"; + } + + return metadata.value.str; +} + static void ma_node_process_pcm_frames_internal(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) @@ -3620,8 +3786,10 @@ static void ma_data_source_node_process_pcm_frames(ma_node* pNode, const float** static ma_node_vtable g_ma_data_source_node_vtable = { ma_data_source_node_process_pcm_frames, - 0, /* 0 input buses. */ - 1, /* 1 output bus. */ + NULL, /* onGetRequiredInputFrameCount */ + NULL, /* onGetMetadata */ + 0, /* 0 input buses. */ + 1, /* 1 output bus. */ 0 }; @@ -3744,8 +3912,10 @@ static void ma_splitter_node_process_pcm_frames(ma_node* pNode, const float** pp static ma_node_vtable g_ma_splitter_node_vtable = { ma_splitter_node_process_pcm_frames, - 1, /* 1 input bus. */ - 2, /* 2 output buses. */ + NULL, /* onGetRequiredInputFrameCount */ + NULL, /* onGetMetadata */ + 1, /* 1 input bus. */ + 2, /* 2 output buses. */ 0 }; @@ -8539,7 +8709,7 @@ static void ma_engine_node_process_pcm_frames__general(ma_engine_node* pEngineNo *pFrameCountOut = totalFramesProcessedOut; } -void ma_engine_node_process_pcm_frames__sound(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +static void ma_engine_node_process_pcm_frames__sound(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) { /* For sounds, we need to first read from the data source. Then we need to apply the engine effects (pan, pitch, fades, etc.). */ ma_result result = MA_SUCCESS; @@ -8649,7 +8819,7 @@ void ma_engine_node_process_pcm_frames__sound(ma_node* pNode, const float** ppFr ma_engine_node_update_pitch_if_required(&pSound->engineNode); } -void ma_engine_node_process_pcm_frames__group(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +static void ma_engine_node_process_pcm_frames__group(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) { /* For groups, the input data has already been read and we just need to apply the effect. */ ma_engine_node_process_pcm_frames__general((ma_engine_node*)pNode, ppFramesIn, pFrameCountIn, ppFramesOut, pFrameCountOut); @@ -8661,10 +8831,22 @@ void ma_engine_node_process_pcm_frames__group(ma_node* pNode, const float** ppFr ma_engine_node_update_pitch_if_required((ma_engine_node*)pNode); } +static ma_uint32 ma_engine_node_get_required_input_frame_count__group(ma_node* pNode, ma_uint32 outputFrameCount) +{ + ma_uint64 result = ma_engine_node_get_required_input_frame_count(pNode, outputFrameCount); + if (result > 0xFFFFFFFF) { + result = 0xFFFFFFFF; /* Will never happen because miniaudio will only ever process in relatively small chunks. */ + } + + return (ma_uint32)result; +} + static ma_node_vtable g_ma_engine_node_vtable__sound = { ma_engine_node_process_pcm_frames__sound, + NULL, /* onGetRequiredInputFrameCount */ + NULL, /* onGetMetadata */ 0, /* Sounds are data source nodes which means they have zero inputs (their input is drawn from the data source itself). */ 1, /* Sounds have one output bus. */ 0 /* Default flags. */ @@ -8673,9 +8855,11 @@ static ma_node_vtable g_ma_engine_node_vtable__sound = static ma_node_vtable g_ma_engine_node_vtable__group = { ma_engine_node_process_pcm_frames__group, + ma_engine_node_get_required_input_frame_count__group, + NULL, /* onGetMetadata */ 1, /* Groups have one input bus. */ 1, /* Groups have one output bus. */ - 0 /* Default flags. */ + MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES /* The engine node does resampling so should let miniaudio know about it. */ }; MA_API ma_result ma_engine_node_init(const ma_engine_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_engine_node* pEngineNode)