xtc/src/graph/graph.c

656 lines
25 KiB
C
Raw Normal View History

2026-10-26 23:20:00 +01:00
#include "graph_internal.h"
#include <math.h>
2026-10-26 22:51:25 +01:00
#include <stdlib.h>
#include <string.h>
2026-10-26 22:51:25 +01:00
/**
* @brief Allocates an empty graph with the requested node capacity.
*
* @param max_nodes Maximum number of graph nodes.
* @param out_graph Receives the newly allocated graph; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
2026-10-26 23:20:00 +01:00
xtc_result xtc_graph_create(size_t max_nodes, xtc_graph **out_graph)
2026-10-26 22:51:25 +01:00
{
2026-10-26 23:20:00 +01:00
xtc_graph *graph;
2026-10-27 01:37:21 +01:00
if (out_graph == NULL)
return XTC_ERR_INVALID_ARGUMENT;
2026-10-26 22:51:25 +01:00
*out_graph = NULL;
2026-10-27 01:37:21 +01:00
if (max_nodes == 0U || max_nodes > XTC_MAX_NODES)
return XTC_ERR_INVALID_ARGUMENT;
2026-10-26 23:20:00 +01:00
graph = calloc(1U, sizeof(*graph));
2026-10-27 01:37:21 +01:00
if (graph == NULL)
return XTC_ERR_OUT_OF_MEMORY;
2026-10-26 23:20:00 +01:00
graph->nodes = calloc(max_nodes, sizeof(*graph->nodes));
graph->edges = calloc(max_nodes * max_nodes, sizeof(*graph->edges));
2026-10-26 22:51:25 +01:00
if (graph->nodes == NULL || graph->edges == NULL) {
2026-10-26 23:20:00 +01:00
xtc_graph_destroy(graph);
return XTC_ERR_OUT_OF_MEMORY;
2026-10-26 22:51:25 +01:00
}
graph->max_nodes = max_nodes;
*out_graph = graph;
2026-10-26 23:20:00 +01:00
return XTC_OK;
2026-10-26 22:51:25 +01:00
}
/**
* @brief Releases graph storage; accepts NULL.
*
* @param graph Graph model; borrowed for this call.
*/
2026-10-26 23:20:00 +01:00
void xtc_graph_destroy(xtc_graph *graph)
2026-10-26 22:51:25 +01:00
{
if (graph != NULL) {
free(graph->nodes);
free(graph->edges);
free(graph);
}
}
/**
* @brief Finds a graph's dense node index by stable identifier.
*
* @param graph Graph model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @return Dense node index, or SIZE_MAX if absent.
*/
static size_t node_index(const xtc_graph *graph, size_t id)
{
size_t i;
2026-10-27 01:37:21 +01:00
for (i = 0U; i < graph->node_count; ++i)
if (graph->nodes[i].id == id)
return i;
return SIZE_MAX;
}
/**
* @brief Appends a node after checking type, value, capacity, and identifier availability.
*
* @param graph Graph model; borrowed for this call.
* @param type Node type to create.
* @param value Value to encode, write, or apply.
* @param out_node_id Receives the node id.
* @return XTC_OK on success, or an error code describing the failure.
*/
2026-10-27 01:37:21 +01:00
static xtc_result add_node(xtc_graph *graph, xtc_node_type type, float value, size_t *out_node_id)
2026-10-26 22:51:25 +01:00
{
2026-10-26 23:20:00 +01:00
if (graph == NULL || out_node_id == NULL || !isfinite(value))
return XTC_ERR_INVALID_ARGUMENT;
2026-10-27 01:37:21 +01:00
if (graph->node_count == graph->max_nodes || graph->next_id == SIZE_MAX)
return XTC_ERR_CAPACITY;
2026-10-26 23:20:00 +01:00
graph->nodes[graph->node_count].type = type;
graph->nodes[graph->node_count].value = value;
graph->nodes[graph->node_count++].id = graph->next_id;
*out_node_id = graph->next_id++;
2026-10-26 23:20:00 +01:00
return XTC_OK;
}
2026-10-27 01:37:21 +01:00
/**
* @brief Adds a gain node with a finite linear gain value.
*
* @param graph Graph model; borrowed for this call.
* @param gain Linear gain, 0 to 4 for track controls.
* @param out_node_id Receives the node id.
* @return XTC_OK on success, or an error code describing the failure.
*/
2026-10-26 23:20:00 +01:00
xtc_result xtc_graph_add_gain(xtc_graph *graph, float gain, size_t *out_node_id)
2026-10-27 01:37:21 +01:00
{
return add_node(graph, XTC_NODE_GAIN, gain, out_node_id);
}
/**
* @brief Adds a sine oscillator node with a positive frequency in hertz.
*
* @param graph Graph model; borrowed for this call.
* @param frequency Oscillator frequency in hertz.
* @param out_node_id Receives the node id.
* @return XTC_OK on success, or an error code describing the failure.
*/
2026-10-26 23:20:00 +01:00
xtc_result xtc_graph_add_oscillator(xtc_graph *graph, float frequency, size_t *out_node_id)
{
2026-10-27 01:37:21 +01:00
if (frequency < 0.0f)
return XTC_ERR_INVALID_ARGUMENT;
2026-10-26 23:20:00 +01:00
return add_node(graph, XTC_NODE_OSCILLATOR, frequency, out_node_id);
}
2026-10-26 22:51:25 +01:00
/**
* @brief Adds a pass-through node that records the last block's absolute peak.
*
* @param graph Graph model; borrowed for this call.
* @param out_node_id Receives the node id.
* @return XTC_OK on success, or an error code describing the failure.
*/
2026-10-27 01:37:21 +01:00
xtc_result xtc_graph_add_meter(xtc_graph *graph, size_t *out_node_id)
{
return add_node(graph, XTC_NODE_METER, 0.0f, out_node_id);
}
/**
* @brief Adds a source node for the separately rendered MIDI instrument buffer.
*
* @param graph Graph model; borrowed for this call.
* @param out_node_id Receives the node id.
* @return XTC_OK on success, or an error code describing the failure.
*/
2026-10-27 01:37:21 +01:00
xtc_result xtc_graph_add_instrument(xtc_graph *graph, size_t *out_node_id)
{
return add_node(graph, XTC_NODE_INSTRUMENT, 0.0f, out_node_id);
}
/**
* @brief Adds a source node for the separately rendered timeline audio buffer.
*
* @param graph Graph model; borrowed for this call.
* @param out_node_id Receives the node id.
* @return XTC_OK on success, or an error code describing the failure.
*/
2026-10-27 01:37:21 +01:00
xtc_result xtc_graph_add_audio(xtc_graph *graph, size_t *out_node_id)
{
return add_node(graph, XTC_NODE_AUDIO, 0.0f, out_node_id);
}
/**
* @brief Adds an explicit output node included in the final mix.
*
* @param graph Graph model; borrowed for this call.
* @param out_node_id Receives the node id.
* @return XTC_OK on success, or an error code describing the failure.
*/
2026-10-27 01:37:21 +01:00
xtc_result xtc_graph_add_output(xtc_graph *graph, size_t *out_node_id)
{
return add_node(graph, XTC_NODE_OUTPUT, 0.0f, out_node_id);
}
/**
* @brief Copies the direction, signal type, and channel count of a node port.
*
* @param graph Graph model; borrowed for this call.
* @param node_id Stable node identifier.
* @param port_id Node port identifier.
* @param out_port Receives the port.
* @return XTC_OK on success, or an error code describing the failure.
*/
2026-10-27 01:37:21 +01:00
xtc_result xtc_graph_get_port(const xtc_graph *graph, size_t node_id, size_t port_id, xtc_port_info *out_port)
2026-10-26 23:20:00 +01:00
{
xtc_node_type type;
2026-10-27 01:37:21 +01:00
if (graph == NULL || out_port == NULL)
return XTC_ERR_INVALID_ARGUMENT;
node_id = node_index(graph, node_id);
2026-10-27 01:37:21 +01:00
if (node_id == SIZE_MAX)
return XTC_ERR_NOT_FOUND;
2026-10-26 23:20:00 +01:00
type = graph->nodes[node_id].type;
2026-10-27 01:37:21 +01:00
if (port_id > 1U ||
(port_id == 0U && (type == XTC_NODE_OSCILLATOR || type == XTC_NODE_AUDIO || type == XTC_NODE_INSTRUMENT ||
(type == XTC_NODE_PLUGIN && !graph->nodes[node_id].plugin.inputs) ||
2026-10-27 01:37:21 +01:00
(type == XTC_NODE_NATIVE && graph->nodes[node_id].native.kind != XTC_NATIVE_EFFECT))) ||
(port_id == 1U && type == XTC_NODE_OUTPUT))
return XTC_ERR_NOT_FOUND;
2026-10-26 23:20:00 +01:00
out_port->direction = port_id == 0U ? XTC_PORT_INPUT : XTC_PORT_OUTPUT;
out_port->signal_type = XTC_SIGNAL_AUDIO;
out_port->channel_count = 1U;
return XTC_OK;
2026-10-26 22:51:25 +01:00
}
/**
* @brief Validates the graph and writes dense node indexes in topological order.
*
* @param graph Graph model; borrowed for this call.
* @param order Receives dense node indexes in topological order; must hold the graph node count.
* @return XTC_OK on success, or an error code describing the failure.
*/
2026-10-26 23:20:00 +01:00
xtc_result xtc_graph_order(const xtc_graph *graph, size_t *order)
2026-10-26 22:51:25 +01:00
{
2026-10-26 23:20:00 +01:00
size_t indegree[XTC_MAX_NODES] = {0U};
size_t head = 0U, tail = 0U, i;
2026-10-27 01:37:21 +01:00
if (graph == NULL || order == NULL)
return XTC_ERR_INVALID_ARGUMENT;
2026-10-26 23:20:00 +01:00
for (i = 0U; i < graph->edge_count; ++i)
++indegree[graph->edges[i].destination];
for (i = 0U; i < graph->node_count; ++i)
2026-10-27 01:37:21 +01:00
if (indegree[i] == 0U)
order[tail++] = i;
2026-10-26 22:51:25 +01:00
while (head < tail) {
2026-10-26 23:20:00 +01:00
size_t node = order[head++];
for (i = 0U; i < graph->edge_count; ++i) {
2026-10-26 22:51:25 +01:00
if (graph->edges[i].source == node) {
size_t dest = graph->edges[i].destination;
2026-10-27 01:37:21 +01:00
if (--indegree[dest] == 0U)
order[tail++] = dest;
2026-10-26 22:51:25 +01:00
}
}
}
2026-10-26 23:20:00 +01:00
return tail == graph->node_count ? XTC_OK : XTC_ERR_GRAPH_CYCLE;
2026-10-26 22:51:25 +01:00
}
/**
* @brief Checks graph validity by attempting a topological ordering.
*
* @param graph Graph model; borrowed for this call.
* @return XTC_OK on success, or an error code describing the failure.
*/
2026-10-26 23:20:00 +01:00
xtc_result xtc_graph_validate(const xtc_graph *graph)
2026-10-26 22:51:25 +01:00
{
2026-10-26 23:20:00 +01:00
size_t order[XTC_MAX_NODES];
return xtc_graph_order(graph, order);
2026-10-26 22:51:25 +01:00
}
2026-10-26 23:20:00 +01:00
/**
* @brief Adds a validated audio connection while preserving an acyclic graph.
*
* @param graph Graph model; borrowed for this call.
* @param source_node Stable source node identifier.
* @param source_port Source output port identifier.
* @param destination_node Stable destination node identifier.
* @param destination_port Destination input port identifier.
* @return XTC_OK on success, or an error code describing the failure.
*/
2026-10-27 01:37:21 +01:00
xtc_result xtc_graph_connect_ports(xtc_graph *graph, size_t source_node, size_t source_port, size_t destination_node,
size_t destination_port)
2026-10-26 23:20:00 +01:00
{
xtc_port_info source, dest;
size_t i;
xtc_result result = xtc_graph_get_port(graph, source_node, source_port, &source);
2026-10-27 01:37:21 +01:00
if (result != XTC_OK)
return result;
2026-10-26 23:20:00 +01:00
result = xtc_graph_get_port(graph, destination_node, destination_port, &dest);
2026-10-27 01:37:21 +01:00
if (result != XTC_OK)
return result;
2026-10-26 23:20:00 +01:00
if (source.direction != XTC_PORT_OUTPUT || dest.direction != XTC_PORT_INPUT ||
source.signal_type != dest.signal_type || source.channel_count != dest.channel_count)
return XTC_ERR_INVALID_ARGUMENT;
source_node = node_index(graph, source_node);
destination_node = node_index(graph, destination_node);
2026-10-26 23:20:00 +01:00
for (i = 0U; i < graph->edge_count; ++i)
if (graph->edges[i].source == source_node && graph->edges[i].destination == destination_node)
return XTC_ERR_INVALID_STATE;
2026-10-27 01:37:21 +01:00
if (graph->edge_count == graph->max_nodes * graph->max_nodes)
return XTC_ERR_CAPACITY;
2026-10-26 23:20:00 +01:00
graph->edges[graph->edge_count].source = source_node;
graph->edges[graph->edge_count++].destination = destination_node;
result = xtc_graph_validate(graph);
2026-10-27 01:37:21 +01:00
if (result != XTC_OK)
--graph->edge_count;
2026-10-26 23:20:00 +01:00
return result;
}
2026-10-27 01:37:21 +01:00
/**
* @brief Connects a source's output port 1 to a destination's input port 0.
*
* @param graph Graph model; borrowed for this call.
* @param source_node Stable source node identifier.
* @param destination_node Stable destination node identifier.
* @return XTC_OK on success, or an error code describing the failure.
*/
2026-10-26 23:20:00 +01:00
xtc_result xtc_graph_connect(xtc_graph *graph, size_t source_node, size_t destination_node)
2026-10-27 01:37:21 +01:00
{
return xtc_graph_connect_ports(graph, source_node, 1U, destination_node, 0U);
}
/**
* @brief Copies the graph's node and connection counts.
*
* @param graph Graph model; borrowed for this call.
* @param out_nodes Receives the nodes.
* @param out_connections Receives the connections.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_get_counts(const xtc_graph *graph, size_t *out_nodes, size_t *out_connections)
{
2026-10-27 01:37:21 +01:00
if (graph == NULL || out_nodes == NULL || out_connections == NULL)
return XTC_ERR_INVALID_ARGUMENT;
*out_nodes = graph->node_count;
*out_connections = graph->edge_count;
return XTC_OK;
}
2026-10-27 01:37:21 +01:00
/**
* @brief Copies node metadata selected by dense list index.
*
* @param graph Graph model; borrowed for this call.
* @param index Zero-based index in the dense list.
* @param out_node Receives the node.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_get_node(const xtc_graph *graph, size_t index, xtc_node_info *out_node)
{
2026-10-27 01:37:21 +01:00
if (graph == NULL || out_node == NULL)
return XTC_ERR_INVALID_ARGUMENT;
if (index >= graph->node_count)
return XTC_ERR_NOT_FOUND;
out_node->id = graph->nodes[index].id;
out_node->type = graph->nodes[index].type;
out_node->value = graph->nodes[index].value;
return XTC_OK;
}
2026-10-27 01:37:21 +01:00
/**
* @brief Copies connection metadata selected by dense list index.
*
* @param graph Graph model; borrowed for this call.
* @param index Zero-based index in the dense list.
* @param out_connection Receives the connection.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_get_connection(const xtc_graph *graph, size_t index, xtc_connection_info *out_connection)
{
2026-10-27 01:37:21 +01:00
if (graph == NULL || out_connection == NULL)
return XTC_ERR_INVALID_ARGUMENT;
if (index >= graph->edge_count)
return XTC_ERR_NOT_FOUND;
out_connection->source_node = graph->nodes[graph->edges[index].source].id;
out_connection->destination_node = graph->nodes[graph->edges[index].destination].id;
out_connection->source_port = 1U;
out_connection->destination_port = 0U;
return XTC_OK;
}
2026-10-27 01:37:21 +01:00
/**
* @brief Sets whether rendering requires explicit output nodes rather than terminal-node fallback.
*
* @param graph Graph model; borrowed for this call.
* @param required One to require explicit output nodes, zero to allow terminal-node fallback.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_require_output_nodes(xtc_graph *graph, int required)
{
2026-10-27 01:37:21 +01:00
if (graph == NULL || (required != 0 && required != 1))
return XTC_ERR_INVALID_ARGUMENT;
graph->require_output_nodes = required;
return XTC_OK;
}
2026-10-27 01:37:21 +01:00
/**
* @brief Removes a node and all connections touching it.
*
* @param graph Graph model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @return XTC_OK on success, or an error code describing the failure.
*/
static xtc_result remove_node(xtc_graph *graph, size_t id)
{
size_t index = node_index(graph, id), i, count = 0U;
2026-10-27 01:37:21 +01:00
if (index == SIZE_MAX)
return XTC_ERR_NOT_FOUND;
for (i = 0U; i < graph->edge_count; ++i) {
xtc_edge edge = graph->edges[i];
2026-10-27 01:37:21 +01:00
if (edge.source == index || edge.destination == index)
continue;
if (edge.source > index)
--edge.source;
if (edge.destination > index)
--edge.destination;
graph->edges[count++] = edge;
}
graph->edge_count = count;
2026-10-27 01:37:21 +01:00
memmove(graph->nodes + index, graph->nodes + index + 1U, (graph->node_count - index - 1U) * sizeof(*graph->nodes));
--graph->node_count;
return XTC_OK;
}
2026-10-27 01:37:21 +01:00
/**
* @brief Removes the connection matching all source and destination port fields.
*
* @param graph Graph model; borrowed for this call.
* @param connection Connection identifying both node and port pairs.
* @return XTC_OK on success, or an error code describing the failure.
*/
static xtc_result disconnect_ports(xtc_graph *graph, const xtc_connection_info *connection)
{
size_t i;
xtc_port_info port;
xtc_result result = xtc_graph_get_port(graph, connection->source_node, connection->source_port, &port);
2026-10-27 01:37:21 +01:00
if (result != XTC_OK)
return result;
if (port.direction != XTC_PORT_OUTPUT)
return XTC_ERR_INVALID_ARGUMENT;
result = xtc_graph_get_port(graph, connection->destination_node, connection->destination_port, &port);
2026-10-27 01:37:21 +01:00
if (result != XTC_OK)
return result;
if (port.direction != XTC_PORT_INPUT)
return XTC_ERR_INVALID_ARGUMENT;
for (i = 0U; i < graph->edge_count; ++i) {
if (graph->edges[i].source == node_index(graph, connection->source_node) &&
graph->edges[i].destination == node_index(graph, connection->destination_node)) {
2026-10-27 01:37:21 +01:00
memmove(graph->edges + i, graph->edges + i + 1U, (graph->edge_count - i - 1U) * sizeof(*graph->edges));
--graph->edge_count;
return XTC_OK;
}
}
return XTC_ERR_NOT_FOUND;
}
2026-10-27 01:37:21 +01:00
/* Independent control-thread model copy; no plugin code is loaded. */
xtc_result xtc_graph_clone(const xtc_graph *source, xtc_graph **out)
{
xtc_graph *graph;
xtc_result result;
if (!out) return XTC_ERR_INVALID_ARGUMENT;
*out = NULL;
if (!source) return XTC_ERR_INVALID_ARGUMENT;
result = xtc_graph_create(source->max_nodes, &graph);
if (result != XTC_OK)
return result;
graph->node_count = source->node_count;
graph->edge_count = source->edge_count;
graph->next_id = source->next_id;
graph->require_output_nodes = source->require_output_nodes;
memcpy(graph->nodes, source->nodes, source->node_count * sizeof(*graph->nodes));
memcpy(graph->edges, source->edges, source->edge_count * sizeof(*graph->edges));
*out = graph;
return XTC_OK;
}
/**
* @brief Applies an edit to a new graph copy, leaving the source unchanged.
*
* @param source Graph model; borrowed for this call.
* @param edit Graph edit command.
* @param out_graph Receives the newly allocated graph; caller owns it on success.
* @param out_added_id Receives the added id.
* @return XTC_OK on success, or an error code describing the failure.
*/
2026-10-27 01:37:21 +01:00
xtc_result xtc_graph_apply_edit(const xtc_graph *source, const xtc_graph_edit *edit, xtc_graph **out_graph,
size_t *out_added_id)
{
xtc_graph *graph;
xtc_result result;
size_t added_id = 0U;
2026-10-27 01:37:21 +01:00
if (out_graph == NULL)
return XTC_ERR_INVALID_ARGUMENT;
*out_graph = NULL;
2026-10-27 01:37:21 +01:00
if (source == NULL || edit == NULL)
return XTC_ERR_INVALID_ARGUMENT;
result = xtc_graph_clone(source,&graph);
if (result != XTC_OK) return result;
switch (edit->kind) {
case XTC_EDIT_ADD_NODE:
switch (edit->data.add.type) {
2026-10-27 01:37:21 +01:00
case XTC_NODE_GAIN:
result = xtc_graph_add_gain(graph, edit->data.add.value, &added_id);
break;
case XTC_NODE_OSCILLATOR:
result = xtc_graph_add_oscillator(graph, edit->data.add.value, &added_id);
break;
case XTC_NODE_METER:
result = xtc_graph_add_meter(graph, &added_id);
break;
case XTC_NODE_INSTRUMENT:
result = xtc_graph_add_instrument(graph, &added_id);
break;
case XTC_NODE_AUDIO:
result = xtc_graph_add_audio(graph, &added_id);
break;
case XTC_NODE_OUTPUT:
result = xtc_graph_add_output(graph, &added_id);
break;
default:
result = XTC_ERR_INVALID_ARGUMENT;
break;
}
break;
2026-10-27 01:37:21 +01:00
case XTC_EDIT_REMOVE_NODE:
result = remove_node(graph, edit->data.node_id);
break;
case XTC_EDIT_CONNECT:
2026-10-27 01:37:21 +01:00
result =
xtc_graph_connect_ports(graph, edit->data.connection.source_node, edit->data.connection.source_port,
edit->data.connection.destination_node, edit->data.connection.destination_port);
break;
case XTC_EDIT_DISCONNECT:
result = disconnect_ports(graph, &edit->data.connection);
break;
case XTC_EDIT_INSERT_GAIN: {
const xtc_connection_info *c = &edit->data.connection;
result = disconnect_ports(graph, c);
if (result == XTC_OK) result = xtc_graph_add_gain(graph, 1.0f, &added_id);
if (result == XTC_OK) result = xtc_graph_connect_ports(graph, c->source_node, c->source_port, added_id, 0);
if (result == XTC_OK) result = xtc_graph_connect_ports(graph, added_id, 1, c->destination_node, c->destination_port);
break;
}
2026-10-27 01:37:21 +01:00
default:
result = XTC_ERR_INVALID_ARGUMENT;
break;
}
2026-10-27 01:37:21 +01:00
if (result != XTC_OK) {
xtc_graph_destroy(graph);
return result;
}
if (out_added_id != NULL && (edit->kind == XTC_EDIT_ADD_NODE || edit->kind == XTC_EDIT_INSERT_GAIN))
2026-10-27 01:37:21 +01:00
*out_added_id = added_id;
*out_graph = graph;
return XTC_OK;
}
/**
* @brief Adds a validated native effect or source descriptor to the graph.
*
* @param graph Graph model; borrowed for this call.
* @param descriptor Native processor descriptor.
* @param value Value to encode, write, or apply.
* @param out Receives the operation result.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_add_native(xtc_graph *graph, const xtc_native_descriptor *descriptor, float value, size_t *out)
{
xtc_result result = xtc_native_validate(descriptor);
2026-10-27 01:37:21 +01:00
if (result != XTC_OK)
return result;
if (descriptor->kind == XTC_NATIVE_INSTRUMENT)
return XTC_ERR_UNSUPPORTED;
result = add_node(graph, XTC_NODE_NATIVE, value, out);
2026-10-27 01:37:21 +01:00
if (result == XTC_OK)
graph->nodes[graph->node_count - 1].native = *descriptor;
return result;
}
2026-10-27 01:37:21 +01:00
/**
* @brief Copies the native descriptor of a node selected by stable identifier.
*
* @param graph Graph model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @param out Receives the operation result.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_get_native(const xtc_graph *graph, size_t id, xtc_native_descriptor *out)
{
size_t i;
2026-10-27 01:37:21 +01:00
if (!graph || !out)
return XTC_ERR_INVALID_ARGUMENT;
i = node_index(graph, id);
2026-10-27 01:37:21 +01:00
if (i == SIZE_MAX)
return XTC_ERR_NOT_FOUND;
if (graph->nodes[i].type != XTC_NODE_NATIVE)
return XTC_ERR_INVALID_ARGUMENT;
*out = graph->nodes[i].native;
return XTC_OK;
}
xtc_result xtc_graph_add_plugin(xtc_graph *g, const xtc_plugin_spec *s, size_t *id)
{
xtc_result r = xtc_plugin_spec_validate(s);
if (r != XTC_OK) return r;
if (s->instrument) return XTC_ERR_UNSUPPORTED;
r = add_node(g,XTC_NODE_PLUGIN,0,id);
if (r == XTC_OK) g->nodes[g->node_count-1].plugin = *s;
return r;
}
xtc_result xtc_graph_get_plugin(const xtc_graph *g, size_t id, xtc_plugin_spec *out)
{
size_t i;
if (!g || !out) return XTC_ERR_INVALID_ARGUMENT;
i = node_index(g,id);
if (i == SIZE_MAX) return XTC_ERR_NOT_FOUND;
if (g->nodes[i].type != XTC_NODE_PLUGIN) return XTC_ERR_INVALID_ARGUMENT;
*out = g->nodes[i].plugin; return XTC_OK;
}
xtc_result xtc_graph_set_plugin(xtc_graph *g, size_t id, const xtc_plugin_spec *s)
{
size_t i;
xtc_result r = xtc_plugin_spec_validate(s);
if (!g || r != XTC_OK) return XTC_ERR_INVALID_ARGUMENT;
i = node_index(g,id);
if (i == SIZE_MAX) return XTC_ERR_NOT_FOUND;
if (g->nodes[i].type != XTC_NODE_PLUGIN) return XTC_ERR_INVALID_ARGUMENT;
if (s->instrument) return XTC_ERR_UNSUPPORTED;
/* Parameter edits cannot replace identity/layout or invalidate wiring. */
if (s->format != g->nodes[i].plugin.format || strcmp(s->identifier,g->nodes[i].plugin.identifier) ||
strcmp(s->path,g->nodes[i].plugin.path) || strcmp(s->label,g->nodes[i].plugin.label) ||
s->index != g->nodes[i].plugin.index || s->unique_id != g->nodes[i].plugin.unique_id ||
s->inputs != g->nodes[i].plugin.inputs || s->control_count != g->nodes[i].plugin.control_count ||
memcmp(s->ports,g->nodes[i].plugin.ports,s->control_count*sizeof(*s->ports))) return XTC_ERR_UNSUPPORTED;
g->nodes[i].plugin = *s; return XTC_OK;
}
xtc_result xtc_graph_replace_plugin(xtc_graph *g, size_t id, const xtc_plugin_spec *s)
{
size_t i;
if (!g || xtc_plugin_spec_validate(s) != XTC_OK) return XTC_ERR_INVALID_ARGUMENT;
i = node_index(g,id);
if (i == SIZE_MAX) return XTC_ERR_NOT_FOUND;
if (g->nodes[i].type != XTC_NODE_PLUGIN) return XTC_ERR_INVALID_ARGUMENT;
if (s->instrument) return XTC_ERR_UNSUPPORTED;
if (g->nodes[i].plugin.inputs != s->inputs) return XTC_ERR_UNSUPPORTED;
g->nodes[i].plugin = *s;
return XTC_OK;
}
xtc_result xtc_graph_analyze(const xtc_graph *graph, xtc_routing_info *items, size_t capacity, size_t *count)
{
xtc_routing_info info[XTC_MAX_NODES] = {{0}};
size_t order[XTC_MAX_NODES], i, j;
int explicit_outputs = 0;
xtc_result result;
if (!graph || !count || (!items && graph->node_count)) return XTC_ERR_INVALID_ARGUMENT;
if (capacity < graph->node_count) return XTC_ERR_CAPACITY;
result = xtc_graph_order(graph, order);
if (result != XTC_OK) return result;
for (i = 0; i < graph->node_count; ++i) {
info[i].id = graph->nodes[i].id;
if (graph->nodes[i].type == XTC_NODE_OUTPUT) explicit_outputs = 1;
}
for (i = 0; i < graph->edge_count; ++i) {
++info[graph->edges[i].source].outputs;
++info[graph->edges[i].destination].inputs;
}
for (i = 0; i < graph->node_count; ++i) {
xtc_port_info port;
info[i].external_input = !info[i].inputs && xtc_graph_get_port(graph, info[i].id, 0, &port) == XTC_OK;
info[i].reaches_output = graph->nodes[i].type == XTC_NODE_OUTPUT ||
(!explicit_outputs && !graph->require_output_nodes && !info[i].outputs);
}
for (i = 0; i < graph->node_count; ++i) {
size_t node = order[i];
for (j = 0; j < graph->edge_count; ++j) if (graph->edges[j].source == node) {
size_t destination = graph->edges[j].destination;
if (info[destination].level <= info[node].level) info[destination].level = info[node].level + 1;
}
}
for (i = graph->node_count; i > 0; --i) {
size_t node = order[i-1];
if (info[node].reaches_output)
for (j = 0; j < graph->edge_count; ++j) if (graph->edges[j].destination == node)
info[graph->edges[j].source].reaches_output = 1;
}
if (graph->node_count) memcpy(items, info, graph->node_count * sizeof(*items));
*count = graph->node_count;
return XTC_OK;
}