Phase 32: add MIDI track CLAP, VST2 and VST3 instruments
This commit is contained in:
parent
9e0e4562d7
commit
fe484537f9
41 changed files with 1138 additions and 65 deletions
|
|
@ -827,6 +827,27 @@ int main(int argc, char **argv)
|
|||
unlink("/tmp/xtc-gui-modern-test.xtc"); window.do_callback();
|
||||
}
|
||||
}
|
||||
for (const char *variable : {"XTC_TEST_SYNTH_CLAP","XTC_TEST_SYNTH_VST2","XTC_TEST_SYNTH_VST3"}) {
|
||||
if (const char *module = std::getenv(variable)) {
|
||||
reset(); MainWindow window(false); auto *sequence = timeline(&window);
|
||||
CHECK(sequence->add_track() == XTC_OK && sequence->add_midi_clip() == XTC_OK);
|
||||
uint64_t track = sequence->selected_track(); xtc_plugin_spec spec{};
|
||||
CHECK(window.assign_instrument(track,module,0) == XTC_OK);
|
||||
CHECK(xtc_project_get_instrument(sequence->project(),track,&spec) == XTC_OK && spec.instrument);
|
||||
CHECK(window.undo(false) == XTC_OK);
|
||||
CHECK(xtc_project_get_instrument(sequence->project(),track,&spec) == XTC_ERR_NOT_FOUND);
|
||||
CHECK(window.undo(true) == XTC_OK);
|
||||
CHECK(window.select_live_instrument(UINT64_MAX) == XTC_OK);
|
||||
CHECK(window.select_live_instrument(track) == XTC_OK);
|
||||
const xtc_project *before = sequence->project();
|
||||
CHECK(window.assign_instrument(track,"/absent/instrument.so",0) == XTC_ERR_NOT_FOUND && sequence->project() == before);
|
||||
CHECK(window.save_project("/tmp/xtc-gui-instrument.xtc") == XTC_OK);
|
||||
CHECK(window.new_project() == XTC_OK && window.open_project("/tmp/xtc-gui-instrument.xtc") == XTC_OK);
|
||||
CHECK(xtc_project_get_instrument(sequence->project(),track,&spec) == XTC_OK && spec.instrument);
|
||||
CHECK(window.assign_instrument(track,nullptr,0) == XTC_OK);
|
||||
unlink("/tmp/xtc-gui-instrument.xtc"); window.do_callback();
|
||||
}
|
||||
}
|
||||
if (failures != 0) return 1;
|
||||
std::printf("GUI controls/errors/views/timers/cleanup passed: %u opens, %u closes.\n", opens, closes);
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ def main():
|
|||
run([str(binaries / 'xtc-check'), '--inspect', str(example)])
|
||||
assert (resources / 'xtc/examples/README.md').is_file()
|
||||
diagnose = subprocess.check_output([str(binaries / 'xtc-check'), '--diagnose'], text=True)
|
||||
assert 'Project formats: 1..11' in diagnose and 'JACK compiled:' in diagnose
|
||||
assert 'Project formats: 1..12' in diagnose and 'JACK compiled:' in diagnose
|
||||
# Replacing a file symlink must not overwrite an unrelated target.
|
||||
protected = stage / 'unrelated-target'
|
||||
protected.write_text('keep unrelated target\n')
|
||||
|
|
|
|||
121
tests/integration/test_instruments.c
Normal file
121
tests/integration/test_instruments.c
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "xtc/xtc_plugin.h"
|
||||
#include "xtc/xtc_mixer.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.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 xtc_result render(xtc_midi_plan *plan, uint64_t position, const xtc_midi_event *events, size_t count, float *out, size_t frames)
|
||||
{
|
||||
xtc_result r; audio = 1;
|
||||
r = xtc_midi_plan_render(plan,position,events,count,out,frames);
|
||||
audio = 0; return r;
|
||||
}
|
||||
static void check(const char *path)
|
||||
{
|
||||
xtc_project *p = NULL, *q = NULL;
|
||||
xtc_midi_plan *plan = NULL;
|
||||
xtc_plugin_spec spec, actual;
|
||||
xtc_plugin *plugin = NULL;
|
||||
uint64_t track, clip, note, start, end, live;
|
||||
float out[600]; size_t i;
|
||||
char file[256];
|
||||
xtc_midi_event events[] = {{3,XTC_MIDI_NOTE_ON,0,60,127},{19,XTC_MIDI_NOTE_OFF,0,60,0}};
|
||||
assert(xtc_plugin_probe(path,0,48000,&spec) == XTC_OK && spec.instrument && !spec.inputs);
|
||||
assert(xtc_plugin_create(&spec,48000,256,&plugin) == XTC_OK);
|
||||
assert(xtc_plugin_process_midi(plugin,events,2,out,31) == XTC_OK);
|
||||
for (i = 0; i < 31; ++i) assert(out[i] == (i >= 3 && i < 19 ? 1.0f : 0));
|
||||
xtc_plugin_destroy(plugin);
|
||||
assert(xtc_project_create(&p) == XTC_OK && xtc_project_create(&q) == XTC_OK);
|
||||
assert(xtc_project_add_track(p,"Synth",&track) == XTC_OK);
|
||||
assert(xtc_project_add_midi_clip(p,track,0,960,&clip) == XTC_OK);
|
||||
assert(xtc_project_add_note(p,clip,2,10,60,127,0,¬e) == XTC_OK);
|
||||
assert(xtc_project_tick_to_sample(p,2,48000,&start) == XTC_OK);
|
||||
assert(xtc_project_tick_to_sample(p,12,48000,&end) == XTC_OK);
|
||||
assert(xtc_project_set_instrument(p,track,&spec) == XTC_OK);
|
||||
assert(xtc_project_get_live_instrument(p,&live) == XTC_OK && live == track);
|
||||
assert(xtc_midi_plan_create(p,48000,&plan) == XTC_OK);
|
||||
assert(render(plan,0,NULL,0,out,600) == XTC_OK);
|
||||
for (i = 0; i < 600; ++i) assert(fabsf(out[i]-(i >= start && i < end ? 1.0f : 0)) < .000001f);
|
||||
/* Seek chases notes, panic suppresses them until the next discontinuity. */
|
||||
assert(render(plan,start+1,NULL,0,out,1) == XTC_OK && out[0] == 1);
|
||||
xtc_midi_plan_panic(plan,start+2);
|
||||
assert(render(plan,start+2,NULL,0,out,1) == XTC_OK && out[0] == 0);
|
||||
assert(render(plan,1000,events,2,out,31) == XTC_OK);
|
||||
for (i = 0; i < 31; ++i) assert(out[i] == (i >= 3 && i < 19 ? 1.0f : 0));
|
||||
xtc_midi_plan_destroy(plan);
|
||||
snprintf(file,sizeof(file),"/tmp/xtc-instrument-%ld.xtc",(long)getpid());
|
||||
assert(xtc_project_save(p,file) == XTC_OK);
|
||||
assert(xtc_project_load(q,file) == XTC_OK);
|
||||
assert(xtc_project_get_instrument(q,track,&actual) == XTC_OK && actual.instrument && !strcmp(actual.path,path));
|
||||
assert(xtc_project_get_live_instrument(q,&live) == XTC_OK && live == track);
|
||||
assert(xtc_midi_plan_create(q,48000,&plan) == XTC_OK);
|
||||
xtc_midi_plan_destroy(plan);
|
||||
assert(xtc_project_set_instrument(q,track,NULL) == XTC_OK);
|
||||
assert(xtc_project_get_instrument(q,track,&actual) == XTC_ERR_NOT_FOUND);
|
||||
assert(xtc_project_get_live_instrument(q,&live) == XTC_OK && live == UINT64_MAX);
|
||||
/* Independent instances sum through track gains, and stable IDs survive reorder. */
|
||||
{
|
||||
uint64_t second, clip2, note2, bus;
|
||||
assert(xtc_project_add_track(p,"Second",&second) == XTC_OK);
|
||||
assert(xtc_project_add_midi_clip(p,second,0,960,&clip2) == XTC_OK);
|
||||
assert(xtc_project_add_note(p,clip2,2,10,62,127,1,¬e2) == XTC_OK);
|
||||
assert(xtc_project_set_instrument(p,second,&spec) == XTC_OK);
|
||||
assert(xtc_project_set_mix(p,second,.25f,0,0,0) == XTC_OK);
|
||||
assert(xtc_project_move_track(p,second,0) == XTC_OK);
|
||||
assert(xtc_project_get_instrument(p,track,&actual) == XTC_OK);
|
||||
assert(xtc_midi_plan_create(p,48000,&plan) == XTC_OK);
|
||||
assert(render(plan,0,NULL,0,out,600) == XTC_OK);
|
||||
assert(fabsf(out[start]-1.25f) < .000001f);
|
||||
xtc_midi_plan_destroy(plan);
|
||||
assert(xtc_project_add_bus(p,"Synth bus",&bus) == XTC_OK);
|
||||
assert(xtc_project_set_instrument(p,bus,&spec) == XTC_ERR_INVALID_STATE);
|
||||
assert(xtc_project_route_track(p,second,bus) == XTC_OK);
|
||||
assert(xtc_project_set_mix(p,bus,.5f,0,0,0) == XTC_OK);
|
||||
assert(xtc_midi_plan_create(p,48000,&plan) == XTC_OK);
|
||||
assert(render(plan,0,NULL,0,out,600) == XTC_OK && fabsf(out[start]-1.125f) < .000001f);
|
||||
xtc_midi_plan_destroy(plan);
|
||||
assert(xtc_project_set_mix(p,second,.25f,0,1,0) == XTC_OK);
|
||||
assert(xtc_midi_plan_create(p,48000,&plan) == XTC_OK);
|
||||
assert(render(plan,0,NULL,0,out,600) == XTC_OK && out[start] == 1);
|
||||
xtc_midi_plan_destroy(plan);
|
||||
assert(xtc_project_remove_track(p,second) == XTC_OK);
|
||||
assert(xtc_project_get_live_instrument(p,&live) == XTC_OK && live == UINT64_MAX);
|
||||
}
|
||||
/* Exhausting a chunk's event budget fails silent and recovers on next render. */
|
||||
{
|
||||
xtc_midi_event many[256];
|
||||
assert(xtc_project_set_live_instrument(p,track) == XTC_OK);
|
||||
for (i = 0; i < 256; ++i) many[i] = (xtc_midi_event){0,XTC_MIDI_NOTE_ON,0,60,127};
|
||||
assert(xtc_midi_plan_create(p,48000,&plan) == XTC_OK);
|
||||
assert(render(plan,1000,many,256,out,31) == XTC_ERR_CAPACITY);
|
||||
for (i = 0; i < 31; ++i) assert(out[i] == 0);
|
||||
assert(render(plan,1031,events,2,out,31) == XTC_OK);
|
||||
xtc_midi_plan_destroy(plan);
|
||||
}
|
||||
/* Missing modules remain model data; playback preparation fails cleanly. */
|
||||
actual = spec; strcpy(actual.path,"/absent/xtc-instrument.so");
|
||||
assert(xtc_project_set_instrument(q,track,&actual) == XTC_OK);
|
||||
assert(xtc_project_save(q,file) == XTC_OK && xtc_project_load(p,file) == XTC_OK);
|
||||
plan = NULL;
|
||||
assert(xtc_midi_plan_create(p,48000,&plan) == XTC_ERR_NOT_FOUND && !plan);
|
||||
xtc_project_destroy(q); xtc_project_destroy(p); unlink(file);
|
||||
printf("%s instrument offsets, scheduling, live routing, seek/panic and v12 reload passed.\n",xtc_plugin_format_name(spec.format));
|
||||
}
|
||||
int main(int argc, char **argv)
|
||||
{ int i; assert(argc == 4); for (i = 1; i < argc; ++i) check(argv[i]); assert(setenv("XTC_TEST_NATIVE_NOTES","1",1) == 0); check(argv[1]); return 0; }
|
||||
|
|
@ -19,7 +19,7 @@ int main(void)
|
|||
assert(mkdtemp(root));
|
||||
assert(xtc_project_create(&p) == XTC_OK);
|
||||
snprintf(file,sizeof(file),"%s/migration.xtc",root);
|
||||
for (version = 1; version <= 11; ++version) {
|
||||
for (version = 1; version <= 12; ++version) {
|
||||
xtc_track_info t; xtc_clip_info c; size_t notes, points;
|
||||
snprintf(old,sizeof(old),"tests/fixtures/project-v%u.xtc",version);
|
||||
assert(xtc_project_load(p,old) == XTC_OK);
|
||||
|
|
@ -89,6 +89,6 @@ int main(void)
|
|||
assert(xtc_project_load(p,file) == XTC_ERR_FORMAT);
|
||||
{ xtc_clip_info c; assert(xtc_project_find_clip(p,clip,&c) == XTC_OK && c.asset_path[0]); } }
|
||||
xtc_project_destroy(p); unlink(file); rmdir(moved);
|
||||
puts("v1-v11 migration, current folder relocation, cwd independence and missing-media preservation passed.");
|
||||
puts("v1-v12 migration, current folder relocation, cwd independence and missing-media preservation passed.");
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,8 +65,26 @@ with tempfile.TemporaryDirectory(prefix="xtc-inspect-") as directory:
|
|||
if number == 3:
|
||||
project.write_text(modern.replace(identity.encode().hex(), b"bad-class-id".hex()))
|
||||
check("--validate", project, 1)
|
||||
# V12 track bindings retain references without executing module contents.
|
||||
for number, identity in ((1, "org.xtc.instrument"), (2, ""),
|
||||
(3, "0123456789abcdef0123456789abcdef")):
|
||||
binding = (f"INSTRUMENT 0 {number} 0 42 {b'synth'.hex()}\n"
|
||||
f"MODULE {str(module).encode().hex()}\n"
|
||||
f"IDENTITY {identity.encode().hex() if identity else '-'}\n")
|
||||
document = base.replace("XTC_PROJECT 10", "XTC_PROJECT 12").replace(
|
||||
"END\n", "TRACK_INSTRUMENTS 1\n" + binding + "LIVE_INSTRUMENT 0\nEND\n")
|
||||
project.write_text(document)
|
||||
check("--inspect", project, 0, "instrument track 0", "no plugin code executed")
|
||||
for malformed in (
|
||||
document.replace("TRACK_INSTRUMENTS 1", "TRACK_INSTRUMENTS 2").replace(binding,binding+binding),
|
||||
document.replace("INSTRUMENT 0 ", "INSTRUMENT 99 "),
|
||||
document.replace("LIVE_INSTRUMENT 0", "LIVE_INSTRUMENT 99"),
|
||||
document.replace(f"INSTRUMENT 0 {number}", "INSTRUMENT 0 0"),
|
||||
):
|
||||
project.write_text(malformed)
|
||||
check("--validate", project, 1)
|
||||
for text, messages in [
|
||||
(base.replace("XTC_PROJECT 10", "XTC_PROJECT 999"), ("line 1", "999", "1..11")),
|
||||
(base.replace("XTC_PROJECT 10", "XTC_PROJECT 999"), ("line 1", "999", "1..12")),
|
||||
("XTC_PROJECT 10\n", ("line 2", "APP")),
|
||||
(base.replace("TEMPO 120000", "TEMPO 0"), ("line 3", "TEMPO")),
|
||||
(base + "OPTIONAL_FUTURE 1\n", ("Unknown fields",)),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue