265 lines
11 KiB
C
265 lines
11 KiB
C
#include "../graph/graph_internal.h"
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#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;
|
|
uint64_t position;
|
|
xtc_native **native;
|
|
};
|
|
|
|
void xtc_plan_destroy(xtc_plan *plan)
|
|
{
|
|
if (plan == NULL)
|
|
return;
|
|
if (plan->native) {
|
|
size_t i;
|
|
for (i = 0; i < plan->node_count * 2; ++i)
|
|
xtc_native_destroy(plan->native[i]);
|
|
}
|
|
free(plan->native);
|
|
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->native = calloc(storage_nodes * 2U, sizeof(*plan->native));
|
|
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 * 2U, sizeof(*plan->buffers));
|
|
plan->peaks = calloc(storage_nodes, sizeof(*plan->peaks));
|
|
plan->phases = calloc(storage_nodes, sizeof(*plan->phases));
|
|
if (plan->native == NULL || 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);
|
|
}
|
|
for (node = 0; node < plan->node_count; ++node) {
|
|
if (plan->nodes[node].type == XTC_NODE_NATIVE) {
|
|
size_t side;
|
|
for (side = 0; side < 2; ++side) {
|
|
result = xtc_native_create(&plan->nodes[node].native, sample_rate, max_frames, plan->nodes[node].value,
|
|
&plan->native[side * plan->node_count + node]);
|
|
if (result != XTC_OK) {
|
|
xtc_plan_destroy(plan);
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < plan->node_count * 2; ++i)
|
|
xtc_native_reset(plan->native[i]);
|
|
}
|
|
plan->position = 0;
|
|
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_stereo(xtc_plan *plan, const float *input, const float *timeline,
|
|
const float *timeline_right, const float *instrument, const float *instrument_right,
|
|
float *output, float *right, size_t frame_count)
|
|
{
|
|
size_t index, frame, edge, channel;
|
|
if (plan == NULL || output == NULL || (right && right == output))
|
|
return XTC_ERR_INVALID_ARGUMENT;
|
|
if (frame_count > plan->max_frames || plan->position > UINT64_MAX - frame_count)
|
|
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 (channel = 0; channel < (right ? 2U : 1U); ++channel) {
|
|
const float *audio_source = channel ? timeline_right : timeline;
|
|
const float *midi_source = channel ? instrument_right : instrument;
|
|
float *destination = channel ? right : output;
|
|
for (index = 0U; index < plan->node_count; ++index) {
|
|
size_t node = plan->order[index];
|
|
xtc_node spec = plan->nodes[node];
|
|
float *buffer = plan->buffers + (channel * plan->node_count + 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_INSTRUMENT) {
|
|
sample = midi_source ? midi_source[frame] : 0.0f;
|
|
} else if (spec.type == XTC_NODE_AUDIO) {
|
|
sample = audio_source ? audio_source[frame] : 0.0f;
|
|
} else 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[(channel * plan->node_count + 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);
|
|
}
|
|
if (spec.type == XTC_NODE_NATIVE) {
|
|
xtc_native_block block = {spec.native.kind == XTC_NATIVE_EFFECT ? buffer : NULL,
|
|
buffer,
|
|
frame_count,
|
|
plan->position,
|
|
NULL,
|
|
0};
|
|
xtc_result result = xtc_native_process(plan->native[channel * plan->node_count + node], &block);
|
|
if (result != XTC_OK) {
|
|
memset(output, 0, frame_count * sizeof(*output));
|
|
if (right)
|
|
memset(right, 0, frame_count * sizeof(*right));
|
|
return result;
|
|
}
|
|
}
|
|
if (!right || channel == 1)
|
|
plan->phases[node] = phase;
|
|
if (spec.type == XTC_NODE_METER)
|
|
plan->peaks[node] = channel && plan->peaks[node] > peak ? 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[(channel * plan->node_count + index) * plan->max_frames + frame];
|
|
destination[frame] = isfinite(sample) ? sample : 0.0f;
|
|
}
|
|
}
|
|
plan->position += frame_count;
|
|
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;
|
|
}
|
|
|
|
xtc_result xtc_plan_process(xtc_plan *plan, const float *input, float *output, size_t frames)
|
|
{
|
|
return xtc_plan_process_audio(plan, input, NULL, output, frames);
|
|
}
|
|
|
|
xtc_result xtc_plan_process_audio(xtc_plan *plan, const float *input, const float *timeline, float *output,
|
|
size_t frames)
|
|
{
|
|
return xtc_plan_process_sources(plan, input, timeline, NULL, output, frames);
|
|
}
|
|
|
|
xtc_result xtc_plan_process_sources(xtc_plan *p, const float *in, const float *audio, const float *midi, float *out,
|
|
size_t frames)
|
|
{
|
|
return xtc_plan_process_stereo(p, in, audio, NULL, midi, NULL, out, NULL, frames);
|
|
}
|