feat(plugins): qualify host VST3 chains and refresh the DAW gallery (phase 89)

This commit is contained in:
Johannes Findeisen 2026-11-06 01:50:25 +01:00
commit 26ace28bb5
73 changed files with 344 additions and 18 deletions

View file

@ -259,6 +259,22 @@ int track_insert_tests()
CHECK(xtc_project_get_chain(s->project(),track,&chain)==XTC_OK && chain.count==1);
auto *preset_before=s->project();CHECK(panel->load_preset("/missing/preset.xtc")!=XTC_OK && s->project()==preset_before);
unlink("/tmp/xtc-inserts-preset.xtc");
for(const char *variable:{"XTC_TEST_PLUGIN","XTC_TEST_CLAP","XTC_TEST_VST2","XTC_TEST_VST3"}) {
if(const char *binary=std::getenv(variable)) {
CHECK(window.add_track_effect(track,binary,0)==XTC_OK);
CHECK(xtc_project_get_chain(s->project(),track,&chain)==XTC_OK && chain.count==2);
CHECK(window.undo()==XTC_OK);panel->refresh(true);
}
}
CHECK(xtc_project_get_chain(s->project(),track,&chain)==XTC_OK);
auto restored_chain=chain;for(size_t i=1;i<XTC_TRACK_INSERTS;++i)chain.slots[i]=chain.slots[0];chain.count=XTC_TRACK_INSERTS;
for(auto &slot:chain.slots){slot.enabled=1;slot.wet=.5f;}
CHECK(window.set_track_chain(track,chain)==XTC_OK);
auto *insert_canvas=graph_canvas(panel);auto *insert_scroll=dynamic_cast<Fl_Scroll *>(insert_canvas->parent());
CHECK(insert_scroll && insert_canvas->w()>insert_scroll->w());
insert_scroll->scroll_to(insert_canvas->w()-insert_scroll->w(),0);CHECK(insert_scroll->xposition()>0);
const auto *full_before=s->project();CHECK(window.add_track_effect(track,XTC_BUILTIN_PATH,2)==XTC_ERR_CAPACITY && s->project()==full_before);
CHECK(window.set_track_chain(track,restored_chain)==XTC_OK);
CHECK(window.save_project("/tmp/xtc-inserts-gui.xtc")==XTC_OK && window.new_project()==XTC_OK && window.open_project("/tmp/xtc-inserts-gui.xtc")==XTC_OK);
CHECK(xtc_project_get_chain(s->project(),track,&chain)==XTC_OK && chain.count==1);unlink("/tmp/xtc-inserts-gui.xtc");
window.do_callback();puts("Track inserts GUI adoption/undo/rollback/reload passed.");return failures ? 1 : 0;

View file

@ -83,6 +83,26 @@ with tempfile.TemporaryDirectory(prefix="xtc-inspect-") as directory:
):
project.write_text(malformed)
check("--validate", project, 1)
# V17 track-chain dependency inspection must not dlopen the readable fake module.
for number, name, identity in ((1, "CLAP", "org.xtc.fixture"), (2, "VST2", ""),
(3, "VST3", "0123456789abcdef0123456789abcdef")):
chain = ("CHAINS 1\nCHAIN 0 1 3f800000\n"
f"INSERT 1 3f800000 {number} 0 42 1 120000 {b'effect'.hex()}\n"
f"MODULE {str(module).encode().hex()}\nIDENTITY {identity.encode().hex() if identity else '-'}\n"
"CONTROLS 0\nPARAMETERS 0\n")
document = base.replace("XTC_PROJECT 10", "XTC_PROJECT 17").replace(
"END\n", "TRACK_INSTRUMENTS 0\nLIVE_INSTRUMENT 18446744073709551615\nMARKERS 0\n" + chain + "END\n")
project.write_text(document)
check("--inspect", project, 0, name + " insert track 0 slot 1", "no plugin code executed", "missing/unreadable: 0")
for malformed in (document.replace("CHAIN 0 1", "CHAIN 0 9"),
document.replace("INSERT 1 3f800000", "INSERT 1 7f800000"),
document.replace("CHAIN 0 1", "CHAIN 99 1")):
project.write_text(malformed)
check("--validate", project, 1)
project.write_text(document.replace(str(module).encode().hex(), b"/missing/effect.so".hex()))
check("--inspect", project, 2, name + " insert track 0", "missing/unreadable: 1")
project.write_text(document.replace("XTC_PROJECT 17", "XTC_PROJECT 16").replace(chain, ""))
check("--validate", project, 0, "metadata valid")
for text, messages in [
(base.replace("XTC_PROJECT 10", "XTC_PROJECT 999"), ("line 1", "999", "1..17")),
("XTC_PROJECT 10\n", ("line 2", "APP")),

View file

@ -45,7 +45,21 @@ 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; assert(p->started); for (i = 0; i < data->numSamples; ++i) data->outputs[0].Steinberg_Vst_AudioBusBuffers_channelBuffers32[0][i] = data->inputs[0].Steinberg_Vst_AudioBusBuffers_channelBuffers32[0][i]*.5f; return 0; }
{
instance *p=from_processor(self);int32_t i;Steinberg_int32 index=-1,offset=-1;Steinberg_Vst_ParamValue value=-1;
Steinberg_Vst_IParameterChanges *changes=data->outputParameterChanges;Steinberg_Vst_IParamValueQueue *queue;Steinberg_Vst_ParamID id=7;
assert(p->started && changes && changes->lpVtbl->getParameterCount(changes)==0);
queue=changes->lpVtbl->addParameterData(changes,&id,&index);assert(queue && index==0);
assert(queue->lpVtbl->getParameterId(queue)==7 && queue->lpVtbl->getPointCount(queue)==0);
assert(queue->lpVtbl->addPoint(queue,0,.2,&index)==0 && queue->lpVtbl->addPoint(queue,1,.8,&index)==0);
assert(queue->lpVtbl->getPointCount(queue)==1 && queue->lpVtbl->getPoint(queue,0,&offset,&value)==0 && offset==1 && value==.8);
assert(changes->lpVtbl->getParameterData(changes,0)==queue && changes->lpVtbl->getParameterData(changes,-1)==NULL);
for(id=8;id<71;++id)assert(changes->lpVtbl->addParameterData(changes,&id,&index));
assert(changes->lpVtbl->getParameterCount(changes)==64);
assert(changes->lpVtbl->addParameterData(changes,&id,&index)==NULL);
for(i=0;i<data->numSamples;++i)data->outputs[0].Steinberg_Vst_AudioBusBuffers_channelBuffers32[0][i]=data->inputs[0].Steinberg_Vst_AudioBusBuffers_channelBuffers32[0][i]*.5f;
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; }

