2026-10-27 17:34:46 +01:00
|
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
|
#include "xtc/xtc_plugin.h"
|
|
|
|
|
#include "xtc/xtc_project.h"
|
|
|
|
|
#include "xtc/xtc_realtime.h"
|
|
|
|
|
#include <assert.h>
|
|
|
|
|
#include <dlfcn.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <unistd.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 s) { assert(!audio); return __real_calloc(n,s); }
|
|
|
|
|
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 mode) { assert(!audio); return __real_dlopen(p,mode); }
|
|
|
|
|
int __wrap_dlclose(void *p) { assert(!audio); return __real_dlclose(p); }
|
|
|
|
|
static void render(xtc_realtime *rt)
|
|
|
|
|
{
|
|
|
|
|
float in[31],left[31],right[31]; size_t i;
|
|
|
|
|
for (i = 0; i < 31; ++i) in[i] = i % 2 ? -.5f : .25f;
|
|
|
|
|
audio = 1;
|
|
|
|
|
assert(xtc_realtime_process_stereo(rt,in,NULL,NULL,NULL,NULL,left,right,31) == XTC_OK);
|
|
|
|
|
audio = 0;
|
|
|
|
|
for (i = 0; i < 31; ++i) assert(left[i] == in[i]*.5f && right[i] == left[i]);
|
|
|
|
|
xtc_plugin_idle();
|
|
|
|
|
}
|
|
|
|
|
static void check(const char *path, xtc_plugin_format format)
|
|
|
|
|
{
|
|
|
|
|
xtc_plugin_spec spec, actual, bad;
|
|
|
|
|
xtc_plugin_details details;
|
|
|
|
|
xtc_plugin *plugin = NULL, *other = NULL;
|
|
|
|
|
xtc_project *p = NULL, *loaded = NULL;
|
|
|
|
|
xtc_graph *graph = NULL;
|
|
|
|
|
xtc_realtime *rt = NULL;
|
|
|
|
|
size_t node, output, i;
|
|
|
|
|
char document[] = "/tmp/xtc-modern-XXXXXX";
|
|
|
|
|
int fd = mkstemp(document); assert(fd >= 0); close(fd);
|
|
|
|
|
assert(xtc_plugin_probe(path,0,48000,&spec) == XTC_OK && spec.format == format);
|
|
|
|
|
assert(xtc_plugin_probe_format(format,path,0,48000,&actual) == XTC_OK);
|
|
|
|
|
assert(!memcmp(&spec,&actual,sizeof(spec)));
|
|
|
|
|
assert(xtc_plugin_describe(&spec,48000,&details) == XTC_OK && details.name[0]);
|
|
|
|
|
assert(xtc_plugin_create(&spec,48000,31,&plugin) == XTC_OK);
|
|
|
|
|
assert(xtc_plugin_create(&spec,48000,31,&other) == XTC_OK);
|
|
|
|
|
{ float data[31]; for (i = 0; i < 31; ++i) data[i] = 1;
|
|
|
|
|
audio = 1; assert(xtc_plugin_process(plugin,data,data,31) == XTC_OK); audio = 0;
|
|
|
|
|
for (i = 0; i < 31; ++i) assert(data[i] == .5f);
|
|
|
|
|
audio = 1; assert(xtc_plugin_process(other,NULL,data,31) == XTC_OK); audio = 0;
|
|
|
|
|
for (i = 0; i < 31; ++i) assert(data[i] == 0);
|
|
|
|
|
assert(xtc_plugin_process(plugin,data,data,32) == XTC_ERR_INVALID_ARGUMENT); }
|
|
|
|
|
xtc_plugin_idle(); xtc_plugin_destroy(plugin); xtc_plugin_destroy(other); plugin = NULL;
|
|
|
|
|
assert(xtc_project_create(&p) == XTC_OK && xtc_project_create(&loaded) == XTC_OK);
|
|
|
|
|
assert(xtc_graph_create(2,&graph) == XTC_OK);
|
|
|
|
|
assert(xtc_graph_add_plugin(graph,&spec,&node) == XTC_OK && xtc_graph_add_output(graph,&output) == XTC_OK);
|
|
|
|
|
assert(xtc_graph_connect(graph,node,output) == XTC_OK);
|
|
|
|
|
assert(xtc_project_set_graph(p,graph) == XTC_OK);
|
|
|
|
|
assert(xtc_project_save(p,document) == XTC_OK && xtc_project_load(loaded,document) == XTC_OK);
|
|
|
|
|
assert(xtc_graph_get_plugin(xtc_project_graph(loaded),node,&actual) == XTC_OK);
|
|
|
|
|
assert(!memcmp(&actual,&spec,sizeof(spec)));
|
|
|
|
|
assert(xtc_realtime_create(48000,31,&rt) == XTC_OK);
|
|
|
|
|
assert(xtc_realtime_submit(rt,xtc_project_graph(loaded)) == XTC_OK); render(rt);
|
|
|
|
|
for (i = 0; i < 50; ++i) {
|
|
|
|
|
xtc_realtime_collect(rt);
|
|
|
|
|
assert(xtc_realtime_submit(rt,graph) == XTC_OK); render(rt);
|
|
|
|
|
}
|
|
|
|
|
if (format == XTC_PLUGIN_VST3) {
|
|
|
|
|
bad = spec; strcpy(bad.identifier,"not-a-class-id");
|
|
|
|
|
assert(xtc_plugin_spec_validate(&bad) == XTC_ERR_INVALID_ARGUMENT);
|
|
|
|
|
}
|
|
|
|
|
bad = spec; bad.control_count = 1;
|
|
|
|
|
assert(xtc_plugin_spec_validate(&bad) == XTC_ERR_UNSUPPORTED);
|
|
|
|
|
bad = spec; ++bad.unique_id;
|
|
|
|
|
assert(xtc_graph_replace_plugin(graph,node,&bad) == XTC_OK);
|
|
|
|
|
xtc_realtime_collect(rt);
|
|
|
|
|
assert(xtc_realtime_submit(rt,graph) == XTC_ERR_UNSUPPORTED); render(rt);
|
|
|
|
|
bad = spec; strcpy(bad.path,"/missing/xtc-modern-plugin.so");
|
|
|
|
|
assert(xtc_graph_replace_plugin(graph,node,&bad) == XTC_OK && xtc_project_set_graph(p,graph) == XTC_OK);
|
|
|
|
|
assert(xtc_project_save(p,document) == XTC_OK && xtc_project_load(loaded,document) == XTC_OK);
|
|
|
|
|
assert(xtc_graph_get_plugin(xtc_project_graph(loaded),node,&actual) == XTC_OK && !strcmp(actual.path,bad.path));
|
|
|
|
|
assert(xtc_plugin_create(&actual,48000,31,&plugin) == XTC_ERR_NOT_FOUND && !plugin);
|
|
|
|
|
xtc_realtime_destroy(rt); xtc_graph_destroy(graph); xtc_project_destroy(p); xtc_project_destroy(loaded); unlink(document);
|
|
|
|
|
printf("%s identity, buffers, stereo graph, retirement, reload and missing dependency passed.\n",xtc_plugin_format_name(format));
|
|
|
|
|
}
|
2026-11-06 18:13:49 +01:00
|
|
|
static void context_check(const char *path)
|
|
|
|
|
{
|
|
|
|
|
xtc_plugin_spec spec; xtc_plugin *plugin = NULL;
|
|
|
|
|
void *handle, *symbol;
|
|
|
|
|
void (*read_context)(double *, int64_t *, double *, uint32_t *) = NULL;
|
|
|
|
|
float data[31] = {0}; double tempo, music; int64_t position; uint32_t flags;
|
|
|
|
|
assert(xtc_plugin_probe_format(XTC_PLUGIN_VST3,path,0,48000,&spec) == XTC_OK);
|
|
|
|
|
spec.tempo = 137000;
|
|
|
|
|
assert(xtc_plugin_create(&spec,48000,31,&plugin) == XTC_OK);
|
|
|
|
|
handle = dlopen(path,RTLD_NOW | RTLD_LOCAL); assert(handle);
|
|
|
|
|
symbol = dlsym(handle,"xtc_fixture_context"); assert(symbol && sizeof(symbol) == sizeof(read_context));
|
|
|
|
|
memcpy(&read_context,&symbol,sizeof(symbol));
|
|
|
|
|
audio = 1; assert(xtc_plugin_process_at(plugin,data,data,31,48000) == XTC_OK); audio = 0;
|
|
|
|
|
read_context(&tempo,&position,&music,&flags);
|
|
|
|
|
assert(tempo == 137 && position == 48000 && music == 137.0/60.0);
|
|
|
|
|
assert(flags == ((1U << 1) | (1U << 9) | (1U << 10)));
|
|
|
|
|
assert(xtc_plugin_process_at(plugin,data,data,31,0) == XTC_OK);
|
|
|
|
|
read_context(&tempo,&position,&music,&flags); assert(position == 0 && music == 0);
|
|
|
|
|
assert(xtc_plugin_process(plugin,data,data,31) == XTC_OK);
|
|
|
|
|
read_context(&tempo,&position,&music,&flags); assert(position == 31);
|
|
|
|
|
assert(xtc_plugin_process_at(plugin,data,data,31,UINT64_MAX) == XTC_ERR_INVALID_ARGUMENT);
|
|
|
|
|
read_context(&tempo,&position,&music,&flags); assert(position == 31);
|
|
|
|
|
xtc_plugin_destroy(plugin); assert(dlclose(handle) == 0);
|
|
|
|
|
puts("VST3 tempo, signed sample clock, seek and sequential context passed.");
|
|
|
|
|
}
|
2026-10-27 17:34:46 +01:00
|
|
|
int main(int argc,char **argv)
|
|
|
|
|
{
|
|
|
|
|
unsigned int i; xtc_plugin_spec spec; xtc_plugin *p = NULL; float data[1] = {1};
|
|
|
|
|
assert(argc == 4);
|
2026-11-06 18:13:49 +01:00
|
|
|
context_check(argv[3]);
|
2026-10-27 17:34:46 +01:00
|
|
|
check(argv[1],XTC_PLUGIN_CLAP); check(argv[2],XTC_PLUGIN_VST2); check(argv[3],XTC_PLUGIN_VST3);
|
|
|
|
|
for (i = 1; i < 4; ++i) assert(xtc_plugin_probe_format(XTC_PLUGIN_CLAP,argv[1],i,48000,&spec) != XTC_OK);
|
|
|
|
|
assert(xtc_plugin_probe_format(XTC_PLUGIN_CLAP,argv[1],4,48000,&spec) == XTC_OK);
|
|
|
|
|
assert(xtc_plugin_create(&spec,48000,1,&p) == XTC_OK);
|
|
|
|
|
assert(xtc_plugin_process(p,data,data,1) == XTC_ERR_INVALID_STATE && data[0] == 0); xtc_plugin_destroy(p); p = NULL;
|
|
|
|
|
assert(xtc_plugin_probe_format(XTC_PLUGIN_CLAP,argv[1],5,48000,&spec) == XTC_OK);
|
|
|
|
|
assert(xtc_plugin_create(&spec,48000,1,&p) == XTC_OK);
|
|
|
|
|
assert(xtc_plugin_process(p,data,data,1) == XTC_ERR_INVALID_STATE && data[0] == 0); xtc_plugin_destroy(p);
|
|
|
|
|
assert(xtc_plugin_probe_format(XTC_PLUGIN_CLAP,argv[1],6,48000,&spec) == XTC_OK);
|
|
|
|
|
p = NULL; data[0] = 1;
|
|
|
|
|
assert(xtc_plugin_create(&spec,48000,1,&p) == XTC_OK);
|
|
|
|
|
assert(xtc_plugin_process(p,data,data,1) == XTC_OK && data[0] == .5f);
|
|
|
|
|
xtc_plugin_idle();
|
|
|
|
|
assert(xtc_plugin_process(p,data,data,1) == XTC_ERR_INVALID_STATE && data[0] == 0); xtc_plugin_destroy(p);
|
|
|
|
|
assert(xtc_plugin_probe_format(XTC_PLUGIN_CLAP,argv[1],7,48000,&spec) == XTC_ERR_NOT_FOUND);
|
|
|
|
|
assert(setenv("XTC_VST2_FIXTURE_LAYOUT","1",1) == 0);
|
|
|
|
|
assert(xtc_plugin_probe_format(XTC_PLUGIN_VST2,argv[2],0,48000,&spec) == XTC_ERR_UNSUPPORTED);
|
|
|
|
|
unsetenv("XTC_VST2_FIXTURE_LAYOUT");
|
|
|
|
|
assert(setenv("XTC_VST3_FIXTURE_INIT","1",1) == 0);
|
|
|
|
|
assert(xtc_plugin_probe_format(XTC_PLUGIN_VST3,argv[3],0,48000,&spec) == XTC_ERR_INVALID_STATE);
|
|
|
|
|
unsetenv("XTC_VST3_FIXTURE_INIT");
|
|
|
|
|
puts("Modern plugin partial initialization/start/process failures passed."); return 0;
|
|
|
|
|
}
|