2026-10-27 04:00:27 +01:00
|
|
|
#include "xtc/xtc_plugin.h"
|
|
|
|
|
#include "xtc/xtc_dsp.h"
|
2026-10-27 06:25:30 +01:00
|
|
|
#include "xtc/xtc_project.h"
|
2026-10-27 04:00:27 +01:00
|
|
|
#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. */
|
2026-10-27 06:44:22 +01:00
|
|
|
#include "config.h"
|
2026-10-27 04:00:27 +01:00
|
|
|
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 ||
|
2026-10-27 06:25:30 +01:00
|
|
|
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;
|
2026-10-27 04:00:27 +01:00
|
|
|
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;
|
2026-10-27 06:25:30 +01:00
|
|
|
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;
|
|
|
|
|
}
|
2026-10-27 04:00:27 +01:00
|
|
|
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;
|
2026-10-27 06:25:30 +01:00
|
|
|
xtc_plugin_point points[XTC_PLUGIN_POINTS];
|
|
|
|
|
uint64_t samples[XTC_PLUGIN_POINTS], next;
|
|
|
|
|
unsigned int point_count;
|
2026-10-27 04:00:27 +01:00
|
|
|
};
|
|
|
|
|
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;
|
2026-10-27 06:25:30 +01:00
|
|
|
s->tempo = 120000;
|
2026-10-27 04:00:27 +01:00
|
|
|
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)) ||
|
2026-10-27 16:24:21 +01:00
|
|
|
(LADSPA_IS_HINT_SAMPLE_RATE(h->HintDescriptor) &&
|
|
|
|
|
((LADSPA_IS_HINT_BOUNDED_BELOW(h->HintDescriptor) && fabs((double)h->LowerBound*rate) > FLT_MAX) ||
|
|
|
|
|
(LADSPA_IS_HINT_BOUNDED_ABOVE(h->HintDescriptor) && fabs((double)h->UpperBound*rate) > FLT_MAX))) ||
|
2026-10-27 04:00:27 +01:00
|
|
|
(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;
|
|
|
|
|
}
|
2026-10-27 16:24:21 +01:00
|
|
|
xtc_result xtc_plugin_describe(const xtc_plugin_spec *s, double rate, xtc_plugin_details *out)
|
2026-10-27 04:00:27 +01:00
|
|
|
{
|
|
|
|
|
void *module = NULL; const LADSPA_Descriptor *d; xtc_plugin_spec actual = {0};
|
2026-10-27 16:24:21 +01:00
|
|
|
xtc_result r; size_t i; xtc_plugin_details details = {0};
|
2026-10-27 04:00:27 +01:00
|
|
|
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;
|
2026-10-27 16:24:21 +01:00
|
|
|
p.default_value = actual.values[i]; details.parameters[i] = p;
|
|
|
|
|
details.flags[i] = (LADSPA_IS_HINT_INTEGER(h->HintDescriptor) ? XTC_PLUGIN_INTEGER : 0U) |
|
|
|
|
|
(LADSPA_IS_HINT_TOGGLED(h->HintDescriptor) ? XTC_PLUGIN_TOGGLED : 0U) |
|
|
|
|
|
(LADSPA_IS_HINT_LOGARITHMIC(h->HintDescriptor) ? XTC_PLUGIN_LOGARITHMIC : 0U) |
|
|
|
|
|
(LADSPA_IS_HINT_SAMPLE_RATE(h->HintDescriptor) ? XTC_PLUGIN_SAMPLE_RATE : 0U) |
|
|
|
|
|
(LADSPA_IS_HINT_BOUNDED_BELOW(h->HintDescriptor) ? XTC_PLUGIN_BOUNDED_BELOW : 0U) |
|
|
|
|
|
(LADSPA_IS_HINT_BOUNDED_ABOVE(h->HintDescriptor) ? XTC_PLUGIN_BOUNDED_ABOVE : 0U);
|
|
|
|
|
}
|
|
|
|
|
if (r == XTC_OK) {
|
|
|
|
|
snprintf(details.name,sizeof(details.name),"%s",d->Name ? d->Name : d->Label);
|
|
|
|
|
snprintf(details.maker,sizeof(details.maker),"%s",d->Maker ? d->Maker : "Unknown");
|
|
|
|
|
snprintf(details.copyright,sizeof(details.copyright),"%s",d->Copyright ? d->Copyright : "Unspecified");
|
|
|
|
|
details.hard_realtime = LADSPA_IS_HARD_RT_CAPABLE(d->Properties) ? 1U : 0U;
|
|
|
|
|
*out = details;
|
2026-10-27 04:00:27 +01:00
|
|
|
}
|
|
|
|
|
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];
|
|
|
|
|
}
|
2026-10-27 06:25:30 +01:00
|
|
|
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;
|
2026-10-27 04:00:27 +01:00
|
|
|
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;
|
|
|
|
|
}
|
2026-10-27 06:25:30 +01:00
|
|
|
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)
|
2026-10-27 04:00:27 +01:00
|
|
|
{
|
|
|
|
|
size_t i;
|
2026-10-27 06:25:30 +01:00
|
|
|
if (!p || !output || frames > p->frames || position > UINT64_MAX-frames) return XTC_ERR_INVALID_ARGUMENT;
|
2026-10-27 04:00:27 +01:00
|
|
|
if (!frames) return XTC_OK;
|
2026-10-27 06:25:30 +01:00
|
|
|
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;
|
2026-10-27 04:00:27 +01:00
|
|
|
}
|
2026-10-27 06:25:30 +01:00
|
|
|
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); }
|
|
|
|
|
|
2026-10-27 04:00:27 +01:00
|
|
|
#else
|
2026-10-27 16:24:21 +01:00
|
|
|
xtc_result xtc_plugin_describe(const xtc_plugin_spec *s, double r, xtc_plugin_details *out)
|
2026-10-27 04:00:27 +01:00
|
|
|
{ (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; }
|
2026-10-27 06:25:30 +01:00
|
|
|
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; }
|
2026-10-27 04:00:27 +01:00
|
|
|
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
|
2026-10-27 16:24:21 +01:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|