View file

@ -2,6 +2,7 @@
#include "xtc/xtc_plugin.h"
#include "xtc/xtc_editing.h"
#include "xtc/xtc_audio.h"
#include "xtc/xtc_sample.h"
#include <assert.h>
#include <math.h>
#include <stdio.h>
@ -47,9 +48,36 @@ static void rendering(void)
for(size_t j=0;j<32;++j)assert(fabsf(out[j]-(j<8 ? 0.f : .05f*sinf(6.28318530718f*440.f*(float)(j-8)/8000.f)))<.00001f);
xtc_midi_plan_destroy(midi);xtc_project_destroy(p);unlink(wave);
}
static void host_vst3(void)
{
xtc_project *p=NULL;xtc_track_chain c={0};xtc_midi_plan *plan=NULL;uint64_t t,clip,note;float out[256];double energy=0;
if(!getenv("XTC_TEST_HOST_VST3"))return;
assert(xtc_project_create(&p)==XTC_OK && xtc_project_add_track(p,"Host VST3",&t)==XTC_OK && xtc_project_add_midi_clip(p,t,0,960,&clip)==XTC_OK && xtc_project_add_note(p,clip,0,960,69,100,0,&note)==XTC_OK);
const char *names[]={"ZamEQ2","ZamAutoSat","ZamDelay"};c.count=3;c.output_gain=1;
for(size_t i=0;i<3;++i) {
char binary[512];snprintf(binary,sizeof(binary),"/usr/lib/vst3/%s.vst3/Contents/x86_64-linux/%s.so",names[i],names[i]);
assert(xtc_plugin_probe_format(XTC_PLUGIN_VST3,binary,0,48000,&c.slots[i].plugin)==XTC_OK);
c.slots[i].enabled=1;c.slots[i].wet=1;printf("Host VST3 loaded: %s (%s)\n",c.slots[i].plugin.label,c.slots[i].plugin.identifier);
}
assert(xtc_project_set_chain(p,t,&c)==XTC_OK && xtc_midi_plan_create(p,48000,&plan)==XTC_OK);
for(size_t block=0;block<32;++block) {
assert(xtc_midi_plan_render(plan,block*256,NULL,0,out,256)==XTC_OK);
for(size_t i=0;i<256;++i){assert(isfinite(out[i]));energy+=out[i]*out[i];}
}
assert(energy>0);xtc_midi_plan_destroy(plan);
{char wave[]="/tmp/xtc-vst3-export-XXXXXX";int fd=mkstemp(wave),done=0;xtc_export *job=NULL;xtc_sample *sample=NULL;xtc_sample_stats stats;size_t frames;unsigned int rate;
assert(fd>=0);close(fd);unlink(wave);
assert(xtc_export_create(p,wave,48000,100,&job)==XTC_OK);
while(!done)assert(xtc_export_step(job,&done)==XTC_OK);
xtc_export_destroy(job);
assert(xtc_sample_load(wave,&sample)==XTC_OK && xtc_sample_info(sample,&rate,&frames)==XTC_OK && rate==48000 && frames>0);
assert(xtc_sample_analyze(sample,0,frames,&stats)==XTC_OK && stats.rms>0);
xtc_sample_destroy(sample);unlink(wave);}
xtc_project_destroy(p);
}
int main(void)
{
rendering();
rendering();host_vst3();
xtc_project *p=NULL,*q=NULL;uint64_t a,b,duplicate;xtc_track_chain c={0},got={0};
char file[]="/tmp/xtc-chain-XXXXXX";int fd=mkstemp(file);assert(fd>=0);close(fd);
assert(xtc_project_create(&p)==XTC_OK && xtc_project_add_track(p,"Audio",&a)==XTC_OK && xtc_project_add_track(p,"Other",&b)==XTC_OK);