#define _POSIX_C_SOURCE 200809L #include "xtc/xtc_native.h" #include "xtc/xtc_realtime.h" #include #include #include #include #include #include typedef struct { float gain; int held; } state; static unsigned int prepared, destroyed, resets, fail_prepare; static int fail_process, nonfinite; static xtc_result prepare(double rate, size_t frames, float gain, void **out) { state *s = calloc(1, sizeof(*s)); assert(rate == 48000 && frames == 64); if (!s) return XTC_ERR_OUT_OF_MEMORY; s->gain = gain; *out = s; ++prepared; return prepared == fail_prepare ? XTC_ERR_IO : XTC_OK; } static void reset(void *context) { state *s = context; if (s) s->held = 0; ++resets; } static void destroy(void *context) { if (context) ++destroyed; free(context); } static xtc_result effect(void *context, const xtc_native_block *b) { const state *s = context; size_t i; for (i = 0; i < b->frames; ++i) b->output[i] = nonfinite ? NAN : (b->input ? b->input[i] : 0) * s->gain; return fail_process ? XTC_ERR_IO : XTC_OK; } static xtc_result instrument(void *context, const xtc_native_block *b) { state *s = context; size_t i, event = 0; for (i = 0; i < b->frames; ++i) { while (event < b->event_count && b->events[event].offset == i) { s->held = b->events[event].type == XTC_MIDI_NOTE_ON; ++event; } b->output[i] = s->held ? s->gain : 0; } return XTC_OK; } static xtc_native_descriptor descriptor(void) { xtc_native_descriptor d = {XTC_NATIVE_ABI_VERSION, sizeof(d), "org.xtc.test-gain", XTC_NATIVE_EFFECT, prepare, reset, effect, destroy}; return d; } int main(void) { xtc_native_descriptor d = descriptor(), bad; xtc_native *processor = NULL; float input[64], out[64], right[64]; size_t i, node, output, nodes, edges; xtc_native_block block = {input, out, 64, 0, NULL, 0}; xtc_graph *graph = NULL, *copy = NULL; xtc_plan *plan = NULL; xtc_realtime *engine = NULL; xtc_project *project = NULL; xtc_port_info port; xtc_graph_edit edit = {0}; char dir[] = "/tmp/xtc-native-XXXXXX", path[256], sentinel[8] = {0}; FILE *f; assert(mkdtemp(dir)); snprintf(path, sizeof(path), "%s/song.xtc", dir); for (i = 0; i < 64; ++i) input[i] = 0.25f; bad = d; bad.abi_version = 99; assert(xtc_native_create(&bad, 48000, 64, 1, &processor) == XTC_ERR_UNSUPPORTED && !processor); bad = d; --bad.struct_size; assert(xtc_native_validate(&bad) == XTC_ERR_UNSUPPORTED); bad = d; bad.process = NULL; assert(xtc_native_validate(&bad) == XTC_ERR_INVALID_ARGUMENT); bad = d; memset(bad.identifier, 'x', sizeof(bad.identifier)); assert(xtc_native_validate(&bad) == XTC_ERR_INVALID_ARGUMENT); assert(xtc_native_create(&d, NAN, 64, 1, &processor) == XTC_ERR_INVALID_ARGUMENT); fail_prepare = prepared + 1; assert(xtc_native_create(&d, 48000, 64, 1, &processor) == XTC_ERR_IO && !processor && destroyed == prepared); fail_prepare = 0; assert(xtc_native_create(&d, 48000, 64, 0.5f, &processor) == XTC_OK); d.process = instrument; // descriptor was copied, not borrowed. assert(xtc_native_process(processor, &block) == XTC_OK && out[0] == 0.125f); block.frames = 65; out[0] = 17; assert(xtc_native_process(processor, &block) == XTC_ERR_CAPACITY && out[0] == 17); block.frames = 64; fail_process = 1; assert(xtc_native_process(processor, &block) == XTC_ERR_IO && out[0] == 0 && out[63] == 0); fail_process = 0; nonfinite = 1; assert(xtc_native_process(processor, &block) == XTC_OK && out[1] == 0); nonfinite = 0; block.input = input; block.output = input; assert(xtc_native_process(processor, &block) == XTC_OK && input[0] == 0.125f); xtc_native_destroy(processor); processor = NULL; d = descriptor(); assert(xtc_graph_create(2, &graph) == XTC_OK && xtc_graph_add_native(graph, &d, 2, &node) == XTC_OK); assert(xtc_graph_get_native(graph, node, &bad) == XTC_OK && strcmp(bad.identifier, d.identifier) == 0); assert(xtc_graph_add_output(graph, &output) == XTC_OK && xtc_graph_connect(graph, node, output) == XTC_OK); assert(xtc_graph_get_port(graph, node, 0, &port) == XTC_OK); // Partial compile destroys every prepared state on the control thread. fail_prepare = prepared + 2; assert(xtc_graph_compile(graph, 48000, 64, &plan) == XTC_ERR_IO && !plan && destroyed == prepared); fail_prepare = 0; assert(xtc_graph_compile(graph, 48000, 64, &plan) == XTC_OK); assert(xtc_plan_process_stereo(plan, input, NULL, NULL, NULL, NULL, out, right, 64) == XTC_OK && out[0] == 0.25f && right[0] == 0.25f); fail_process = 1; assert(xtc_plan_process_stereo(plan, input, NULL, NULL, NULL, NULL, out, right, 64) == XTC_ERR_IO); assert(out[0] == 0 && right[0] == 0 && out[63] == 0 && right[63] == 0); fail_process = 0; assert(xtc_plan_reset(plan) == XTC_OK); assert(xtc_plan_process_stereo(plan, input, NULL, NULL, NULL, NULL, out, right, 64) == XTC_OK && out[0] == 0.25f); xtc_plan_destroy(plan); plan = NULL; assert(xtc_project_create(&project) == XTC_OK && xtc_project_set_graph(project, graph) == XTC_OK); f = fopen(path, "wb"); assert(f && fwrite("retain", 1, 6, f) == 6 && fclose(f) == 0); assert(xtc_project_save(project, path) == XTC_ERR_UNSUPPORTED); f = fopen(path, "rb"); assert(f && fread(sentinel, 1, 8, f) == 6 && fclose(f) == 0 && !strcmp(sentinel, "retain")); xtc_project_destroy(project); edit.kind = XTC_EDIT_REMOVE_NODE; edit.data.node_id = output; assert(xtc_graph_apply_edit(graph, &edit, ©, NULL) == XTC_OK); xtc_graph_destroy(graph); graph = copy; assert(xtc_graph_get_counts(graph, &nodes, &edges) == XTC_OK && nodes == 1 && edges == 0); assert(xtc_realtime_create(48000, 64, &engine) == XTC_OK && xtc_realtime_submit(engine, graph) == XTC_OK); i = destroyed; assert(xtc_realtime_process(engine, input, out, 64) == XTC_OK && out[0] == 0.25f && destroyed == i); assert(xtc_realtime_submit(engine, graph) == XTC_OK); assert(xtc_realtime_process(engine, input, out, 64) == XTC_OK && destroyed == i); xtc_realtime_collect(engine); assert(destroyed == i + 2); xtc_realtime_destroy(engine); xtc_graph_destroy(graph); d.kind = XTC_NATIVE_SOURCE; assert(xtc_graph_create(1, &graph) == XTC_OK && xtc_graph_add_native(graph, &d, 1, &node) == XTC_OK); assert(xtc_graph_get_port(graph, node, 0, &port) == XTC_ERR_NOT_FOUND); assert(xtc_graph_compile(graph, 48000, 64, &plan) == XTC_OK); assert(xtc_plan_process(plan, input, out, 64) == XTC_OK && out[0] == 0 && out[63] == 0); xtc_plan_destroy(plan); xtc_graph_destroy(graph); // An event instrument renders a source buffer that routes through the graph. d.kind = XTC_NATIVE_INSTRUMENT; d.process = instrument; assert(xtc_native_create(&d, 48000, 64, 0.2f, &processor) == XTC_OK); { xtc_midi_event events[] = {{4, XTC_MIDI_NOTE_ON, 0, 60, 100}, {8, XTC_MIDI_NOTE_OFF, 0, 60, 0}}; block = (xtc_native_block){NULL, out, 64, 0, events, 2}; assert(xtc_native_process(processor, &block) == XTC_OK && out[3] == 0 && out[4] == 0.2f && out[7] == 0.2f && out[8] == 0); events[1].offset = 2; out[0] = 17; i = resets; assert(xtc_native_process(processor, &block) == XTC_ERR_INVALID_ARGUMENT && out[0] == 17 && resets == i); block.event_count = 1; block.position = 64; assert(xtc_native_process(processor, &block) == XTC_OK && out[63] == 0.2f); block.event_count = 0; block.position = 512; assert(xtc_native_process(processor, &block) == XTC_OK && out[63] == 0); // seek resets held notes assert(xtc_graph_create(2, &graph) == XTC_OK); assert(xtc_graph_add_native(graph, &d, 1, &node) == XTC_ERR_UNSUPPORTED); assert(xtc_graph_add_instrument(graph, &node) == XTC_OK && xtc_graph_add_output(graph, &output) == XTC_OK && xtc_graph_connect(graph, node, output) == XTC_OK); block.position = 0; block.event_count = 2; events[1].offset = 8; assert(xtc_native_process(processor, &block) == XTC_OK); assert(xtc_graph_compile(graph, 48000, 64, &plan) == XTC_OK && xtc_plan_process_sources(plan, NULL, NULL, out, right, 64) == XTC_OK && memcmp(out, right, sizeof(out)) == 0); xtc_plan_destroy(plan); xtc_graph_destroy(graph); } xtc_native_destroy(processor); assert(prepared == destroyed); assert(unlink(path) == 0 && rmdir(dir) == 0); puts("Native ABI/lifecycle/failure cleanup/graph retirement/instrument events passed."); return 0; }