1
0
Fork 0
forked from hanez/xtc
xtc/tests/unit/test_native.c

229 lines
9.1 KiB
C

#define _POSIX_C_SOURCE 200809L
#include "xtc/xtc_native.h"
#include "xtc/xtc_realtime.h"
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
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, &copy, 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;
}