232 lines
12 KiB
C
232 lines
12 KiB
C
#define _POSIX_C_SOURCE 200809L
|
|
#include "xtc/xtc_plugin.h"
|
|
#include "xtc/xtc_project.h"
|
|
#include "xtc/xtc_realtime.h"
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <dlfcn.h>
|
|
static int audio;
|
|
void *__real_malloc(size_t);
|
|
void *__real_calloc(size_t,size_t);
|
|
void *__real_realloc(void *,size_t);
|
|
void __real_free(void *);
|
|
void *__real_dlopen(const char *,int);
|
|
int __real_dlclose(void *);
|
|
void *__wrap_malloc(size_t n) { assert(!audio); return __real_malloc(n); }
|
|
void *__wrap_calloc(size_t n,size_t z) { assert(!audio); return __real_calloc(n,z); }
|
|
void *__wrap_realloc(void *p,size_t n) { assert(!audio); return __real_realloc(p,n); }
|
|
void __wrap_free(void *p) { assert(!audio); __real_free(p); }
|
|
void *__wrap_dlopen(const char *p,int flags) { assert(!audio); return __real_dlopen(p,flags); }
|
|
int __wrap_dlclose(void *p) { assert(!audio); return __real_dlclose(p); }
|
|
static void graph_roundtrip(const xtc_plugin_spec *spec)
|
|
{
|
|
xtc_graph *g = NULL; xtc_plan *p = NULL, *again = NULL;
|
|
xtc_project *project = NULL, *loaded = NULL;
|
|
xtc_plugin_spec copy;
|
|
char path[] = "/tmp/xtc-plugin-project-XXXXXX";
|
|
float input[31], out[31], expected[31]; size_t i, plugin, output;
|
|
int fd = mkstemp(path); assert(fd >= 0); close(fd);
|
|
assert(xtc_graph_create(8,&g) == XTC_OK);
|
|
assert(xtc_graph_add_plugin(g,spec,&plugin) == XTC_OK);
|
|
assert(xtc_graph_add_output(g,&output) == XTC_OK);
|
|
assert(xtc_graph_connect(g,plugin,output) == XTC_OK);
|
|
assert(xtc_graph_compile(g,48000,31,&p) == XTC_OK);
|
|
for (i = 0; i < 31; ++i) input[i] = (float)i / 100;
|
|
audio = 1;
|
|
assert(xtc_plan_process(p,input,expected,31) == XTC_OK);
|
|
audio = 0;
|
|
{ int nonzero = 0; for (i = 0; i < 31; ++i) if (expected[i] != 0) nonzero = 1; assert(nonzero); }
|
|
assert(xtc_project_create(&project) == XTC_OK);
|
|
assert(xtc_project_set_graph(project,g) == XTC_OK);
|
|
assert(xtc_project_save(project,path) == XTC_OK);
|
|
assert(xtc_project_create(&loaded) == XTC_OK);
|
|
assert(xtc_project_load(loaded,path) == XTC_OK);
|
|
assert(xtc_graph_get_plugin(xtc_project_graph(loaded),plugin,©) == XTC_OK);
|
|
assert(!strcmp(copy.path,spec->path) && !strcmp(copy.label,spec->label));
|
|
assert(xtc_graph_compile(xtc_project_graph(loaded),48000,31,&again) == XTC_OK);
|
|
audio = 1;
|
|
assert(xtc_plan_process(again,input,out,31) == XTC_OK);
|
|
audio = 0;
|
|
for (i = 0; i < 31; ++i) assert(out[i] == expected[i] && isfinite(out[i]));
|
|
xtc_plan_destroy(again); again = NULL;
|
|
if (spec->control_count) {
|
|
char malformed[] = "/tmp/xtc-plugin-malformed-XXXXXX", record[1536];
|
|
int descriptor = mkstemp(malformed);
|
|
FILE *source = fopen(path,"r"), *dest = fdopen(descriptor,"w");
|
|
assert(descriptor >= 0 && source && dest);
|
|
while (fgets(record,sizeof(record),source)) {
|
|
if (!strncmp(record,"CONTROL ",8)) fputs("CONTROL 64 3f800000\n",dest);
|
|
else fputs(record,dest);
|
|
}
|
|
assert(!ferror(source)); fclose(source); assert(fclose(dest) == 0);
|
|
assert(xtc_project_load(loaded,malformed) == XTC_ERR_FORMAT);
|
|
assert(xtc_graph_get_plugin(xtc_project_graph(loaded),plugin,©) == XTC_OK);
|
|
assert(!strcmp(copy.path,spec->path));
|
|
unlink(malformed);
|
|
}
|
|
/* Metadata-only loading/saving preserves absent modules. */
|
|
strcpy(copy.path,"/nonexistent/xtc-preserved-plugin.so");
|
|
xtc_graph_destroy(g); g = NULL;
|
|
assert(xtc_graph_create(8,&g) == XTC_OK);
|
|
assert(xtc_graph_add_plugin(g,©,&plugin) == XTC_OK);
|
|
assert(xtc_project_set_graph(project,g) == XTC_OK);
|
|
assert(xtc_project_save(project,path) == XTC_OK);
|
|
xtc_project_destroy(loaded); loaded = NULL;
|
|
assert(xtc_project_create(&loaded) == XTC_OK);
|
|
assert(xtc_project_load(loaded,path) == XTC_OK);
|
|
assert(xtc_graph_compile(xtc_project_graph(loaded),48000,31,&again) == XTC_ERR_NOT_FOUND && !again);
|
|
assert(xtc_project_save(loaded,path) == XTC_OK);
|
|
xtc_graph_destroy(g); xtc_project_destroy(project); xtc_project_destroy(loaded);
|
|
xtc_plan_destroy(p); unlink(path);
|
|
/* Repeated publication, audio adoption and control retirement. */
|
|
{
|
|
xtc_realtime *rt = NULL;
|
|
assert(xtc_graph_create(8,&g) == XTC_OK);
|
|
assert(xtc_graph_add_plugin(g,spec,&plugin) == XTC_OK);
|
|
assert(xtc_realtime_create(48000,31,&rt) == XTC_OK);
|
|
for (i = 0; i < 100; ++i) {
|
|
assert(xtc_realtime_submit(rt,g) == XTC_OK);
|
|
audio = 1;
|
|
assert(xtc_realtime_process(rt,input,out,31) == XTC_OK);
|
|
audio = 0;
|
|
xtc_realtime_collect(rt);
|
|
}
|
|
xtc_realtime_destroy(rt); xtc_graph_destroy(g);
|
|
}
|
|
}
|
|
int main(int argc, char **argv)
|
|
{
|
|
xtc_plugin_spec s, bad; xtc_plugin *p = NULL; float data[17]; size_t i;
|
|
assert(argc == 2 || argc == 3);
|
|
assert(xtc_plugin_probe(argv[1],argc == 3 && strcmp(argv[2],"fixture") ? (unsigned int)strtoul(argv[2],NULL,10) : 0,48000,&s) == XTC_OK);
|
|
printf("Testing %s (%u controls, %u inputs)\n",s.label,s.control_count,s.inputs);
|
|
graph_roundtrip(&s);
|
|
{
|
|
xtc_parameter_info info[XTC_PLUGIN_CONTROLS];
|
|
assert(xtc_plugin_parameters(&s,48000,info) == XTC_OK);
|
|
for (i = 0; i < s.control_count; ++i) assert(info[i].id == s.ports[i] && info[i].name[0]);
|
|
}
|
|
assert(xtc_plugin_create(&s,48000,17,&p) == XTC_OK);
|
|
for (i = 0; i < 17; ++i) data[i] = .5f;
|
|
audio = 1;
|
|
assert(xtc_plugin_process(p,data,data,17) == XTC_OK);
|
|
audio = 0;
|
|
for (i = 0; i < 17; ++i) assert(isfinite(data[i]));
|
|
xtc_plugin_destroy(p); p = NULL;
|
|
bad = s; ++bad.unique_id;
|
|
assert(xtc_plugin_create(&bad,48000,17,&p) == XTC_ERR_UNSUPPORTED && !p);
|
|
bad = s; if (bad.control_count) {
|
|
bad.values[0] = NAN; assert(xtc_plugin_create(&bad,48000,17,&p) == XTC_ERR_INVALID_ARGUMENT);
|
|
}
|
|
if (argc == 3 && !strcmp(argv[2],"fixture")) {
|
|
assert(s.control_count == 1 && s.values[0] == 1);
|
|
s.values[0] = 2;
|
|
assert(xtc_plugin_create(&s,48000,17,&p) == XTC_OK);
|
|
for (i = 0; i < 17; ++i) data[i] = .5f;
|
|
audio = 1;
|
|
assert(xtc_plugin_process(p,data,data,17) == XTC_OK);
|
|
audio = 0;
|
|
for (i = 0; i < 17; ++i) assert(data[i] == 1);
|
|
xtc_plugin_destroy(p); p = NULL;
|
|
graph_roundtrip(&s);
|
|
s.point_count = 2;
|
|
s.points[0] = (xtc_plugin_point){0,s.ports[0],0,.5f};
|
|
s.points[1] = (xtc_plugin_point){1,s.ports[0],0,2};
|
|
graph_roundtrip(&s);
|
|
{
|
|
xtc_graph *g = NULL; xtc_realtime *rt = NULL; size_t id;
|
|
float in[1] = {1}, out[1];
|
|
assert(xtc_graph_create(8,&g) == XTC_OK && xtc_graph_add_plugin(g,&s,&id) == XTC_OK);
|
|
assert(xtc_realtime_create(48000,31,&rt) == XTC_OK);
|
|
assert(xtc_realtime_position(rt,25) == XTC_OK && xtc_realtime_submit(rt,g) == XTC_OK);
|
|
audio = 1; assert(xtc_realtime_process(rt,in,out,1) == XTC_OK && out[0] == 2); audio = 0;
|
|
xtc_realtime_collect(rt);
|
|
assert(xtc_realtime_submit(rt,g) == XTC_OK);
|
|
audio = 1; assert(xtc_realtime_process(rt,in,out,1) == XTC_OK && out[0] == 2); audio = 0;
|
|
xtc_realtime_collect(rt);
|
|
xtc_realtime_destroy(rt); xtc_graph_destroy(g);
|
|
}
|
|
{
|
|
float whole[31], split[31], in[31]; xtc_plugin *other = NULL;
|
|
for (i = 0; i < 31; ++i) in[i] = 1;
|
|
assert(xtc_plugin_create(&s,48000,31,&p) == XTC_OK);
|
|
assert(xtc_plugin_create(&s,48000,31,&other) == XTC_OK);
|
|
audio = 1;
|
|
assert(xtc_plugin_process_at(p,in,whole,31,0) == XTC_OK);
|
|
assert(xtc_plugin_process_at(other,in,split,7,0) == XTC_OK);
|
|
assert(xtc_plugin_process_at(other,in+7,split+7,24,7) == XTC_OK);
|
|
audio = 0;
|
|
for (i = 0; i < 31; ++i) {
|
|
assert(whole[i] == split[i]);
|
|
assert(fabsf(whole[i]-(i < 25 ? .5f+1.5f*(float)i/25 : 2)) < .000001f);
|
|
}
|
|
assert(xtc_plugin_process_at(other,in,split,1,25) == XTC_OK && split[0] == 2);
|
|
xtc_plugin_destroy(p); xtc_plugin_destroy(other); p = NULL;
|
|
}
|
|
s.point_count = 0;
|
|
s.values[0] = 3; assert(xtc_plugin_create(&s,48000,17,&p) == XTC_ERR_UNSUPPORTED && !p);
|
|
assert(xtc_plugin_probe(argv[1],1,48000,&s) == XTC_OK && !s.inputs);
|
|
graph_roundtrip(&s);
|
|
assert(xtc_plugin_probe(argv[1],2,48000,&s) == XTC_ERR_UNSUPPORTED);
|
|
assert(xtc_plugin_probe(argv[1],3,48000,&s) == XTC_OK);
|
|
assert(xtc_plugin_create(&s,48000,17,&p) == XTC_ERR_INVALID_STATE && !p);
|
|
{
|
|
xtc_plugin_details details;
|
|
xtc_plugin_spec saved;
|
|
assert(xtc_plugin_probe(argv[1],0,48000,&s) == XTC_OK);
|
|
assert(xtc_plugin_describe(&s,48000,&details) == XTC_OK);
|
|
assert(!strcmp(details.name,"Test gain") && !strcmp(details.maker,"xtc tests") &&
|
|
!strcmp(details.copyright,"MIT") && !details.parameters[0].unit[0]);
|
|
assert(details.parameters[0].minimum == 0 && details.parameters[0].maximum == 2);
|
|
assert(details.flags[0] == (XTC_PLUGIN_BOUNDED_BELOW|XTC_PLUGIN_BOUNDED_ABOVE));
|
|
s.values[0] = .25f;
|
|
s.point_count = 1; s.points[0] = (xtc_plugin_point){0,s.ports[0],0,.5f};
|
|
saved = s;
|
|
assert(xtc_plugin_reset(&s,48000,63) == XTC_ERR_NOT_FOUND && !memcmp(&s,&saved,sizeof(s)));
|
|
assert(xtc_plugin_reset(&s,48000,s.ports[0]) == XTC_OK && s.values[0] == 1 && !s.point_count);
|
|
assert(xtc_plugin_probe(argv[1],4,48000,&s) == XTC_OK && s.values[0] == 2);
|
|
assert(xtc_plugin_describe(&s,48000,&details) == XTC_OK && (details.flags[0] & XTC_PLUGIN_INTEGER));
|
|
s.values[0] = .5f; assert(xtc_plugin_create(&s,48000,17,&p) == XTC_ERR_UNSUPPORTED && !p);
|
|
assert(xtc_plugin_reset(&s,48000,UINT32_MAX) == XTC_OK && s.values[0] == 2);
|
|
assert(xtc_plugin_probe(argv[1],5,48000,&s) == XTC_OK && s.values[0] == 1);
|
|
assert(xtc_plugin_describe(&s,48000,&details) == XTC_OK && (details.flags[0] & XTC_PLUGIN_TOGGLED));
|
|
s.values[0] = .5f; assert(xtc_plugin_create(&s,48000,17,&p) == XTC_ERR_UNSUPPORTED && !p);
|
|
assert(xtc_plugin_probe(argv[1],6,48000,&s) == XTC_OK);
|
|
assert(xtc_plugin_describe(&s,48000,&details) == XTC_OK &&
|
|
(details.flags[0] & XTC_PLUGIN_SAMPLE_RATE) && (details.flags[0] & XTC_PLUGIN_LOGARITHMIC));
|
|
assert(fabsf(details.parameters[0].minimum-48) < .001f && fabsf(details.parameters[0].maximum-4800) < .01f);
|
|
assert(xtc_plugin_probe(argv[1],7,48000,&s) == XTC_ERR_UNSUPPORTED);
|
|
assert(xtc_plugin_probe(argv[1],9,48000,&s) == XTC_ERR_NOT_FOUND);
|
|
// Noncommutative effects demonstrate that wires define DSP order,
|
|
// independent of node creation order; persisted graphs retain it.
|
|
{
|
|
xtc_graph *g = NULL; xtc_plan *plan = NULL;
|
|
xtc_plugin_spec gain, offset; size_t first, second, output;
|
|
float input[1] = {.5f}, out[1];
|
|
assert(xtc_plugin_probe(argv[1],0,48000,&gain) == XTC_OK);
|
|
assert(xtc_plugin_probe(argv[1],8,48000,&offset) == XTC_OK);
|
|
gain.values[0] = 2;
|
|
assert(xtc_graph_create(8,&g) == XTC_OK);
|
|
assert(xtc_graph_add_plugin(g,&gain,&first) == XTC_OK);
|
|
assert(xtc_graph_add_plugin(g,&offset,&second) == XTC_OK);
|
|
assert(xtc_graph_add_output(g,&output) == XTC_OK);
|
|
assert(xtc_graph_connect(g,first,second) == XTC_OK && xtc_graph_connect(g,second,output) == XTC_OK);
|
|
assert(xtc_graph_compile(g,48000,1,&plan) == XTC_OK);
|
|
audio = 1; assert(xtc_plan_process(plan,input,out,1) == XTC_OK && out[0] == 2); audio = 0;
|
|
xtc_plan_destroy(plan); plan = NULL;
|
|
// Swap identities at stable IDs: gain -> offset becomes offset -> gain.
|
|
assert(xtc_graph_replace_plugin(g,first,&offset) == XTC_OK);
|
|
assert(xtc_graph_replace_plugin(g,second,&gain) == XTC_OK);
|
|
assert(xtc_graph_compile(g,48000,1,&plan) == XTC_OK);
|
|
audio = 1; assert(xtc_plan_process(plan,input,out,1) == XTC_OK && out[0] == 3); audio = 0;
|
|
xtc_plan_destroy(plan); xtc_graph_destroy(g);
|
|
}
|
|
}
|
|
}
|
|
puts("Plugin lifecycle, identity, layouts, processing and persisted graph checks passed.");
|
|
return 0;
|
|
}
|