2026-11-06 01:34:08 +01:00
|
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
|
#include "xtc/xtc_plugin.h"
|
|
|
|
|
#include "xtc/xtc_editing.h"
|
2026-11-06 01:35:53 +01:00
|
|
|
#include "xtc/xtc_audio.h"
|
2026-11-06 01:50:25 +01:00
|
|
|
#include "xtc/xtc_sample.h"
|
2026-11-06 01:34:08 +01:00
|
|
|
#include <assert.h>
|
|
|
|
|
#include <math.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <unistd.h>
|
2026-11-06 01:35:53 +01:00
|
|
|
static void rendering(void)
|
|
|
|
|
{
|
|
|
|
|
xtc_project *p=NULL;xtc_audio_plan *audio=NULL;xtc_midi_plan *midi=NULL;
|
|
|
|
|
xtc_track_chain c={0};xtc_recorder *rec=NULL;uint64_t track,clip,copy,note;
|
|
|
|
|
char wave[]="/tmp/xtc-fx-audio-XXXXXX";float impulse[32]={1},out[32],right[32];int fd=mkstemp(wave);assert(fd>=0);close(fd);unlink(wave);
|
|
|
|
|
assert(xtc_recorder_create(wave,8000,32,&rec)==XTC_OK);xtc_recorder_push(rec,impulse,32);assert(xtc_recorder_finish(rec)==XTC_OK);xtc_recorder_destroy(rec);
|
|
|
|
|
assert(xtc_project_create(&p)==XTC_OK && xtc_project_add_track(p,"Audio",&track)==XTC_OK);
|
|
|
|
|
assert(xtc_project_import_audio(p,track,wave,0,&clip)==XTC_OK);
|
|
|
|
|
assert(xtc_project_duplicate_clip(p,clip,track,0,©)==XTC_OK);
|
|
|
|
|
assert(xtc_plugin_probe_format(XTC_PLUGIN_BUILTIN,XTC_BUILTIN_PATH,2,8000,&c.slots[0].plugin)==XTC_OK);
|
|
|
|
|
c.count=1;c.output_gain=.5f;c.slots[0].enabled=1;c.slots[0].wet=1;
|
|
|
|
|
c.slots[0].plugin.values[0]=1;c.slots[0].plugin.values[1]=0;c.slots[0].plugin.values[2]=1;
|
|
|
|
|
assert(xtc_project_set_chain(p,track,&c)==XTC_OK && xtc_audio_plan_create(p,8000,&audio)==XTC_OK);
|
|
|
|
|
assert(xtc_audio_plan_set_fade_frames(audio,0)==XTC_OK && xtc_audio_plan_render_stereo(audio,0,out,right,32)==XTC_OK);
|
|
|
|
|
for(size_t j=0;j<32;++j)assert(fabsf(out[j]-(j==8 ? 1.f : 0.f))<.00001f && right[j]==out[j]);
|
|
|
|
|
/* Discontinuity resets internal delay, and block boundaries preserve history. */
|
|
|
|
|
assert(xtc_audio_plan_render(audio,0,out,4)==XTC_OK && out[0]==0);
|
|
|
|
|
assert(xtc_audio_plan_render(audio,4,out,12)==XTC_OK && fabsf(out[4]-1)<.00001f);
|
|
|
|
|
xtc_audio_plan_destroy(audio);audio=NULL;
|
|
|
|
|
c.slots[0].enabled=0;assert(xtc_project_set_chain(p,track,&c)==XTC_OK && xtc_audio_plan_create(p,8000,&audio)==XTC_OK);
|
|
|
|
|
assert(xtc_audio_plan_set_fade_frames(audio,0)==XTC_OK && xtc_audio_plan_render(audio,0,out,32)==XTC_OK && out[0]==1 && out[8]==0);
|
|
|
|
|
xtc_audio_plan_destroy(audio);audio=NULL;
|
2026-11-06 01:41:23 +01:00
|
|
|
c.slots[0].enabled=1;c.slots[0].wet=.25f;
|
|
|
|
|
assert(xtc_project_set_chain(p,track,&c)==XTC_OK && xtc_audio_plan_create(p,8000,&audio)==XTC_OK);
|
|
|
|
|
assert(xtc_audio_plan_set_fade_frames(audio,0)==XTC_OK && xtc_audio_plan_render(audio,0,out,32)==XTC_OK);
|
|
|
|
|
assert(fabsf(out[0]-.75f)<.00001f && fabsf(out[8]-.25f)<.00001f);xtc_audio_plan_destroy(audio);audio=NULL;
|
|
|
|
|
c.slots[0].wet=0;assert(xtc_project_set_chain(p,track,&c)==XTC_OK && xtc_audio_plan_create(p,8000,&audio)==XTC_OK);
|
|
|
|
|
assert(xtc_audio_plan_set_fade_frames(audio,0)==XTC_OK && xtc_audio_plan_render(audio,0,out,32)==XTC_OK && out[0]==1 && out[8]==0);xtc_audio_plan_destroy(audio);audio=NULL;
|
2026-11-06 01:35:53 +01:00
|
|
|
/* Reject split ownership of a nonlinear chain on a hybrid track. */
|
|
|
|
|
assert(xtc_project_add_midi_clip(p,track,0,960,&clip)==XTC_OK);
|
|
|
|
|
assert(xtc_audio_plan_create(p,8000,&audio)==XTC_ERR_UNSUPPORTED && audio==NULL);
|
|
|
|
|
xtc_project_destroy(p);p=NULL;
|
|
|
|
|
assert(xtc_project_create(&p)==XTC_OK && xtc_project_add_track(p,"MIDI",&track)==XTC_OK && xtc_project_add_midi_clip(p,track,0,960,&clip)==XTC_OK);
|
|
|
|
|
assert(xtc_project_add_note(p,clip,0,960,69,127,0,¬e)==XTC_OK);
|
2026-11-06 01:41:23 +01:00
|
|
|
c.slots[0].enabled=1;c.slots[0].wet=1;assert(xtc_project_set_chain(p,track,&c)==XTC_OK && xtc_midi_plan_create(p,8000,&midi)==XTC_OK);
|
2026-11-06 01:35:53 +01:00
|
|
|
assert(xtc_midi_plan_set_fade_frames(midi,0)==XTC_OK && xtc_midi_plan_render(midi,0,NULL,0,out,32)==XTC_OK);
|
|
|
|
|
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);
|
|
|
|
|
}
|
2026-11-06 01:50:25 +01:00
|
|
|
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,¬e)==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);
|
|
|
|
|
}
|
2026-11-06 01:34:08 +01:00
|
|
|
int main(void)
|
|
|
|
|
{
|
2026-11-06 01:50:25 +01:00
|
|
|
rendering();host_vst3();
|
2026-11-06 01:34:08 +01:00
|
|
|
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);
|
|
|
|
|
assert(xtc_project_get_chain(p,a,&got)==XTC_OK && got.count==0 && got.output_gain==1);
|
|
|
|
|
assert(xtc_plugin_probe_format(XTC_PLUGIN_BUILTIN,XTC_BUILTIN_PATH,2,48000,&c.slots[0].plugin)==XTC_OK);
|
|
|
|
|
c.count=1;c.output_gain=.75f;c.slots[0].enabled=1;c.slots[0].wet=.5f;
|
|
|
|
|
assert(xtc_project_set_chain(p,a,&c)==XTC_OK);
|
|
|
|
|
assert(xtc_project_save(p,file)==XTC_OK && xtc_project_create(&q)==XTC_OK && xtc_project_load(q,file)==XTC_OK);
|
|
|
|
|
assert(xtc_project_get_chain(q,a,&got)==XTC_OK && got.count==1 && got.output_gain==.75f && got.slots[0].wet==.5f && !strcmp(got.slots[0].plugin.label,c.slots[0].plugin.label));
|
|
|
|
|
assert(xtc_project_duplicate_track(q,a,"Copy",&duplicate)==XTC_OK && xtc_project_get_chain(q,duplicate,&got)==XTC_OK && got.count==1);
|
|
|
|
|
assert(xtc_project_move_track(q,a,2)==XTC_OK && xtc_project_remove_track(q,b)==XTC_OK && xtc_project_get_chain(q,a,&got)==XTC_OK && got.count==1);
|
|
|
|
|
c.output_gain=NAN;assert(xtc_project_set_chain(q,a,&c)==XTC_ERR_INVALID_ARGUMENT);
|
|
|
|
|
assert(xtc_project_get_chain(q,a,&got)==XTC_OK && got.output_gain==.75f);
|
|
|
|
|
c.output_gain=1;c.slots[0].wet=2;assert(xtc_project_set_chain(q,a,&c)==XTC_ERR_INVALID_ARGUMENT);
|
|
|
|
|
c.slots[0].wet=1;c.count=XTC_TRACK_INSERTS+1;assert(xtc_track_chain_validate(&c)==XTC_ERR_INVALID_ARGUMENT);
|
2026-11-06 01:40:13 +01:00
|
|
|
assert(xtc_track_chain_preset_save(&got,file)==XTC_OK);
|
|
|
|
|
memset(&c,0,sizeof(c));assert(xtc_track_chain_preset_load(file,&c)==XTC_OK && c.count==1 && c.output_gain==.75f);
|
|
|
|
|
{FILE *broken=fopen(file,"w");assert(broken);fputs("bad preset\n",broken);fclose(broken);}
|
|
|
|
|
assert(xtc_track_chain_preset_load(file,&c)==XTC_ERR_FORMAT && c.count==1 && c.output_gain==.75f);
|
2026-11-06 01:34:08 +01:00
|
|
|
xtc_project_destroy(p);xtc_project_destroy(q);unlink(file);puts("Track insert metadata/persistence passed.");return 0;
|
|
|
|
|
}
|