2026-10-27 03:37:19 +01:00
|
|
|
#include "xtc/xtc_dsp.h"
|
2026-10-27 01:21:04 +01:00
|
|
|
#include "xtc/xtc_native.h"
|
|
|
|
|
#include <math.h>
|
|
|
|
|
#include <stdlib.h>
|
2026-10-27 01:37:21 +01:00
|
|
|
|
2026-10-27 01:21:04 +01:00
|
|
|
struct xtc_native {
|
|
|
|
|
xtc_native_descriptor descriptor;
|
|
|
|
|
void *state;
|
|
|
|
|
size_t max_frames;
|
|
|
|
|
uint64_t next_position;
|
|
|
|
|
};
|
2026-10-27 01:37:21 +01:00
|
|
|
|
2026-10-27 01:58:27 +01:00
|
|
|
/**
|
|
|
|
|
* @brief Checks that a native descriptor is compatible and complete.
|
|
|
|
|
*
|
|
|
|
|
* Validates the ABI version and structure size, the kind, the presence of all
|
|
|
|
|
* callbacks, and that the identifier is a nonempty printable ASCII string.
|
|
|
|
|
*
|
|
|
|
|
* @param d Descriptor to validate; must not be NULL.
|
|
|
|
|
* @return XTC_OK if the descriptor is usable, XTC_ERR_INVALID_ARGUMENT for a
|
|
|
|
|
* missing descriptor or invalid fields, or XTC_ERR_UNSUPPORTED for an
|
|
|
|
|
* ABI version or structure-size mismatch.
|
|
|
|
|
*/
|
2026-10-27 01:21:04 +01:00
|
|
|
xtc_result xtc_native_validate(const xtc_native_descriptor *d)
|
|
|
|
|
{
|
|
|
|
|
size_t i;
|
|
|
|
|
if (!d)
|
|
|
|
|
return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
if (d->abi_version != XTC_NATIVE_ABI_VERSION || d->struct_size != sizeof(*d))
|
|
|
|
|
return XTC_ERR_UNSUPPORTED;
|
2026-10-27 01:37:21 +01:00
|
|
|
if (d->kind < XTC_NATIVE_EFFECT || d->kind > XTC_NATIVE_INSTRUMENT || !d->prepare || !d->reset || !d->process ||
|
|
|
|
|
!d->destroy || !d->identifier[0])
|
2026-10-27 01:21:04 +01:00
|
|
|
return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
for (i = 0; i < sizeof(d->identifier); ++i) {
|
|
|
|
|
unsigned char c = (unsigned char)d->identifier[i];
|
|
|
|
|
if (!c)
|
|
|
|
|
return XTC_OK;
|
|
|
|
|
if (c < 33 || c > 126)
|
|
|
|
|
return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
}
|
2026-10-27 01:37:21 +01:00
|
|
|
|
2026-10-27 01:58:27 +01:00
|
|
|
/**
|
|
|
|
|
* @brief Copies a native descriptor and prepares a processor instance on the control thread.
|
|
|
|
|
*
|
|
|
|
|
* @param d Native processor descriptor.
|
|
|
|
|
* @param rate Sample rate in hertz.
|
|
|
|
|
* @param frames Number of audio frames.
|
|
|
|
|
* @param value Value to encode, write, or apply.
|
|
|
|
|
* @param out Receives the newly allocated object; caller owns it on success.
|
|
|
|
|
* @return XTC_OK on success, or an error code describing the failure.
|
|
|
|
|
*/
|
2026-10-27 01:37:21 +01:00
|
|
|
xtc_result xtc_native_create(const xtc_native_descriptor *d, double rate, size_t frames, float value, xtc_native **out)
|
2026-10-27 01:21:04 +01:00
|
|
|
{
|
|
|
|
|
xtc_native *p;
|
|
|
|
|
xtc_result r;
|
|
|
|
|
if (!out)
|
|
|
|
|
return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
*out = NULL;
|
|
|
|
|
r = xtc_native_validate(d);
|
|
|
|
|
if (r != XTC_OK)
|
|
|
|
|
return r;
|
2026-10-27 01:37:21 +01:00
|
|
|
if (!isfinite(rate) || rate <= 0 || rate > 768000 || !frames || frames > 65536 || !isfinite(value))
|
2026-10-27 01:21:04 +01:00
|
|
|
return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
p = calloc(1, sizeof(*p));
|
|
|
|
|
if (!p)
|
|
|
|
|
return XTC_ERR_OUT_OF_MEMORY;
|
|
|
|
|
p->descriptor = *d;
|
|
|
|
|
p->max_frames = frames;
|
|
|
|
|
p->next_position = UINT64_MAX;
|
|
|
|
|
r = d->prepare(rate, frames, value, &p->state);
|
|
|
|
|
if (r != XTC_OK) {
|
|
|
|
|
xtc_native_destroy(p);
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
p->descriptor.reset(p->state);
|
|
|
|
|
*out = p;
|
|
|
|
|
return XTC_OK;
|
|
|
|
|
}
|
2026-10-27 01:37:21 +01:00
|
|
|
|
2026-10-27 01:58:27 +01:00
|
|
|
/**
|
|
|
|
|
* @brief Destroys native state and releases the host wrapper; accepts NULL.
|
|
|
|
|
*
|
|
|
|
|
* @param p Native processor instance.
|
|
|
|
|
*/
|
2026-10-27 01:21:04 +01:00
|
|
|
void xtc_native_destroy(xtc_native *p)
|
|
|
|
|
{
|
|
|
|
|
if (p) {
|
|
|
|
|
p->descriptor.destroy(p->state);
|
|
|
|
|
free(p);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-27 01:37:21 +01:00
|
|
|
|
2026-10-27 01:58:27 +01:00
|
|
|
/**
|
|
|
|
|
* @brief Resets native state and transport continuity; accepts NULL.
|
|
|
|
|
*
|
|
|
|
|
* @param p Native processor instance.
|
|
|
|
|
*/
|
2026-10-27 01:21:04 +01:00
|
|
|
void xtc_native_reset(xtc_native *p)
|
|
|
|
|
{
|
|
|
|
|
if (p) {
|
|
|
|
|
p->descriptor.reset(p->state);
|
|
|
|
|
p->next_position = UINT64_MAX;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-27 01:37:21 +01:00
|
|
|
|
2026-10-27 01:58:27 +01:00
|
|
|
/**
|
|
|
|
|
* @brief Processes a bounded native block, resetting on discontinuity and silencing callback failures.
|
|
|
|
|
*
|
|
|
|
|
* @param p Native processor instance.
|
|
|
|
|
* @param b Borrowed processing buffers and block metadata.
|
|
|
|
|
* @return XTC_OK on success, or an error code describing the failure.
|
|
|
|
|
*/
|
2026-10-27 01:21:04 +01:00
|
|
|
xtc_result xtc_native_process(xtc_native *p, const xtc_native_block *b)
|
|
|
|
|
{
|
|
|
|
|
size_t i;
|
|
|
|
|
xtc_result r;
|
2026-10-27 01:37:21 +01:00
|
|
|
if (!p || !b || !b->output || b->position > UINT64_MAX - b->frames || b->event_count > XTC_MIDI_MAX_EVENTS ||
|
|
|
|
|
(b->event_count && !b->events) || (b->event_count && p->descriptor.kind != XTC_NATIVE_INSTRUMENT))
|
2026-10-27 01:21:04 +01:00
|
|
|
return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
if (b->frames > p->max_frames)
|
|
|
|
|
return XTC_ERR_CAPACITY;
|
|
|
|
|
for (i = 0; i < b->event_count; ++i) {
|
|
|
|
|
const xtc_midi_event *e = b->events + i;
|
2026-10-27 01:37:21 +01:00
|
|
|
if (e->offset >= b->frames || (i && e->offset < b->events[i - 1].offset) || e->type < XTC_MIDI_NOTE_ON ||
|
2026-10-27 06:25:30 +01:00
|
|
|
e->type > XTC_MIDI_CONTROL || e->channel > 15 || e->pitch > 127 || e->velocity > 127 ||
|
2026-10-27 01:37:21 +01:00
|
|
|
(e->type == XTC_MIDI_NOTE_ON && !e->velocity))
|
2026-10-27 01:21:04 +01:00
|
|
|
return XTC_ERR_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
if (!b->frames)
|
|
|
|
|
return XTC_OK;
|
|
|
|
|
if (p->next_position != b->position)
|
|
|
|
|
p->descriptor.reset(p->state);
|
|
|
|
|
r = p->descriptor.process(p->state, b);
|
|
|
|
|
if (r != XTC_OK) {
|
|
|
|
|
for (i = 0; i < b->frames; ++i)
|
|
|
|
|
b->output[i] = 0;
|
|
|
|
|
xtc_native_reset(p);
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
for (i = 0; i < b->frames; ++i)
|
2026-10-27 03:37:19 +01:00
|
|
|
b->output[i] = xtc_dsp_finite(b->output[i]);
|
2026-10-27 01:21:04 +01:00
|
|
|
p->next_position = b->position + b->frames;
|
|
|
|
|
return XTC_OK;
|
|
|
|
|
}
|