229 lines
9.1 KiB
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, ©, 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;
|
|
}
|