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
2
tests/fixtures/README.md
vendored
2
tests/fixtures/README.md
vendored
|
|
@ -3,7 +3,7 @@
|
|||
Store small, redistributable fixtures here. Document their origin and license.
|
||||
Do not commit large recordings or assets with unclear licensing.
|
||||
|
||||
`project-v1.xtc` through `project-v11.xtc` are hand-authored xtc metadata fixtures
|
||||
`project-v1.xtc` through `project-v12.xtc` are hand-authored xtc metadata fixtures
|
||||
created for this repository, under the project license. They contain no audio
|
||||
or third-party resources. The v5/v6 plugin fixtures intentionally reference a
|
||||
missing module to exercise metadata migration without loading code.
|
||||
|
|
|
|||
41
tests/fixtures/project-v12.xtc
vendored
Normal file
41
tests/fixtures/project-v12.xtc
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
XTC_PROJECT 12
|
||||
APP 0 1 0
|
||||
TEMPO 120000
|
||||
IDS 1 1
|
||||
GRAPH 1 2 2 1
|
||||
NODE 0 1 3f000000
|
||||
NODE 1 4 00000000
|
||||
EDGE 0 1
|
||||
TRACKS 1
|
||||
TRACK 0 4b657973
|
||||
MIX 3f800000 00000000 0 0
|
||||
ROUTE 0 18446744073709551615
|
||||
CLIPS 1
|
||||
CLIP 0 0 0 960 0 960 5665727365
|
||||
ASSET -
|
||||
MIDI 1
|
||||
AUDIO 3f800000 0 0
|
||||
NOTES 1 1
|
||||
NOTE 0 0 0 480 69 100 0
|
||||
AUTOMATION 1
|
||||
POINT 0 0 0 3f800000 0
|
||||
CONTROL_MAPS 16
|
||||
MAP 0 7 10 64
|
||||
MAP 1 7 10 64
|
||||
MAP 2 7 10 64
|
||||
MAP 3 7 10 64
|
||||
MAP 4 7 10 64
|
||||
MAP 5 7 10 64
|
||||
MAP 6 7 10 64
|
||||
MAP 7 7 10 64
|
||||
MAP 8 7 10 64
|
||||
MAP 9 7 10 64
|
||||
MAP 10 7 10 64
|
||||
MAP 11 7 10 64
|
||||
MAP 12 7 10 64
|
||||
MAP 13 7 10 64
|
||||
MAP 14 7 10 64
|
||||
MAP 15 7 10 64
|
||||
TRACK_INSTRUMENTS 0
|
||||
LIVE_INSTRUMENT 18446744073709551615
|
||||
END
|
||||
|
|
@ -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",)),
|
||||
|
|
|
|||
100
tests/plugins/clap_synth_fixture.c
Normal file
100
tests/plugins/clap_synth_fixture.c
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#include <clap/clap.h>
|
||||
#include <clap/ext/thread-check.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
static int loaded, count;
|
||||
static const char *features[] = {CLAP_PLUGIN_FEATURE_INSTRUMENT,NULL};
|
||||
static const clap_plugin_descriptor_t descriptors[] = {
|
||||
{CLAP_VERSION,"xtc.fixture.gain","CLAP gain","xtc","","","","1","",features},
|
||||
{CLAP_VERSION,"xtc.fixture.stereo","Stereo","xtc","","","","1","",features},
|
||||
{CLAP_VERSION,"xtc.fixture.init","Init failure","xtc","","","","1","",features},
|
||||
{CLAP_VERSION,"xtc.fixture.activate","Activate failure","xtc","","","","1","",features},
|
||||
{CLAP_VERSION,"xtc.fixture.start","Start failure","xtc","","","","1","",features},
|
||||
{CLAP_VERSION,"xtc.fixture.process","Process failure","xtc","","","","1","",features},
|
||||
{CLAP_VERSION,"xtc.fixture.restart","Restart request","xtc","","","","1","",features}
|
||||
};
|
||||
typedef struct { clap_plugin_t plugin; const clap_host_t *host; unsigned int index; int active, started; float level; } instance;
|
||||
static bool init(const clap_plugin_t *plugin)
|
||||
{ instance *p = plugin->plugin_data; assert(loaded); return p->index != 2; }
|
||||
static void destroy(const clap_plugin_t *plugin)
|
||||
{ instance *p = plugin->plugin_data; assert(!p->active && !p->started); --count; free(p); }
|
||||
static bool activate(const clap_plugin_t *plugin, double rate, uint32_t min, uint32_t max)
|
||||
{ instance *p = plugin->plugin_data; assert(rate > 0 && min && max >= min && !p->active); if (p->index == 3) return false; p->active = 1; return true; }
|
||||
static void deactivate(const clap_plugin_t *plugin)
|
||||
{ instance *p = plugin->plugin_data; assert(p->active && !p->started); p->active = 0; }
|
||||
static bool start(const clap_plugin_t *plugin)
|
||||
{
|
||||
instance *p = plugin->plugin_data;
|
||||
const clap_host_thread_check_t *check = p->host->get_extension(p->host,CLAP_EXT_THREAD_CHECK);
|
||||
assert(p->active && !p->started && check->is_audio_thread(p->host));
|
||||
if (p->index == 4) return false;
|
||||
p->started = 1; return true;
|
||||
}
|
||||
static void stop(const clap_plugin_t *plugin)
|
||||
{ instance *p = plugin->plugin_data; assert(p->started); p->started = 0; }
|
||||
static void reset(const clap_plugin_t *plugin) { (void)plugin; }
|
||||
static clap_process_status process(const clap_plugin_t *plugin, const clap_process_t *block)
|
||||
{
|
||||
instance *p = plugin->plugin_data; uint32_t i;
|
||||
uint32_t j = 0, event_total = block->in_events->size(block->in_events);
|
||||
assert(p->started && block->audio_inputs_count == 0 && block->audio_outputs_count == 1);
|
||||
if (p->index == 5) return CLAP_PROCESS_ERROR;
|
||||
for (i = 0; i < block->frames_count; ++i) {
|
||||
while (j < event_total) {
|
||||
const clap_event_header_t *h = block->in_events->get(block->in_events,j);
|
||||
const clap_event_midi_t *m = (const clap_event_midi_t *)h;
|
||||
if (h->time != i) break;
|
||||
++j;
|
||||
if (h->type != CLAP_EVENT_MIDI) {
|
||||
const clap_event_note_t *note = (const clap_event_note_t *)h;
|
||||
assert(h->type == CLAP_EVENT_NOTE_ON || h->type == CLAP_EVENT_NOTE_OFF || h->type == CLAP_EVENT_NOTE_CHOKE);
|
||||
p->level = h->type == CLAP_EVENT_NOTE_ON ? (float)note->velocity : 0;
|
||||
continue;
|
||||
}
|
||||
if ((m->data[0]&240) == 144) p->level = (float)m->data[2]/127;
|
||||
else if ((m->data[0]&240) == 128 || ((m->data[0]&240) == 176 && m->data[1] == 120)) p->level = 0;
|
||||
}
|
||||
block->audio_outputs[0].data32[0][i] = p->level;
|
||||
}
|
||||
p->host->request_callback(p->host);
|
||||
if (p->index == 6) p->host->request_restart(p->host);
|
||||
return CLAP_PROCESS_CONTINUE;
|
||||
}
|
||||
static uint32_t port_count(const clap_plugin_t *plugin, bool input) { (void)plugin; return input ? 0 : 1; }
|
||||
static bool port_get(const clap_plugin_t *plugin, uint32_t index, bool input, clap_audio_port_info_t *info)
|
||||
{ instance *p = plugin->plugin_data; (void)input; if (index) return false; memset(info,0,sizeof(*info)); info->channel_count = p->index == 1 ? 2 : 1; return true; }
|
||||
static uint32_t latency(const clap_plugin_t *plugin)
|
||||
{ instance *p = plugin->plugin_data; assert(p->active); return 0; }
|
||||
static uint32_t note_count(const clap_plugin_t *p, bool input) { (void)p; return input ? 1 : 0; }
|
||||
static bool note_get(const clap_plugin_t *p, uint32_t index, bool input, clap_note_port_info_t *info)
|
||||
{ (void)p; if (index || !input) return false; memset(info,0,sizeof(*info)); info->supported_dialects = info->preferred_dialect = getenv("XTC_TEST_NATIVE_NOTES") ? CLAP_NOTE_DIALECT_CLAP : CLAP_NOTE_DIALECT_MIDI; return true; }
|
||||
static const void *extension(const clap_plugin_t *plugin, const char *id)
|
||||
{
|
||||
static const clap_plugin_audio_ports_t ports = {port_count,port_get};
|
||||
static const clap_plugin_latency_t delay = {latency}; (void)plugin;
|
||||
static const clap_plugin_note_ports_t notes = {note_count,note_get};
|
||||
if (!strcmp(id,CLAP_EXT_NOTE_PORTS)) return ¬es;
|
||||
if (!strcmp(id,CLAP_EXT_AUDIO_PORTS)) return &ports;
|
||||
return !strcmp(id,CLAP_EXT_LATENCY) ? &delay : NULL;
|
||||
}
|
||||
static void on_main(const clap_plugin_t *plugin)
|
||||
{ instance *p = plugin->plugin_data; const clap_host_thread_check_t *check = p->host->get_extension(p->host,CLAP_EXT_THREAD_CHECK); assert(check->is_main_thread(p->host)); }
|
||||
static uint32_t plugin_count(const clap_plugin_factory_t *factory) { (void)factory; return 7; }
|
||||
static const clap_plugin_descriptor_t *descriptor(const clap_plugin_factory_t *factory, uint32_t index)
|
||||
{ (void)factory; return index < 7 ? descriptors+index : NULL; }
|
||||
static const clap_plugin_t *create(const clap_plugin_factory_t *factory, const clap_host_t *host, const char *id)
|
||||
{
|
||||
unsigned int i; instance *p; (void)factory;
|
||||
for (i = 0; i < 7 && strcmp(id,descriptors[i].id); ++i) {}
|
||||
if (i == 7) return NULL;
|
||||
p = calloc(1,sizeof(*p)); if (!p) return NULL;
|
||||
p->host = host; p->index = i;
|
||||
p->plugin = (clap_plugin_t){descriptors+i,p,init,destroy,activate,deactivate,start,stop,reset,process,extension,on_main};
|
||||
++count; return &p->plugin;
|
||||
}
|
||||
static bool entry_init(const char *path) { (void)path; assert(!loaded); loaded = 1; return true; }
|
||||
static void entry_exit(void) { assert(loaded && !count); loaded = 0; }
|
||||
static const void *factory(const char *id)
|
||||
{ static const clap_plugin_factory_t f = {plugin_count,descriptor,create}; return !strcmp(id,CLAP_PLUGIN_FACTORY_ID) ? &f : NULL; }
|
||||
CLAP_EXPORT const clap_plugin_entry_t clap_entry = {CLAP_VERSION,entry_init,entry_exit,factory};
|
||||
45
tests/plugins/vst2_synth_fixture.c
Normal file
45
tests/plugins/vst2_synth_fixture.c
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/* Original MIT ABI fixture; not built from or linked to a proprietary SDK. */
|
||||
#include "../../src/plugin/vst2_abi.h"
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
typedef struct { xtc_v2_effect effect; int open, active, started; float level; xtc_v2_midi events[256]; int count; } instance;
|
||||
static intptr_t dispatch(xtc_v2_effect *e, int32_t op, int32_t index, intptr_t value, void *ptr, float option)
|
||||
{
|
||||
instance *p = (instance *)e; (void)index; (void)option;
|
||||
switch (op) {
|
||||
case 0: assert(!p->open); p->open = 1; break;
|
||||
case 1: assert(p->open && !p->active && !p->started); free(p); break;
|
||||
case 12: p->active = value != 0; break;
|
||||
case 25: { xtc_v2_events *events = ptr; int i; assert(events->count <= 256); p->count = events->count; for (i = 0; i < p->count; ++i) p->events[i] = *events->events[i]; break; }
|
||||
case 45: strcpy(ptr,"VST2 gain"); return 1;
|
||||
case 71: assert(p->active); p->started = 1; break;
|
||||
case 72: p->started = 0; break;
|
||||
default: break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static void process(xtc_v2_effect *e, float **input, float **output, int32_t frames)
|
||||
{ instance *p = (instance *)e; int32_t i, j = 0; assert(p->started && !input);
|
||||
for (i = 0; i < frames; ++i) {
|
||||
while (j < p->count && p->events[j].offset == i) {
|
||||
unsigned char *m = p->events[j++].data;
|
||||
if ((m[0]&240) == 144) p->level = (float)m[2]/127;
|
||||
else if ((m[0]&240) == 128 || ((m[0]&240) == 176 && m[1] == 120)) p->level = 0;
|
||||
}
|
||||
output[0][i] = p->level;
|
||||
} p->count = 0;
|
||||
}
|
||||
|
||||
xtc_v2_effect *VSTPluginMain(xtc_v2_host host);
|
||||
xtc_v2_effect *VSTPluginMain(xtc_v2_host host)
|
||||
{
|
||||
instance *p; assert(host(NULL,1,0,0,NULL,0) == 2400);
|
||||
p = calloc(1,sizeof(*p)); if (!p) return NULL;
|
||||
p->effect.magic = XTC_V2_MAGIC; p->effect.dispatch = dispatch;
|
||||
p->effect.inputs = 0; p->effect.outputs = 1;
|
||||
if (getenv("XTC_VST2_FIXTURE_LAYOUT")) p->effect.inputs = 2;
|
||||
p->effect.flags = 16|256;
|
||||
p->effect.unique_id = 0x58544332; p->effect.replace = process;
|
||||
return &p->effect;
|
||||
}
|
||||
88
tests/plugins/vst3_synth_fixture.c
Normal file
88
tests/plugins/vst3_synth_fixture.c
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
#include <stdbool.h>
|
||||
#include <vst3_c_api.h>
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
static int loaded, instances;
|
||||
static const Steinberg_TUID cid = SMTG_INLINE_UID(0x58544333,0x12345678,0x12345678,0x12345678);
|
||||
typedef struct { Steinberg_Vst_IComponent component; Steinberg_Vst_IAudioProcessor processor; unsigned int refs; int initialized, active, started; float level; } instance;
|
||||
static instance *from_processor(void *p) { return (instance *)((char *)p - offsetof(instance,processor)); }
|
||||
static Steinberg_tresult cq(void *self, const Steinberg_TUID iid, void **out)
|
||||
{
|
||||
instance *p = self; *out = NULL;
|
||||
if (!memcmp(iid,Steinberg_Vst_IAudioProcessor_iid,16)) *out = &p->processor;
|
||||
else if (!memcmp(iid,Steinberg_Vst_IComponent_iid,16) || !memcmp(iid,Steinberg_FUnknown_iid,16)) *out = &p->component;
|
||||
if (!*out) return -1;
|
||||
++p->refs; return 0;
|
||||
}
|
||||
static Steinberg_uint32 add(void *self) { return ++((instance *)self)->refs; }
|
||||
static Steinberg_uint32 release(void *self)
|
||||
{ instance *p = self; unsigned int refs = --p->refs; if (!refs) { assert(!p->initialized); --instances; free(p); } return refs; }
|
||||
static Steinberg_tresult pq(void *self, const Steinberg_TUID iid, void **out) { return cq(from_processor(self),iid,out); }
|
||||
static Steinberg_uint32 pa(void *self) { return add(from_processor(self)); }
|
||||
static Steinberg_uint32 pr(void *self) { return release(from_processor(self)); }
|
||||
static Steinberg_tresult init(void *self, Steinberg_FUnknown *host)
|
||||
{ instance *p = self; assert(loaded && host && !p->initialized); if (getenv("XTC_VST3_FIXTURE_INIT")) return -1; p->initialized = 1; return 0; }
|
||||
static Steinberg_tresult terminate(void *self) { instance *p = self; assert(p->initialized && !p->active); p->initialized = 0; return 0; }
|
||||
static Steinberg_tresult controller(void *self, Steinberg_TUID id) { (void)self; (void)id; return -1; }
|
||||
static Steinberg_tresult mode(void *self, Steinberg_Vst_IoMode m) { (void)self; (void)m; return 0; }
|
||||
static Steinberg_int32 buses(void *self, Steinberg_Vst_MediaType type, Steinberg_Vst_BusDirection dir) { (void)self; (void)dir; return type == 0 ? (dir == 1 ? 1 : 0) : (dir == 0 ? 1 : 0); }
|
||||
static Steinberg_tresult bus_info(void *self, Steinberg_Vst_MediaType type, Steinberg_Vst_BusDirection dir, Steinberg_int32 index, struct Steinberg_Vst_BusInfo *info)
|
||||
{ (void)self; (void)dir; assert(type == 0 && index == 0); memset(info,0,sizeof(*info)); info->channelCount = 1; return 0; }
|
||||
static Steinberg_tresult routing(void *self, struct Steinberg_Vst_RoutingInfo *in, struct Steinberg_Vst_RoutingInfo *out)
|
||||
{ (void)self; (void)in; (void)out; return -1; }
|
||||
static Steinberg_tresult activate_bus(void *self, Steinberg_Vst_MediaType type, Steinberg_Vst_BusDirection dir, Steinberg_int32 index, Steinberg_TBool state)
|
||||
{ (void)self; (void)type; (void)dir; (void)index; (void)state; return 0; }
|
||||
static Steinberg_tresult active(void *self, Steinberg_TBool state) { instance *p = self; assert(!p->started); p->active = state; return 0; }
|
||||
static Steinberg_tresult state(void *self, Steinberg_IBStream *stream) { (void)self; (void)stream; return -1; }
|
||||
static Steinberg_tresult arrangements(void *self, Steinberg_Vst_SpeakerArrangement *in, Steinberg_int32 ni, Steinberg_Vst_SpeakerArrangement *out, Steinberg_int32 no)
|
||||
{ (void)self; return ni == 0 && !in && no == 1 && out[0] == Steinberg_Vst_SpeakerArr_kMono ? 0 : -1; }
|
||||
static Steinberg_tresult arrangement(void *self, Steinberg_Vst_BusDirection dir, Steinberg_int32 index, Steinberg_Vst_SpeakerArrangement *out)
|
||||
{ (void)self; (void)dir; (void)index; *out = Steinberg_Vst_SpeakerArr_kMono; return 0; }
|
||||
static Steinberg_tresult sample_size(void *self, Steinberg_int32 size) { (void)self; return size == 0 ? 0 : -1; }
|
||||
static Steinberg_uint32 latency(void *self) { (void)self; return 0; }
|
||||
static Steinberg_tresult setup(void *self, struct Steinberg_Vst_ProcessSetup *data) { (void)self; return data->maxSamplesPerBlock > 0 && data->sampleRate > 0 ? 0 : -1; }
|
||||
static Steinberg_tresult processing(void *self, Steinberg_TBool value) { instance *p = from_processor(self); assert(p->active); p->started = value; return 0; }
|
||||
static Steinberg_tresult process(void *self, struct Steinberg_Vst_ProcessData *data)
|
||||
{ instance *p = from_processor(self); int32_t i, j = 0;
|
||||
Steinberg_Vst_IEventList *events = data->inputEvents;
|
||||
int32_t count = events->lpVtbl->getEventCount(events);
|
||||
assert(p->started && data->numInputs == 0);
|
||||
for (i = 0; i < data->numSamples; ++i) {
|
||||
while (j < count) {
|
||||
struct Steinberg_Vst_Event event;
|
||||
assert(events->lpVtbl->getEvent(events,j,&event) == 0);
|
||||
if (event.sampleOffset != i) break;
|
||||
++j;
|
||||
if (event.type == 0) p->level = event.Steinberg_Vst_Event_noteOn.velocity;
|
||||
else if (event.type == 1) p->level = 0;
|
||||
}
|
||||
data->outputs[0].Steinberg_Vst_AudioBusBuffers_channelBuffers32[0][i] = p->level;
|
||||
} return 0;
|
||||
}
|
||||
|
||||
static Steinberg_Vst_IComponentVtbl component_vtable = {cq,add,release,init,terminate,controller,mode,buses,bus_info,routing,activate_bus,active,state,state};
|
||||
static Steinberg_Vst_IAudioProcessorVtbl processor_vtable = {pq,pa,pr,arrangements,arrangement,sample_size,latency,setup,processing,process,latency};
|
||||
static Steinberg_tresult fq(void *self, const Steinberg_TUID iid, void **out) { (void)iid; *out = self; return 0; }
|
||||
static Steinberg_uint32 fr(void *self) { (void)self; return 1; }
|
||||
static Steinberg_tresult factory_info(void *self, struct Steinberg_PFactoryInfo *info) { (void)self; memset(info,0,sizeof(*info)); return 0; }
|
||||
static Steinberg_int32 classes(void *self) { (void)self; return 1; }
|
||||
static Steinberg_tresult class_info(void *self, Steinberg_int32 index, struct Steinberg_PClassInfo *info)
|
||||
{ (void)self; if (index) return -1; memset(info,0,sizeof(*info)); memcpy(info->cid,cid,16); strcpy(info->category,"Audio Module Class"); strcpy(info->name,"VST3 gain"); return 0; }
|
||||
static Steinberg_tresult create(void *self, Steinberg_FIDString id, Steinberg_FIDString iid, void **out)
|
||||
{
|
||||
instance *p; (void)self; *out = NULL;
|
||||
if (memcmp(id,cid,16) || memcmp(iid,Steinberg_Vst_IComponent_iid,16)) return -1;
|
||||
p = calloc(1,sizeof(*p)); if (!p) return -1;
|
||||
p->component.lpVtbl = &component_vtable; p->processor.lpVtbl = &processor_vtable; p->refs = 1;
|
||||
++instances; *out = p; return 0;
|
||||
}
|
||||
static Steinberg_IPluginFactoryVtbl factory_vtable = {fq,fr,fr,factory_info,classes,class_info,create};
|
||||
static Steinberg_IPluginFactory factory = {&factory_vtable};
|
||||
bool ModuleEntry(void *handle);
|
||||
bool ModuleExit(void);
|
||||
Steinberg_IPluginFactory *GetPluginFactory(void);
|
||||
bool ModuleEntry(void *handle) { assert(handle && !loaded); loaded = 1; return true; }
|
||||
bool ModuleExit(void) { assert(loaded && !instances); loaded = 0; return true; }
|
||||
Steinberg_IPluginFactory *GetPluginFactory(void) { assert(loaded); return &factory; }
|
||||
|
|
@ -197,7 +197,7 @@ int main(void)
|
|||
fputc('\n',legacy); continue;
|
||||
}
|
||||
if (strncmp(line, "MIX ", 4) && strncmp(line, "AUTOMATION ", 11) &&
|
||||
strncmp(line, "POINT ", 6) && strncmp(line, "AUDIO ", 6) && strncmp(line, "ROUTE ", 6) && strncmp(line,"CONTROL_MAPS ",13) && strncmp(line,"MAP ",4))
|
||||
strncmp(line, "POINT ", 6) && strncmp(line, "AUDIO ", 6) && strncmp(line, "ROUTE ", 6) && strncmp(line,"CONTROL_MAPS ",13) && strncmp(line,"MAP ",4) && strncmp(line,"TRACK_INSTRUMENTS ",18) && strncmp(line,"LIVE_INSTRUMENT ",16))
|
||||
assert(fputs(line, legacy) >= 0);
|
||||
}
|
||||
assert(fclose(f) == 0 && fclose(legacy) == 0);
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ int main(void)
|
|||
assert(xtc_project_add_track(old,"Old",&id) == XTC_OK && xtc_project_add_track(next,"New",&id) == XTC_OK);
|
||||
assert(xtc_project_save(old,song) == XTC_OK); size = bytes(song,before);
|
||||
assert(xtc_project_load_diagnostic(loaded,song,&diagnostic) == XTC_OK && diagnostic.result == XTC_OK &&
|
||||
diagnostic.format_version == 11 && !diagnostic.line);
|
||||
diagnostic.format_version == 12 && !diagnostic.line);
|
||||
for (mode = 1; mode <= 5; ++mode) {
|
||||
fault = mode;
|
||||
assert(xtc_project_save(next,song) == XTC_ERR_IO);
|
||||
|
|
@ -81,7 +81,7 @@ int main(void)
|
|||
}
|
||||
write_text(bad,"XTC_PROJECT 999\n");
|
||||
assert(xtc_project_load_diagnostic(loaded,bad,&diagnostic) == XTC_ERR_UNSUPPORTED && diagnostic.line == 1 &&
|
||||
diagnostic.format_version == 999 && !strcmp(diagnostic.record,"XTC_PROJECT") && strstr(diagnostic.message,"1..11"));
|
||||
diagnostic.format_version == 999 && !strcmp(diagnostic.record,"XTC_PROJECT") && strstr(diagnostic.message,"1..12"));
|
||||
write_text(bad,"XTC_PROJECT 10\nAPP 0 1 0\nTEMPO nope\n");
|
||||
assert(xtc_project_load_diagnostic(loaded,bad,&diagnostic) == XTC_ERR_FORMAT && diagnostic.line == 3 &&
|
||||
!strcmp(diagnostic.record,"TEMPO"));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue