2026-10-27 17:34:46 +01:00
|
|
|
#include "modern.h"
|
2026-10-28 17:16:01 +01:00
|
|
|
#include "builtin.h"
|
2026-10-27 17:34:46 +01:00
|
|
|
#include <dlfcn.h>
|
|
|
|
|
#include <math.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
typedef struct xtc_ladspa_plugin xtc_ladspa_plugin;
|
|
|
|
|
xtc_result xtc_ladspa_probe(const char *, unsigned int, double, xtc_plugin_spec *);
|
|
|
|
|
xtc_result xtc_ladspa_describe(const xtc_plugin_spec *, double, xtc_plugin_details *);
|
|
|
|
|
xtc_result xtc_ladspa_create(const xtc_plugin_spec *, double, size_t, xtc_ladspa_plugin **);
|
|
|
|
|
void xtc_ladspa_destroy(xtc_ladspa_plugin *);
|
|
|
|
|
xtc_result xtc_ladspa_process_at(xtc_ladspa_plugin *, const float *, float *, size_t, uint64_t);
|
2026-10-28 17:16:01 +01:00
|
|
|
struct xtc_plugin { xtc_ladspa_plugin *ladspa; xtc_modern *modern; xtc_builtin *builtin; uint64_t next; };
|
2026-10-27 17:34:46 +01:00
|
|
|
const char *xtc_plugin_format_name(xtc_plugin_format f)
|
2026-10-28 17:16:01 +01:00
|
|
|
{ static const char *names[] = {"LADSPA", "CLAP", "VST2", "VST3", "Internal"}; return f >= 0 && f <= XTC_PLUGIN_BUILTIN ? names[f] : "Unknown"; }
|
2026-10-27 17:34:46 +01:00
|
|
|
xtc_result xtc_plugin_probe_format(xtc_plugin_format f, const char *path, unsigned int index, double rate, xtc_plugin_spec *out)
|
|
|
|
|
{
|
|
|
|
|
xtc_modern *p = NULL; xtc_result r;
|
|
|
|
|
if (!out || !path || path[0] != '/' || strlen(path) >= 512 || index >= 1024 || !isfinite(rate) || rate < 1 || rate > 768000)
|
|
|
|
|
return XTC_ERR_INVALID_ARGUMENT;
|
2026-10-28 17:16:01 +01:00
|
|
|
if (f == XTC_PLUGIN_BUILTIN) return !strcmp(path,XTC_BUILTIN_PATH) ? xtc_builtin_probe(index,out) : XTC_ERR_INVALID_ARGUMENT;
|
2026-10-27 17:34:46 +01:00
|
|
|
if (f == XTC_PLUGIN_LADSPA) return xtc_ladspa_probe(path,index,rate,out);
|
|
|
|
|
if (f < XTC_PLUGIN_CLAP || f > XTC_PLUGIN_VST3) return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
r = xtc_modern_open(f,path,index,rate,256,&p,out);
|
|
|
|
|
xtc_modern_destroy(p); return r;
|
|
|
|
|
}
|
|
|
|
|
xtc_result xtc_plugin_probe(const char *path, unsigned int index, double rate, xtc_plugin_spec *out)
|
|
|
|
|
{
|
|
|
|
|
void *module; xtc_plugin_format f = XTC_PLUGIN_LADSPA;
|
|
|
|
|
if (!path || path[0] != '/' || strlen(path) >= 512) return XTC_ERR_INVALID_ARGUMENT;
|
2026-10-28 17:16:01 +01:00
|
|
|
if (!strcmp(path,XTC_BUILTIN_PATH)) return xtc_plugin_probe_format(XTC_PLUGIN_BUILTIN,path,index,rate,out);
|
2026-10-27 17:34:46 +01:00
|
|
|
module = dlopen(path,RTLD_NOW | RTLD_LOCAL);
|
|
|
|
|
if (!module) return XTC_ERR_NOT_FOUND;
|
|
|
|
|
if (dlsym(module,"clap_entry")) f = XTC_PLUGIN_CLAP;
|
|
|
|
|
else if (dlsym(module,"VSTPluginMain")) f = XTC_PLUGIN_VST2;
|
|
|
|
|
else if (dlsym(module,"GetPluginFactory")) f = XTC_PLUGIN_VST3;
|
|
|
|
|
dlclose(module);
|
|
|
|
|
return xtc_plugin_probe_format(f,path,index,rate,out);
|
|
|
|
|
}
|
|
|
|
|
xtc_result xtc_plugin_describe(const xtc_plugin_spec *s, double rate, xtc_plugin_details *out)
|
|
|
|
|
{
|
|
|
|
|
xtc_plugin_spec actual; xtc_result r;
|
|
|
|
|
if (!out || xtc_plugin_spec_validate(s) != XTC_OK) return XTC_ERR_INVALID_ARGUMENT;
|
2026-10-28 17:16:01 +01:00
|
|
|
if (!isfinite(rate) || rate < 1 || rate > 768000) return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
if (s->format == XTC_PLUGIN_BUILTIN) return xtc_builtin_describe(s,out);
|
2026-10-27 17:34:46 +01:00
|
|
|
if (s->format == XTC_PLUGIN_LADSPA) return xtc_ladspa_describe(s,rate,out);
|
|
|
|
|
r = xtc_plugin_probe_format(s->format,s->path,s->index,rate,&actual);
|
|
|
|
|
if (r != XTC_OK) return r;
|
2026-10-27 17:51:48 +01:00
|
|
|
if (actual.instrument != s->instrument || actual.unique_id != s->unique_id || strcmp(actual.identifier,s->identifier) || strcmp(actual.label,s->label)) return XTC_ERR_UNSUPPORTED;
|
2026-10-27 17:34:46 +01:00
|
|
|
memset(out,0,sizeof(*out)); snprintf(out->name,sizeof(out->name),"%s",s->label);
|
|
|
|
|
snprintf(out->maker,sizeof(out->maker),"Not exposed by this adapter");
|
|
|
|
|
snprintf(out->copyright,sizeof(out->copyright),"See plugin license");
|
|
|
|
|
return XTC_OK;
|
|
|
|
|
}
|
2026-11-05 23:55:56 +01:00
|
|
|
void xtc_plugin_reset_history(xtc_plugin *p) {if(p){xtc_builtin_reset(p->builtin);p->next=0;}}
|
2026-11-05 23:50:14 +01:00
|
|
|
size_t xtc_plugin_sample_frames(const xtc_plugin *p) {return p ? xtc_builtin_sample_frames(p->builtin) : 0;}
|
|
|
|
|
size_t xtc_plugin_asset_issues(const xtc_plugin *p) {return p ? xtc_builtin_asset_issues(p->builtin) : 0;}
|
2026-10-27 17:34:46 +01:00
|
|
|
void xtc_plugin_destroy(xtc_plugin *p)
|
2026-10-28 17:16:01 +01:00
|
|
|
{ if (p) { xtc_builtin_destroy(p->builtin); xtc_ladspa_destroy(p->ladspa); xtc_modern_destroy(p->modern); free(p); } }
|
2026-10-27 17:34:46 +01:00
|
|
|
xtc_result xtc_plugin_create(const xtc_plugin_spec *s, double rate, size_t frames, xtc_plugin **out)
|
|
|
|
|
{
|
|
|
|
|
xtc_plugin *p; xtc_result r; xtc_plugin_spec actual;
|
|
|
|
|
if (!out) return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
*out = NULL;
|
|
|
|
|
r = xtc_plugin_spec_validate(s); if (r != XTC_OK) return r;
|
|
|
|
|
if (!frames || frames > 65536 || !isfinite(rate) || rate < 1 || rate > 768000) return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
p = calloc(1,sizeof(*p)); if (!p) return XTC_ERR_OUT_OF_MEMORY;
|
2026-10-28 17:16:01 +01:00
|
|
|
if (s->format == XTC_PLUGIN_BUILTIN) r = xtc_builtin_create(s,rate,frames,&p->builtin);
|
|
|
|
|
else if (s->format == XTC_PLUGIN_LADSPA) r = xtc_ladspa_create(s,rate,frames,&p->ladspa);
|
2026-10-27 17:34:46 +01:00
|
|
|
else {
|
|
|
|
|
r = xtc_modern_open(s->format,s->path,s->index,rate,frames,&p->modern,&actual);
|
2026-10-27 17:51:48 +01:00
|
|
|
if (r == XTC_OK && (actual.instrument != s->instrument || actual.unique_id != s->unique_id || actual.inputs != s->inputs ||
|
2026-10-27 17:34:46 +01:00
|
|
|
strcmp(actual.identifier,s->identifier) || strcmp(actual.label,s->label))) r = XTC_ERR_UNSUPPORTED;
|
|
|
|
|
}
|
|
|
|
|
if (r != XTC_OK) { xtc_plugin_destroy(p); return r; }
|
|
|
|
|
*out = p; return XTC_OK;
|
|
|
|
|
}
|
2026-11-05 23:53:03 +01:00
|
|
|
xtc_result xtc_plugin_process_stereo_at(xtc_plugin *p,const float *left,const float *right,float *out,float *out_right,size_t frames,uint64_t position)
|
|
|
|
|
{
|
|
|
|
|
xtc_result r;if(!p || !p->builtin)return XTC_ERR_UNSUPPORTED;
|
|
|
|
|
r=xtc_builtin_stereo(p->builtin,left,right,out,out_right,frames,position);
|
|
|
|
|
if(r==XTC_OK)p->next=position+frames;
|
|
|
|
|
return r;
|
|
|
|
|
}
|
2026-10-27 17:34:46 +01:00
|
|
|
xtc_result xtc_plugin_process_at(xtc_plugin *p, const float *in, float *out, size_t frames, uint64_t position)
|
|
|
|
|
{
|
|
|
|
|
xtc_result r;
|
|
|
|
|
if (!p) return XTC_ERR_INVALID_ARGUMENT;
|
2026-10-28 17:16:01 +01:00
|
|
|
r = p->builtin ? xtc_builtin_process(p->builtin,in,out,frames,position) : p->modern ? xtc_modern_process(p->modern,in,out,frames) : xtc_ladspa_process_at(p->ladspa,in,out,frames,position);
|
2026-10-27 17:34:46 +01:00
|
|
|
if (r == XTC_OK) p->next = position <= UINT64_MAX-frames ? position+frames : 0;
|
|
|
|
|
return r;
|
|
|
|
|
}
|
2026-11-05 23:47:54 +01:00
|
|
|
xtc_result xtc_plugin_process_midi_at(xtc_plugin *p, const xtc_midi_event *events, size_t count, float *out, size_t frames, uint64_t position)
|
|
|
|
|
{
|
|
|
|
|
xtc_result r;
|
|
|
|
|
if (!p || position>UINT64_MAX-frames) return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
r=p->builtin ? xtc_builtin_midi(p->builtin,events,count,out,frames,position) : p->modern ? xtc_modern_process_midi(p->modern,events,count,out,frames) : XTC_ERR_UNSUPPORTED;
|
|
|
|
|
if(r==XTC_OK)p->next=position+frames;
|
|
|
|
|
return r;
|
|
|
|
|
}
|
2026-10-27 17:51:48 +01:00
|
|
|
xtc_result xtc_plugin_process_midi(xtc_plugin *p, const xtc_midi_event *events, size_t count, float *out, size_t frames)
|
2026-11-05 23:47:54 +01:00
|
|
|
{ return xtc_plugin_process_midi_at(p,events,count,out,frames,p ? p->next : 0); }
|
2026-10-27 17:34:46 +01:00
|
|
|
xtc_result xtc_plugin_process(xtc_plugin *p, const float *in, float *out, size_t frames)
|
|
|
|
|
{ return xtc_plugin_process_at(p,in,out,frames,p ? p->next : 0); }
|
|
|
|
|
xtc_result xtc_plugin_parameters(const xtc_plugin_spec *s, double rate, xtc_parameter_info out[XTC_PLUGIN_CONTROLS])
|
|
|
|
|
{
|
|
|
|
|
xtc_plugin_details details;
|
|
|
|
|
xtc_result r;
|
|
|
|
|
if (!out) return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
r = xtc_plugin_describe(s,rate,&details);
|
|
|
|
|
if (r == XTC_OK) memcpy(out,details.parameters,sizeof(details.parameters));
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
xtc_result xtc_plugin_reset(xtc_plugin_spec *s, double rate, unsigned int port)
|
|
|
|
|
{
|
|
|
|
|
xtc_plugin_details details;
|
|
|
|
|
xtc_plugin_spec candidate;
|
|
|
|
|
size_t i, count = 0;
|
|
|
|
|
int found = port == UINT32_MAX;
|
|
|
|
|
xtc_result r = xtc_plugin_describe(s,rate,&details);
|
|
|
|
|
if (r != XTC_OK) return r;
|
|
|
|
|
candidate = *s;
|
|
|
|
|
for (i = 0; i < s->control_count; ++i) if (port == UINT32_MAX || s->ports[i] == port) {
|
|
|
|
|
candidate.values[i] = details.parameters[i].default_value;
|
|
|
|
|
found = 1;
|
|
|
|
|
}
|
|
|
|
|
if (!found) return XTC_ERR_NOT_FOUND;
|
|
|
|
|
for (i = 0; i < s->point_count; ++i)
|
|
|
|
|
if (port != UINT32_MAX && s->points[i].port != port) candidate.points[count++] = s->points[i];
|
|
|
|
|
candidate.point_count = (unsigned int)count;
|
|
|
|
|
*s = candidate;
|
|
|
|
|
return XTC_OK;
|
|
|
|
|
}
|
2026-11-05 23:47:54 +01:00
|
|
|
|
|
|
|
|
xtc_result xtc_plugin_preset_save(const xtc_plugin_spec *spec, const char *path)
|
|
|
|
|
{
|
|
|
|
|
xtc_project *project=NULL;uint64_t track;xtc_result r;
|
|
|
|
|
if(!path || xtc_plugin_spec_validate(spec)!=XTC_OK || !spec->instrument)return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
r=xtc_project_create(&project);
|
|
|
|
|
if(r==XTC_OK)r=xtc_project_set_tempo(project,spec->tempo);
|
|
|
|
|
if(r==XTC_OK)r=xtc_project_add_track(project,"Instrument preset",&track);
|
|
|
|
|
if(r==XTC_OK)r=xtc_project_set_instrument(project,track,spec);
|
|
|
|
|
if(r==XTC_OK)r=xtc_project_save(project,path);
|
|
|
|
|
xtc_project_destroy(project);return r;
|
|
|
|
|
}
|
|
|
|
|
xtc_result xtc_plugin_preset_load(const char *path, xtc_plugin_spec *out)
|
|
|
|
|
{
|
|
|
|
|
xtc_project *project=NULL;xtc_track_info track;xtc_plugin_spec spec;size_t tracks,clips;xtc_result r;
|
|
|
|
|
if(!path || !out)return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
r=xtc_project_create(&project);
|
|
|
|
|
if(r==XTC_OK)r=xtc_project_load(project,path);
|
|
|
|
|
if(r==XTC_OK)r=xtc_project_get_counts(project,&tracks,&clips);
|
|
|
|
|
if(r==XTC_OK && (tracks!=1 || clips))r=XTC_ERR_FORMAT;
|
|
|
|
|
if(r==XTC_OK)r=xtc_project_get_track(project,0,&track);
|
|
|
|
|
if(r==XTC_OK)r=xtc_project_get_instrument(project,track.id,&spec);
|
|
|
|
|
if(r==XTC_OK)*out=spec;
|
|
|
|
|
xtc_project_destroy(project);return r;
|
|
|
|
|
}
|