#include "xtc/xtc_plugin.h" #include "xtc/xtc_dsp.h" #include "xtc/xtc_project.h" #include #include #include #include #include /* Header-only optional dependency; absent development header gives explicit * unsupported stubs, preserving metadata-only load/save of plugin documents. */ #include "config.h" static int terminated(const char *s, size_t n) { return memchr(s, 0, n) != NULL; } xtc_result xtc_plugin_spec_validate(const xtc_plugin_spec *s) { size_t i; if (!s || !terminated(s->path,sizeof(s->path)) || s->path[0] != '/' || !terminated(s->label,sizeof(s->label)) || !s->label[0] || s->index >= 1024 || s->inputs > 1 || s->control_count > XTC_PLUGIN_CONTROLS || s->tempo < 20000 || s->tempo > 300000 || s->point_count > XTC_PLUGIN_POINTS) return XTC_ERR_INVALID_ARGUMENT; for (i = 0; i < s->control_count; ++i) if (s->ports[i] >= 64 || (i && s->ports[i] <= s->ports[i-1]) || !isfinite(s->values[i])) return XTC_ERR_INVALID_ARGUMENT; for (i = 0; i < s->point_count; ++i) { size_t j; const xtc_plugin_point *a = s->points+i; if (a->tick > XTC_PROJECT_MAX_TICK || a->step > 1 || !isfinite(a->value)) return XTC_ERR_INVALID_ARGUMENT; for (j = 0; j < s->control_count && s->ports[j] != a->port; ++j) {} if (j == s->control_count) return XTC_ERR_INVALID_ARGUMENT; for (j = 0; j < i; ++j) if (s->points[j].port == a->port && s->points[j].tick == a->tick) return XTC_ERR_INVALID_ARGUMENT; } return XTC_OK; } #ifdef XTC_HAVE_LADSPA #include #include #include struct xtc_plugin { void *module; const LADSPA_Descriptor *descriptor; LADSPA_Handle handle; float *input, *output; float controls[64]; size_t frames; int activated; xtc_plugin_point points[XTC_PLUGIN_POINTS]; uint64_t samples[XTC_PLUGIN_POINTS], next; unsigned int point_count; }; static xtc_result load(const char *path, unsigned int index, void **module, const LADSPA_Descriptor **descriptor) { LADSPA_Descriptor_Function function; void *symbol; if (!path || path[0] != '/' || strlen(path) >= 512 || index >= 1024) return XTC_ERR_INVALID_ARGUMENT; *module = dlopen(path,RTLD_NOW | RTLD_LOCAL); if (!*module) return XTC_ERR_NOT_FOUND; symbol = dlsym(*module,"ladspa_descriptor"); if (!symbol || sizeof(function) != sizeof(symbol)) { dlclose(*module); *module = NULL; return XTC_ERR_UNSUPPORTED; } memcpy(&function,&symbol,sizeof(function)); *descriptor = function(index); if (!*descriptor) { dlclose(*module); *module = NULL; return XTC_ERR_NOT_FOUND; } return XTC_OK; } static int bounds(const LADSPA_PortRangeHint *h, double rate, float value) { double scale = LADSPA_IS_HINT_SAMPLE_RATE(h->HintDescriptor) ? rate : 1; if (!isfinite(value) || (LADSPA_IS_HINT_BOUNDED_BELOW(h->HintDescriptor) && value < h->LowerBound * scale) || (LADSPA_IS_HINT_BOUNDED_ABOVE(h->HintDescriptor) && value > h->UpperBound * scale) || (LADSPA_IS_HINT_INTEGER(h->HintDescriptor) && value != floorf(value)) || (LADSPA_IS_HINT_TOGGLED(h->HintDescriptor) && value != 0 && value != 1)) return 0; return 1; } static float initial(const LADSPA_PortRangeHint *h, double rate) { unsigned long hint = h->HintDescriptor & LADSPA_HINT_DEFAULT_MASK; double scale = LADSPA_IS_HINT_SAMPLE_RATE(h->HintDescriptor) ? rate : 1; double lo = LADSPA_IS_HINT_BOUNDED_BELOW(h->HintDescriptor) ? h->LowerBound * scale : 0; double hi = LADSPA_IS_HINT_BOUNDED_ABOVE(h->HintDescriptor) ? h->UpperBound * scale : 0; double v = 0, fraction = .5; if (hint == LADSPA_HINT_DEFAULT_1) v = 1; else if (hint == LADSPA_HINT_DEFAULT_100) v = 100; else if (hint == LADSPA_HINT_DEFAULT_440) v = 440; else if (hint == LADSPA_HINT_DEFAULT_MINIMUM) v = lo; else if (hint == LADSPA_HINT_DEFAULT_MAXIMUM) v = hi; else if (hint == LADSPA_HINT_DEFAULT_LOW || hint == LADSPA_HINT_DEFAULT_MIDDLE || hint == LADSPA_HINT_DEFAULT_HIGH) { if (hint == LADSPA_HINT_DEFAULT_LOW) fraction = .25; if (hint == LADSPA_HINT_DEFAULT_HIGH) fraction = .75; v = LADSPA_IS_HINT_LOGARITHMIC(h->HintDescriptor) && lo > 0 && hi > 0 ? exp(log(lo)*(1-fraction)+log(hi)*fraction) : lo*(1-fraction)+hi*fraction; } if (LADSPA_IS_HINT_BOUNDED_BELOW(h->HintDescriptor) && v < lo) v = lo; if (LADSPA_IS_HINT_BOUNDED_ABOVE(h->HintDescriptor) && v > hi) v = hi; if (LADSPA_IS_HINT_INTEGER(h->HintDescriptor)) v = round(v); if (LADSPA_IS_HINT_TOGGLED(h->HintDescriptor)) v = v >= .5 ? 1 : 0; return (float)v; } static xtc_result inspect(const LADSPA_Descriptor *d, double rate, xtc_plugin_spec *s) { unsigned long i; unsigned outputs = 0; if (!d->Label || !d->Label[0] || strlen(d->Label) >= sizeof(s->label) || d->UniqueID > UINT32_MAX || !d->PortDescriptors || !d->PortRangeHints || !d->PortCount || d->PortCount > 64 || !d->instantiate || !d->connect_port || !d->run || !d->cleanup) return XTC_ERR_UNSUPPORTED; s->tempo = 120000; s->unique_id = (unsigned int)d->UniqueID; strcpy(s->label,d->Label); for (i = 0; i < d->PortCount; ++i) { LADSPA_PortDescriptor p = d->PortDescriptors[i]; if (!!LADSPA_IS_PORT_INPUT(p) == !!LADSPA_IS_PORT_OUTPUT(p) || !!LADSPA_IS_PORT_AUDIO(p) == !!LADSPA_IS_PORT_CONTROL(p)) return XTC_ERR_UNSUPPORTED; if (LADSPA_IS_PORT_AUDIO(p)) { if (LADSPA_IS_PORT_INPUT(p)) ++s->inputs; else ++outputs; } else if (LADSPA_IS_PORT_INPUT(p)) { size_t n = s->control_count; if (n == XTC_PLUGIN_CONTROLS) return XTC_ERR_UNSUPPORTED; { const LADSPA_PortRangeHint *h = d->PortRangeHints+i; if ((LADSPA_IS_HINT_BOUNDED_BELOW(h->HintDescriptor) && !isfinite(h->LowerBound)) || (LADSPA_IS_HINT_BOUNDED_ABOVE(h->HintDescriptor) && !isfinite(h->UpperBound)) || (LADSPA_IS_HINT_BOUNDED_BELOW(h->HintDescriptor) && LADSPA_IS_HINT_BOUNDED_ABOVE(h->HintDescriptor) && h->LowerBound > h->UpperBound)) return XTC_ERR_UNSUPPORTED; } s->ports[n] = (unsigned int)i; s->values[n] = initial(d->PortRangeHints+i,rate); if (!bounds(d->PortRangeHints+i,rate,s->values[n])) return XTC_ERR_UNSUPPORTED; ++s->control_count; } } return outputs == 1 && s->inputs <= 1 ? XTC_OK : XTC_ERR_UNSUPPORTED; } xtc_result xtc_plugin_probe(const char *path, unsigned int index, double rate, xtc_plugin_spec *out) { void *module = NULL; const LADSPA_Descriptor *d; xtc_plugin_spec s = {0}; xtc_result r; if (!out || !isfinite(rate) || rate < 1 || rate > 768000) return XTC_ERR_INVALID_ARGUMENT; r = load(path,index,&module,&d); if (r != XTC_OK) return r; r = inspect(d,rate,&s); if (r == XTC_OK) { strcpy(s.path,path); s.index = index; *out = s; } dlclose(module); return r; } xtc_result xtc_plugin_parameters(const xtc_plugin_spec *s, double rate, xtc_parameter_info out[XTC_PLUGIN_CONTROLS]) { void *module = NULL; const LADSPA_Descriptor *d; xtc_plugin_spec actual = {0}; xtc_result r; size_t i; if (!out || xtc_plugin_spec_validate(s) != XTC_OK || !isfinite(rate) || rate < 1 || rate > 768000) return XTC_ERR_INVALID_ARGUMENT; r = load(s->path,s->index,&module,&d); if (r != XTC_OK) return r; r = inspect(d,rate,&actual); if (r == XTC_OK && (actual.unique_id != s->unique_id || strcmp(actual.label,s->label) || actual.control_count != s->control_count || actual.inputs != s->inputs || memcmp(actual.ports,s->ports,s->control_count*sizeof(*s->ports)))) r = XTC_ERR_UNSUPPORTED; if (r == XTC_OK) for (i = 0; i < s->control_count; ++i) { const LADSPA_PortRangeHint *h = d->PortRangeHints+s->ports[i]; double scale = LADSPA_IS_HINT_SAMPLE_RATE(h->HintDescriptor) ? rate : 1; xtc_parameter_info p = {0}; p.id = s->ports[i]; snprintf(p.name,sizeof(p.name),"%s",d->PortNames && d->PortNames[p.id] ? d->PortNames[p.id] : "Control"); p.minimum = LADSPA_IS_HINT_BOUNDED_BELOW(h->HintDescriptor) ? (float)(h->LowerBound*scale) : -FLT_MAX; p.maximum = LADSPA_IS_HINT_BOUNDED_ABOVE(h->HintDescriptor) ? (float)(h->UpperBound*scale) : FLT_MAX; p.default_value = actual.values[i]; out[i] = p; } dlclose(module); return r; } void xtc_plugin_destroy(xtc_plugin *p) { if (!p) return; if (p->handle) { if (p->activated && p->descriptor->deactivate) p->descriptor->deactivate(p->handle); p->descriptor->cleanup(p->handle); } if (p->module) dlclose(p->module); free(p->input); free(p->output); free(p); } xtc_result xtc_plugin_create(const xtc_plugin_spec *s, double rate, size_t frames, xtc_plugin **out) { xtc_plugin *p; xtc_plugin_spec actual = {0}; xtc_result r; unsigned long i; size_t c; if (!out) return XTC_ERR_INVALID_ARGUMENT; *out = NULL; r = xtc_plugin_spec_validate(s); if (r != XTC_OK) return r; if (!isfinite(rate) || rate < 1 || rate > 768000 || !frames || frames > 65536) return XTC_ERR_INVALID_ARGUMENT; p = calloc(1,sizeof(*p)); if (!p) return XTC_ERR_OUT_OF_MEMORY; r = load(s->path,s->index,&p->module,&p->descriptor); if (r != XTC_OK) goto fail; r = inspect(p->descriptor,rate,&actual); if (r != XTC_OK) goto fail; r = XTC_ERR_UNSUPPORTED; if (strcmp(actual.label,s->label) || actual.unique_id != s->unique_id || actual.inputs != s->inputs || actual.control_count != s->control_count) goto fail; for (c = 0; c < s->control_count; ++c) { if (s->ports[c] != actual.ports[c] || !bounds(p->descriptor->PortRangeHints+s->ports[c],rate,s->values[c])) goto fail; p->controls[s->ports[c]] = s->values[c]; } for (c = 0; c < s->point_count; ++c) { const xtc_plugin_point *a = s->points+c; const LADSPA_PortRangeHint *h = p->descriptor->PortRangeHints+a->port; if (!bounds(h,rate,a->value) || (!a->step && (LADSPA_IS_HINT_INTEGER(h->HintDescriptor) || LADSPA_IS_HINT_TOGGLED(h->HintDescriptor)))) goto fail; p->points[c] = *a; p->samples[c] = a->tick * (uint64_t)rate * UINT64_C(60000) / ((uint64_t)s->tempo * XTC_TICKS_PER_QUARTER); } p->point_count = s->point_count; p->frames = frames; p->input = calloc(frames,sizeof(float)); p->output = calloc(frames,sizeof(float)); r = XTC_ERR_OUT_OF_MEMORY; if (!p->input || !p->output) goto fail; p->handle = p->descriptor->instantiate(p->descriptor,(unsigned long)rate); r = XTC_ERR_INVALID_STATE; if (!p->handle) goto fail; for (i = 0; i < p->descriptor->PortCount; ++i) { LADSPA_PortDescriptor port = p->descriptor->PortDescriptors[i]; p->descriptor->connect_port(p->handle,i,LADSPA_IS_PORT_CONTROL(port) ? p->controls+i : LADSPA_IS_PORT_INPUT(port) ? p->input : p->output); } if (p->descriptor->activate) p->descriptor->activate(p->handle); p->activated = 1; *out = p; return XTC_OK; fail: xtc_plugin_destroy(p); return r; } static void automate(xtc_plugin *p, uint64_t position) { unsigned long port; for (port = 0; port < p->descriptor->PortCount; ++port) { size_t i, before = SIZE_MAX, after = SIZE_MAX; for (i = 0; i < p->point_count; ++i) if (p->points[i].port == port) { if (p->samples[i] <= position) { if (before == SIZE_MAX || p->points[i].tick > p->points[before].tick) before = i; } else if (after == SIZE_MAX || p->points[i].tick < p->points[after].tick) after = i; } if (before == SIZE_MAX) { if (after != SIZE_MAX) p->controls[port] = p->points[after].value; } else { double value = p->points[before].value; if (after != SIZE_MAX && !p->points[before].step) value += (double)(position-p->samples[before]) / (double)(p->samples[after]-p->samples[before]) * ((double)p->points[after].value-p->points[before].value); p->controls[port] = (float)value; } } } xtc_result xtc_plugin_process_at(xtc_plugin *p, const float *input, float *output, size_t frames, uint64_t position) { size_t i; if (!p || !output || frames > p->frames || position > UINT64_MAX-frames) return XTC_ERR_INVALID_ARGUMENT; if (!frames) return XTC_OK; if (!p->point_count) { for (i = 0; i < frames; ++i) p->input[i] = input ? xtc_dsp_finite(input[i]) : 0; memset(p->output,0,frames*sizeof(float)); p->descriptor->run(p->handle,(unsigned long)frames); for (i = 0; i < frames; ++i) output[i] = xtc_dsp_finite(p->output[i]); } else { /* Buffers stay connected: run(1) reads/writes slot zero, one control * evaluation per absolute sample. Exact aliasing is safe. */ for (i = 0; i < frames; ++i) { automate(p,position+i); p->input[0] = input ? xtc_dsp_finite(input[i]) : 0; p->output[0] = 0; p->descriptor->run(p->handle,1); output[i] = xtc_dsp_finite(p->output[0]); } } p->next = position+frames; return XTC_OK; } xtc_result xtc_plugin_process(xtc_plugin *p, const float *input, float *output, size_t frames) { return xtc_plugin_process_at(p,input,output,frames,p ? p->next : 0); } #else xtc_result xtc_plugin_parameters(const xtc_plugin_spec *s, double r, xtc_parameter_info out[XTC_PLUGIN_CONTROLS]) { (void)s; (void)r; (void)out; return XTC_ERR_UNSUPPORTED; } xtc_result xtc_plugin_probe(const char *p, unsigned int i, double r, xtc_plugin_spec *s) { (void)p; (void)i; (void)r; (void)s; return XTC_ERR_UNSUPPORTED; } xtc_result xtc_plugin_create(const xtc_plugin_spec *s, double r, size_t f, xtc_plugin **out) { (void)s; (void)r; (void)f; if (!out) return XTC_ERR_INVALID_ARGUMENT; *out = NULL; return XTC_ERR_UNSUPPORTED; } void xtc_plugin_destroy(xtc_plugin *p) { (void)p; } xtc_result xtc_plugin_process_at(xtc_plugin *p, const float *i, float *o, size_t f, uint64_t pos) { (void)p; (void)i; (void)o; (void)f; (void)pos; return XTC_ERR_UNSUPPORTED; } xtc_result xtc_plugin_process(xtc_plugin *p, const float *i, float *o, size_t f) { (void)p; (void)i; (void)o; (void)f; return XTC_ERR_UNSUPPORTED; } #endif