2026-10-27 03:48:46 +01:00
|
|
|
#include "xtc/xtc_processor.h"
|
|
|
|
|
#include "xtc/xtc_dsp.h"
|
|
|
|
|
#include <float.h>
|
|
|
|
|
#include <math.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
struct xtc_processor {
|
|
|
|
|
xtc_processor_descriptor descriptor;
|
|
|
|
|
void *state;
|
|
|
|
|
float values[XTC_PROCESSOR_PARAMETERS];
|
|
|
|
|
size_t frames;
|
|
|
|
|
uint64_t next;
|
|
|
|
|
};
|
|
|
|
|
static int text_valid(const char *s, size_t size, int empty)
|
|
|
|
|
{
|
|
|
|
|
size_t i;
|
|
|
|
|
if (!empty && !s[0]) return 0;
|
|
|
|
|
for (i = 0; i < size; ++i) {
|
|
|
|
|
if (!s[i]) return 1;
|
|
|
|
|
if ((unsigned char)s[i] < 32 || (unsigned char)s[i] > 126) return 0;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
xtc_result xtc_processor_validate(const xtc_processor_descriptor *d)
|
|
|
|
|
{
|
|
|
|
|
size_t i, j;
|
|
|
|
|
if (!d) return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
if (d->abi_version != XTC_PROCESSOR_ABI || d->struct_size != sizeof(*d)) return XTC_ERR_UNSUPPORTED;
|
|
|
|
|
if (!text_valid(d->identifier, 64, 0) || d->kind < XTC_NATIVE_EFFECT || d->kind > XTC_NATIVE_INSTRUMENT ||
|
|
|
|
|
d->parameter_count > XTC_PROCESSOR_PARAMETERS || !d->prepare || !d->reset || !d->process || !d->destroy ||
|
|
|
|
|
(d->parameter_count && !d->parameter)) return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
for (i = 0; i < d->parameter_count; ++i) {
|
|
|
|
|
const xtc_parameter_info *p = d->parameters + i;
|
|
|
|
|
if (!text_valid(p->name, sizeof(p->name), 0) || !text_valid(p->unit, sizeof(p->unit), 1) ||
|
|
|
|
|
!isfinite(p->minimum) || !isfinite(p->maximum) || !isfinite(p->default_value) ||
|
|
|
|
|
p->minimum > p->maximum || p->default_value < p->minimum || p->default_value > p->maximum)
|
|
|
|
|
return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
for (j = 0; j < i; ++j) if (d->parameters[j].id == p->id) return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
return XTC_OK;
|
|
|
|
|
}
|
|
|
|
|
static xtc_result create(const xtc_processor_descriptor *d, double rate, size_t frames,
|
|
|
|
|
const float *values, xtc_processor **out)
|
|
|
|
|
{
|
|
|
|
|
xtc_processor *p;
|
|
|
|
|
xtc_result result;
|
|
|
|
|
if (!out) return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
*out = NULL;
|
|
|
|
|
result = xtc_processor_validate(d);
|
|
|
|
|
if (result != XTC_OK) return result;
|
|
|
|
|
if (!isfinite(rate) || rate <= 0 || rate > 768000 || !frames || frames > 65536) return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
p = calloc(1, sizeof(*p));
|
|
|
|
|
if (!p) return XTC_ERR_OUT_OF_MEMORY;
|
|
|
|
|
p->descriptor = *d; p->frames = frames; p->next = UINT64_MAX;
|
|
|
|
|
if (values) memcpy(p->values, values, d->parameter_count * sizeof(float));
|
|
|
|
|
else { size_t i; for (i = 0; i < d->parameter_count; ++i) p->values[i] = d->parameters[i].default_value; }
|
|
|
|
|
result = d->prepare(rate, frames, p->values, d->parameter_count, &p->state);
|
|
|
|
|
if (result != XTC_OK) { xtc_processor_destroy(p); return result; }
|
|
|
|
|
d->reset(p->state);
|
|
|
|
|
*out = p;
|
|
|
|
|
return XTC_OK;
|
|
|
|
|
}
|
|
|
|
|
xtc_result xtc_processor_create(const xtc_processor_descriptor *d, double rate, size_t frames, xtc_processor **out)
|
|
|
|
|
{ return create(d, rate, frames, NULL, out); }
|
|
|
|
|
void xtc_processor_destroy(xtc_processor *p)
|
|
|
|
|
{ if (p) { p->descriptor.destroy(p->state); free(p); } }
|
|
|
|
|
void xtc_processor_reset(xtc_processor *p)
|
|
|
|
|
{ if (p) { p->descriptor.reset(p->state); p->next = UINT64_MAX; } }
|
|
|
|
|
static size_t parameter_index(const xtc_processor_descriptor *d, unsigned int id)
|
|
|
|
|
{
|
|
|
|
|
size_t i;
|
|
|
|
|
for (i = 0; i < d->parameter_count; ++i) if (d->parameters[i].id == id) return i;
|
|
|
|
|
return SIZE_MAX;
|
|
|
|
|
}
|
|
|
|
|
xtc_result xtc_processor_process(xtc_processor *p, const xtc_native_block *b, const xtc_parameter_event *events, size_t count)
|
|
|
|
|
{
|
|
|
|
|
size_t i, cursor = 0, param = 0, midi = 0;
|
|
|
|
|
xtc_result result = XTC_OK;
|
|
|
|
|
if (!p || !b || !b->output || b->position > UINT64_MAX - b->frames || b->frames > p->frames ||
|
|
|
|
|
count > 256 || (count && !events) || b->event_count > 256 || (b->event_count && !b->events) ||
|
|
|
|
|
(b->event_count && p->descriptor.kind != XTC_NATIVE_INSTRUMENT)) return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
for (i = 0; i < count; ++i) {
|
|
|
|
|
size_t index = parameter_index(&p->descriptor, events[i].id);
|
|
|
|
|
if (index == SIZE_MAX || events[i].offset >= b->frames || (i && events[i].offset < events[i-1].offset) ||
|
|
|
|
|
!isfinite(events[i].value) || events[i].value < p->descriptor.parameters[index].minimum ||
|
|
|
|
|
events[i].value > p->descriptor.parameters[index].maximum) return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
for (i = 0; i < b->event_count; ++i) {
|
|
|
|
|
const xtc_midi_event *e = b->events + i;
|
|
|
|
|
if (e->offset >= b->frames || (i && e->offset < b->events[i-1].offset) || e->type < XTC_MIDI_NOTE_ON ||
|
2026-11-05 23:47:54 +01:00
|
|
|
e->type > XTC_MIDI_BEND || e->pitch > 127 || e->velocity > 127 || e->channel > 15 ||
|
2026-10-27 03:48:46 +01:00
|
|
|
(e->type == XTC_MIDI_NOTE_ON && !e->velocity)) return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
if (!b->frames) return XTC_OK;
|
|
|
|
|
if (p->next != b->position) p->descriptor.reset(p->state);
|
|
|
|
|
while (cursor < b->frames) {
|
|
|
|
|
xtc_midi_event local[256];
|
|
|
|
|
size_t end, n = 0;
|
|
|
|
|
xtc_native_block segment;
|
|
|
|
|
while (param < count && events[param].offset == cursor) {
|
|
|
|
|
size_t index = parameter_index(&p->descriptor, events[param].id);
|
|
|
|
|
p->values[index] = events[param].value;
|
|
|
|
|
p->descriptor.parameter(p->state, events[param].id, events[param].value);
|
|
|
|
|
++param;
|
|
|
|
|
}
|
|
|
|
|
end = param < count ? events[param].offset : b->frames;
|
|
|
|
|
while (midi < b->event_count && b->events[midi].offset < end) {
|
|
|
|
|
local[n] = b->events[midi++]; local[n++].offset -= (unsigned int)cursor;
|
|
|
|
|
}
|
|
|
|
|
segment = (xtc_native_block){b->input ? b->input + cursor : NULL, b->output + cursor,
|
|
|
|
|
end - cursor, b->position + cursor, local, n};
|
|
|
|
|
result = p->descriptor.process(p->state, &segment);
|
|
|
|
|
if (result != XTC_OK) { memset(b->output, 0, b->frames * sizeof(float)); xtc_processor_reset(p); return result; }
|
|
|
|
|
cursor = end;
|
|
|
|
|
}
|
|
|
|
|
for (i = 0; i < b->frames; ++i) b->output[i] = xtc_dsp_finite(b->output[i]);
|
|
|
|
|
p->next = b->position + b->frames;
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
static void put(unsigned char *p, uint32_t value)
|
|
|
|
|
{ size_t i; for (i = 0; i < 4; ++i) p[i] = (unsigned char)(value >> (i * 8)); }
|
|
|
|
|
static uint32_t get(const unsigned char *p)
|
|
|
|
|
{ return (uint32_t)p[0] | (uint32_t)p[1]<<8 | (uint32_t)p[2]<<16 | (uint32_t)p[3]<<24; }
|
|
|
|
|
xtc_result xtc_processor_save(const xtc_processor *p, void *data, size_t capacity, size_t *written)
|
|
|
|
|
{
|
|
|
|
|
unsigned char *bytes = data;
|
|
|
|
|
size_t i, size;
|
|
|
|
|
if (!p || !written) return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
size = 80 + p->descriptor.parameter_count * 8;
|
|
|
|
|
*written = size;
|
|
|
|
|
if (!data || capacity < size) return XTC_ERR_CAPACITY;
|
|
|
|
|
if (sizeof(float) != 4 || FLT_RADIX != 2 || FLT_MANT_DIG != 24 || FLT_MAX_EXP != 128) return XTC_ERR_UNSUPPORTED;
|
|
|
|
|
memset(bytes, 0, size); memcpy(bytes, "XTCN", 4); put(bytes+4, 2);
|
|
|
|
|
memcpy(bytes+8, p->descriptor.identifier, strlen(p->descriptor.identifier));
|
|
|
|
|
put(bytes+72, (uint32_t)p->descriptor.parameter_count);
|
|
|
|
|
for (i = 0; i < p->descriptor.parameter_count; ++i) {
|
|
|
|
|
uint32_t bits; memcpy(&bits, p->values+i, 4);
|
|
|
|
|
put(bytes+80+i*8, p->descriptor.parameters[i].id); put(bytes+84+i*8, bits);
|
|
|
|
|
}
|
|
|
|
|
return XTC_OK;
|
|
|
|
|
}
|
|
|
|
|
xtc_result xtc_processor_restore(const xtc_processor_descriptor *d, double rate, size_t frames,
|
|
|
|
|
const void *data, size_t size, xtc_processor **out)
|
|
|
|
|
{
|
|
|
|
|
const unsigned char *bytes = data;
|
|
|
|
|
float values[XTC_PROCESSOR_PARAMETERS] = {0};
|
|
|
|
|
size_t i, j;
|
|
|
|
|
xtc_result result;
|
|
|
|
|
if (!out) return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
*out = NULL;
|
|
|
|
|
result = xtc_processor_validate(d);
|
|
|
|
|
if (result != XTC_OK) return result;
|
|
|
|
|
if (sizeof(float) != 4 || FLT_RADIX != 2 || FLT_MANT_DIG != 24 || FLT_MAX_EXP != 128) return XTC_ERR_UNSUPPORTED;
|
|
|
|
|
if (!bytes || size != 80 + 8*d->parameter_count || memcmp(bytes, "XTCN", 4) || get(bytes+4) != 2 ||
|
|
|
|
|
!memchr(bytes+8, 0, 64) || strcmp((const char *)bytes+8, d->identifier) ||
|
|
|
|
|
get(bytes+72) != d->parameter_count || get(bytes+76)) return XTC_ERR_FORMAT;
|
|
|
|
|
for (i = 0; i < d->parameter_count; ++i) {
|
|
|
|
|
uint32_t bits = get(bytes+84+i*8);
|
|
|
|
|
if (get(bytes+80+i*8) != d->parameters[i].id) return XTC_ERR_UNSUPPORTED;
|
|
|
|
|
memcpy(values+i, &bits, 4);
|
|
|
|
|
if (!isfinite(values[i]) || values[i] < d->parameters[i].minimum || values[i] > d->parameters[i].maximum)
|
|
|
|
|
return XTC_ERR_FORMAT;
|
|
|
|
|
}
|
|
|
|
|
for (j = 8 + strlen((const char *)bytes+8); j < 72; ++j) if (bytes[j]) return XTC_ERR_FORMAT;
|
|
|
|
|
return create(d, rate, frames, values, out);
|
|
|
|
|
}
|