#include "../graph/graph_internal.h" #include #include #include #define XTC_TAU 6.28318530717958647692 struct xtc_plan { size_t node_count, max_frames; xtc_node *nodes; size_t *order, *offsets, *sources; unsigned char *render; float *buffers, *peaks; double *phases; double sample_rate; float input_gain; }; void xtc_plan_destroy(xtc_plan *plan) { if (plan == NULL) return; free(plan->nodes); free(plan->order); free(plan->offsets); free(plan->sources); free(plan->render); free(plan->buffers); free(plan->peaks); free(plan->phases); free(plan); } xtc_result xtc_graph_compile(const xtc_graph *graph, double sample_rate, size_t max_frames, xtc_plan **out_plan) { xtc_plan *plan; xtc_result result; size_t node, edge, storage_nodes, cursor = 0U; int has_output = 0; if (out_plan == NULL) return XTC_ERR_INVALID_ARGUMENT; *out_plan = NULL; if (graph == NULL || !isfinite(sample_rate) || sample_rate <= 0.0 || max_frames == 0U || max_frames > XTC_MAX_FRAMES) return XTC_ERR_INVALID_ARGUMENT; storage_nodes = graph->node_count == 0U ? 1U : graph->node_count; for (node = 0U; node < graph->node_count; ++node) { if (graph->nodes[node].type == XTC_NODE_OSCILLATOR && (double)graph->nodes[node].value > sample_rate * 0.5) return XTC_ERR_INVALID_ARGUMENT; if (graph->nodes[node].type == XTC_NODE_OUTPUT) has_output = 1; } /* Fixed graph/frame limits bound all allocation products below. */ plan = calloc(1U, sizeof(*plan)); if (plan == NULL) return XTC_ERR_OUT_OF_MEMORY; plan->node_count = graph->node_count; plan->max_frames = max_frames; plan->sample_rate = sample_rate; plan->input_gain = 1.0f; plan->nodes = calloc(storage_nodes, sizeof(*plan->nodes)); plan->order = calloc(storage_nodes, sizeof(*plan->order)); plan->offsets = calloc(plan->node_count + 1U, sizeof(*plan->offsets)); plan->sources = calloc(graph->edge_count + 1U, sizeof(*plan->sources)); plan->render = calloc(storage_nodes, sizeof(*plan->render)); plan->buffers = calloc(storage_nodes * max_frames, sizeof(*plan->buffers)); plan->peaks = calloc(storage_nodes, sizeof(*plan->peaks)); plan->phases = calloc(storage_nodes, sizeof(*plan->phases)); if (plan->nodes == NULL || plan->order == NULL || plan->offsets == NULL || plan->sources == NULL || plan->render == NULL || plan->buffers == NULL || plan->peaks == NULL || plan->phases == NULL) { xtc_plan_destroy(plan); return XTC_ERR_OUT_OF_MEMORY; } result = xtc_graph_order(graph, plan->order); if (result != XTC_OK) { xtc_plan_destroy(plan); return result; } memcpy(plan->nodes, graph->nodes, plan->node_count * sizeof(*plan->nodes)); for (node = 0U; node < plan->node_count; ++node) { int terminal = 1; plan->offsets[node] = cursor; for (edge = 0U; edge < graph->edge_count; ++edge) { if (graph->edges[edge].destination == node) plan->sources[cursor++] = graph->edges[edge].source; if (graph->edges[edge].source == node) terminal = 0; } plan->render[node] = (unsigned char)(has_output || graph->require_output_nodes ? plan->nodes[node].type == XTC_NODE_OUTPUT : terminal); } plan->offsets[plan->node_count] = cursor; *out_plan = plan; return XTC_OK; } xtc_result xtc_plan_reset(xtc_plan *plan) { if (plan == NULL) return XTC_ERR_INVALID_ARGUMENT; memset(plan->phases, 0, plan->node_count * sizeof(*plan->phases)); memset(plan->peaks, 0, plan->node_count * sizeof(*plan->peaks)); return XTC_OK; } xtc_result xtc_plan_meter_peak(const xtc_plan *plan, size_t node_id, float *out_peak) { if (plan == NULL || out_peak == NULL) return XTC_ERR_INVALID_ARGUMENT; size_t index; for (index = 0U; index < plan->node_count; ++index) if (plan->nodes[index].id == node_id) break; if (index == plan->node_count) return XTC_ERR_NOT_FOUND; node_id = index; if (plan->nodes[node_id].type != XTC_NODE_METER) return XTC_ERR_INVALID_ARGUMENT; *out_peak = plan->peaks[node_id]; return XTC_OK; } xtc_result xtc_plan_process(xtc_plan *plan, const float *input, float *output, size_t frame_count) { size_t index, frame, edge; if (plan == NULL || output == NULL) return XTC_ERR_INVALID_ARGUMENT; if (frame_count > plan->max_frames) return XTC_ERR_CAPACITY; if (frame_count == 0U) return XTC_OK; /* All external input reads finish before output is written, allowing aliasing. * Nonfinite input/arithmetic becomes silence per sample, never persistent DSP state. */ for (index = 0U; index < plan->node_count; ++index) { size_t node = plan->order[index]; xtc_node spec = plan->nodes[node]; float *buffer = plan->buffers + node * plan->max_frames; float peak = 0.0f; double phase = plan->phases[node]; for (frame = 0U; frame < frame_count; ++frame) { float sample = 0.0f; if (spec.type == XTC_NODE_OSCILLATOR) { sample = (float)sin(XTC_TAU * phase); phase += (double)spec.value / plan->sample_rate; if (phase >= 1.0) phase -= 1.0; } else { if (plan->offsets[node] == plan->offsets[node + 1U]) sample = input == NULL ? 0.0f : input[frame] * plan->input_gain; for (edge = plan->offsets[node]; edge < plan->offsets[node + 1U]; ++edge) sample += plan->buffers[plan->sources[edge] * plan->max_frames + frame]; if (spec.type == XTC_NODE_GAIN) sample *= spec.value; } if (!isfinite(sample)) sample = 0.0f; buffer[frame] = sample; if (fabsf(sample) > peak) peak = fabsf(sample); } plan->phases[node] = phase; if (spec.type == XTC_NODE_METER) plan->peaks[node] = peak; } for (frame = 0U; frame < frame_count; ++frame) { float sample = 0.0f; for (index = 0U; index < plan->node_count; ++index) if (plan->render[index]) sample += plan->buffers[index * plan->max_frames + frame]; output[frame] = isfinite(sample) ? sample : 0.0f; } return XTC_OK; } xtc_result xtc_graph_process_offline(xtc_graph *graph, const float *input, float *output, size_t frame_count, float input_gain) { xtc_plan *plan = NULL; xtc_result result; if (input == NULL || output == NULL || !isfinite(input_gain)) return XTC_ERR_INVALID_ARGUMENT; result = xtc_graph_compile(graph, 48000.0, frame_count == 0U ? 1U : frame_count, &plan); if (result != XTC_OK) return result; plan->input_gain = input_gain; result = xtc_plan_process(plan, input, output, frame_count); xtc_plan_destroy(plan); return result; }