xtc/src/plugin/ladspa.c

268 lines
14 KiB
C

#include "xtc/xtc_plugin.h"
#include "xtc/xtc_dsp.h"
#include "xtc/xtc_project.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <float.h>
#include <stdio.h>
/* 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 <ladspa.h>
#include <dlfcn.h>
#include <limits.h>
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