From c3eadee8918f9dafeb3dacbce612e2f8403047e2 Mon Sep 17 00:00:00 2001 From: hanez Date: Tue, 27 Oct 2026 03:48:46 +0100 Subject: [PATCH] Phase 14: define parameterized native processors and portable configuration state --- Makefile | 10 ++- docs/NATIVE_API.md | 51 +++++++++++ docs/ROADMAP.md | 8 ++ examples/processors.c | 59 +++++++++++++ examples/processors.h | 6 ++ include/xtc/xtc_processor.h | 59 +++++++++++++ src/native/processor.c | 166 ++++++++++++++++++++++++++++++++++++ tests/unit/test_processor.c | 61 +++++++++++++ 8 files changed, 419 insertions(+), 1 deletion(-) create mode 100644 examples/processors.c create mode 100644 examples/processors.h create mode 100644 include/xtc/xtc_processor.h create mode 100644 src/native/processor.c create mode 100644 tests/unit/test_processor.c diff --git a/Makefile b/Makefile index a96684f..a5715fe 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ CXXFLAGS ?= -O2 -g CXXFLAGS += -std=c++17 -Wall -Wextra -Wpedantic -Iinclude LDFLAGS ?= -CORE_SOURCES := src/audio/recovery.c src/dsp/ramp.c src/native/native.c src/mixer/model.c src/mixer/render.c src/project/history.c src/midi/model.c src/midi/instrument.c src/audio/wave.c src/audio/arrangement.c src/audio/recorder.c src/core/xtc.c src/project/project.c src/persistence/project_file.c src/graph/graph.c src/dsp/gain.c src/engine/offline_engine.c src/engine/realtime.c src/backend/jack_backend.c +CORE_SOURCES := src/native/processor.c src/audio/recovery.c src/dsp/ramp.c src/native/native.c src/mixer/model.c src/mixer/render.c src/project/history.c src/midi/model.c src/midi/instrument.c src/audio/wave.c src/audio/arrangement.c src/audio/recorder.c src/core/xtc.c src/project/project.c src/persistence/project_file.c src/graph/graph.c src/dsp/gain.c src/engine/offline_engine.c src/engine/realtime.c src/backend/jack_backend.c JACK_AVAILABLE := $(shell $(PKG_CONFIG) --exists jack 2>/dev/null && echo yes) ifeq ($(JACK_AVAILABLE),yes) JACK_CFLAGS := $(shell $(PKG_CONFIG) --cflags jack) @@ -294,3 +294,11 @@ $(BUILD_DIR)/test_recording: $(BUILD_DIR)/tests/integration/test_recording.o $(B test-recording: $(BUILD_DIR)/test_recording ./$(BUILD_DIR)/test_recording -include $(BUILD_DIR)/tests/integration/test_recording.d + +$(BUILD_DIR)/test_processor: $(BUILD_DIR)/tests/unit/test_processor.o $(BUILD_DIR)/examples/processors.o $(BUILD_DIR)/libxtc.a + $(CC) $(LDFLAGS) -o $@ $^ -lm +.PHONY: test-processor +test-processor: $(BUILD_DIR)/test_processor + ./$(BUILD_DIR)/test_processor +test: test-processor +-include $(BUILD_DIR)/tests/unit/test_processor.d $(BUILD_DIR)/examples/processors.d diff --git a/docs/NATIVE_API.md b/docs/NATIVE_API.md index 35067df..c445710 100644 --- a/docs/NATIVE_API.md +++ b/docs/NATIVE_API.md @@ -81,3 +81,54 @@ Tests cover ABI/size/field rejection, descriptor copying, aliased buffers, prepare/process failures, stereo compile cleanup, retirement/destruction outside processing, event offsets, seek note reset, source ports, instrument-buffer graph routing and unsupported-save preservation. Run `make test`. + +## Phase 14: structured parameter processors + +`xtc_processor.h` is the public C99 parameter-only extension interface (ABI 2). +It complements the ABI 1 graph adapter without changing its layout or behavior. +`examples/processors.c` implements a mono gain effect and a monophonic MIDI sine +instrument using only public headers; `make test-processor` builds and exercises +both. The instrument has immediate note-off and no voice stealing/release tail. + +Descriptors have an exact ABI version and structure size, a persistent identifier, +and up to 16 parameters with unique unsigned IDs, names, units, finite ranges and +defaults. Unsupported ABI versions/sizes are rejected before initialization. +Changing identifier, parameter order, IDs or ranges requires an explicit migration +by the extension author. There is no promise of binary compatibility across +compiler architectures, and no C++/FLTK types in either API. + +Create prepares a copied descriptor on the control thread. Failed preparation +calls destroy, including with NULL: callbacks must implement partial cleanup. +Reset/process/parameter have one audio owner and must never allocate, free, block, +perform I/O or call GUI functions. Destroy is control-thread only after callback +quiescence. Reconfiguration prepares a new independent instance; publish it with +an appropriate owner handoff and retire the old instance after quiescence. The +callback code must remain loaded throughout that lifetime. + +Each instance has one mono output and either one optional input (effect) or no +input (source/instrument). NULL input means silence. Stereo effects use two +independent instances; multichannel interdependent effects have no adapter. +Instruments receive at most 256 sorted sample-offset MIDI events per block. +The API intentionally has no channel-count argument: unsupported layouts cannot +be represented. Graph ABI 1 rejects instrument descriptors; explicitly render +instrument output and feed the existing instrument source buffers. + +Up to 256 sorted parameter events split the process block at exact sample +positions. Equal-offset parameters execute in array order, then MIDI at that +sample. Values outside the descriptor range, bad IDs and malformed events reject +the entire call before touching state/output. Values are stepped; extension +callbacks own any smoothing. Transport discontinuities reset transient state; +reset retains current parameters. Callback errors silence the whole output and +reset voices/history; already applied parameters persist. Nonfinite/subnormal +successful samples become zero. Input/output may alias exactly. + +`xtc_processor_save`/`restore` run only while quiescent and serialize configuration, +not DSP history. Format: `XTCN`, little-endian uint32 version 2, zero-padded +64-byte identifier, uint32 parameter count, zero uint32 reserved, then ordered +pairs of uint32 ID and IEEE float32 bits. Header is 80 bytes, pairs 8 bytes. +Restore validates exact length, identity, IDs, ranges and defaults/descriptor +compatibility before preparing a new instance; failure leaves existing handles +intact. Opaque assets and hidden state are outside this ABI. ABI 1 graph nodes +remain session-only and fail project saves explicitly; ABI 2 configuration blobs +are a separate public facility, not automatically embedded in project files. +GUI graph access continues through public C APIs only. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index c122614..871fdc6 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -264,6 +264,14 @@ and playback of assets above the existing preload limit remain unqualified. - Node state can be saved and restored where required. - No C++ or FLTK types leak into the public C API. +Implemented: ABI 2 parameter-only C processor lifecycle with copied descriptors, +metadata/default/range validation, sample-offset parameter/MIDI ordering, +transactional portable configuration restoration, and public gain/instrument +examples. Lifecycle, invalid input, event ordering, in-place output, reset, +callback failure and state compatibility tests pass. See NATIVE_API.md. ABI 1 +graph compatibility is retained; native graph persistence and opaque plugin +state are explicitly outside this parameter-only interface. + ## Phase 15 — External Plugin Hosting **Goal:** Add external plugin support only after the internal node lifecycle is reliable. diff --git a/examples/processors.c b/examples/processors.c new file mode 100644 index 0000000..d782d86 --- /dev/null +++ b/examples/processors.c @@ -0,0 +1,59 @@ +#include "processors.h" +#include +#include +#include +typedef struct { double rate, phase, frequency; float gain, velocity; unsigned pitch; } voice; +static xtc_result prepare(double rate, size_t frames, const float *values, size_t count, void **out) +{ + voice *v = calloc(1, sizeof(*v)); + (void)frames; (void)count; + *out = v; + if (!v) return XTC_ERR_OUT_OF_MEMORY; + v->rate = rate; v->gain = values[0]; + return XTC_OK; +} +static void reset(void *state) +{ voice *v = state; v->phase = 0; v->velocity = 0; } +static void parameter(void *state, unsigned id, float value) +{ (void)id; ((voice *)state)->gain = value; } +static void destroy(void *state) { free(state); } +static xtc_result effect(void *state, const xtc_native_block *b) +{ + voice *v = state; size_t i; + for (i = 0; i < b->frames; ++i) b->output[i] = b->input ? b->input[i] * v->gain : 0; + return XTC_OK; +} +static xtc_result instrument(void *state, const xtc_native_block *b) +{ + voice *v = state; size_t i, e = 0; + for (i = 0; i < b->frames; ++i) { + while (e < b->event_count && b->events[e].offset == i) { + const xtc_midi_event *m = b->events + e++; + if (m->type == XTC_MIDI_NOTE_ON) { + v->pitch = m->pitch; v->velocity = (float)m->velocity / 127.0f; + v->frequency = 440.0 * pow(2.0, ((double)m->pitch - 69.0) / 12.0); + } else if (m->type == XTC_MIDI_ALL_OFF || m->pitch == v->pitch) v->velocity = 0; + } + b->output[i] = (float)sin(v->phase) * v->velocity * v->gain; + v->phase = fmod(v->phase + 6.283185307179586 * v->frequency / v->rate, 6.283185307179586); + } + return XTC_OK; +} +xtc_processor_descriptor example_effect(void) +{ + xtc_processor_descriptor d; + memset(&d, 0, sizeof(d)); + d.abi_version = XTC_PROCESSOR_ABI; d.struct_size = sizeof(d); + strcpy(d.identifier, "xtc.example.gain.v1"); d.kind = XTC_NATIVE_EFFECT; + d.parameter_count = 1; d.parameters[0].id = 1; + strcpy(d.parameters[0].name, "Gain"); strcpy(d.parameters[0].unit, "linear"); + d.parameters[0].minimum = 0; d.parameters[0].maximum = 2; d.parameters[0].default_value = 1; + d.prepare = prepare; d.reset = reset; d.process = effect; d.parameter = parameter; d.destroy = destroy; + return d; +} +xtc_processor_descriptor example_instrument(void) +{ + xtc_processor_descriptor d = example_effect(); + strcpy(d.identifier, "xtc.example.sine.v1"); d.kind = XTC_NATIVE_INSTRUMENT; d.process = instrument; + return d; +} diff --git a/examples/processors.h b/examples/processors.h new file mode 100644 index 0000000..c675ca0 --- /dev/null +++ b/examples/processors.h @@ -0,0 +1,6 @@ +#ifndef XTC_EXAMPLE_PROCESSORS_H +#define XTC_EXAMPLE_PROCESSORS_H +#include "xtc/xtc_processor.h" +xtc_processor_descriptor example_effect(void); +xtc_processor_descriptor example_instrument(void); +#endif diff --git a/include/xtc/xtc_processor.h b/include/xtc/xtc_processor.h new file mode 100644 index 0000000..fdef72f --- /dev/null +++ b/include/xtc/xtc_processor.h @@ -0,0 +1,59 @@ +#ifndef XTC_PROCESSOR_H +#define XTC_PROCESSOR_H +#include "xtc_native.h" +#ifdef __cplusplus +extern "C" { +#endif +#define XTC_PROCESSOR_ABI 2U +#define XTC_PROCESSOR_PARAMETERS 16U +#define XTC_PROCESSOR_STATE_MAX (80U + 8U * XTC_PROCESSOR_PARAMETERS) +typedef struct { + unsigned int id; + char name[64], unit[16]; + float minimum, maximum, default_value; +} xtc_parameter_info; +typedef struct { + unsigned int offset, id; + float value; +} xtc_parameter_event; +typedef struct { + unsigned int abi_version; + size_t struct_size; + char identifier[64]; + xtc_native_kind kind; + size_t parameter_count; + xtc_parameter_info parameters[XTC_PROCESSOR_PARAMETERS]; + xtc_result (*prepare)(double rate, size_t max_frames, const float *values, size_t count, void **state); + void (*reset)(void *state); + xtc_result (*process)(void *state, const xtc_native_block *block); + void (*parameter)(void *state, unsigned int id, float value); + void (*destroy)(void *state); +} xtc_processor_descriptor; +typedef struct xtc_processor xtc_processor; +/* Descriptors are copied. Trusted C callbacks, exact version/size matching, + * mono ports (stereo = two independent instances). Control: prepare/destroy; + * single audio owner: reset/process/parameter. No callback allocation/I/O/locks. + * Reset clears transient voices/history, retaining parameter values. */ +xtc_result xtc_processor_validate(const xtc_processor_descriptor *descriptor); +xtc_result xtc_processor_create(const xtc_processor_descriptor *descriptor, double rate, size_t frames, + xtc_processor **out); +void xtc_processor_destroy(xtc_processor *processor); +void xtc_processor_reset(xtc_processor *processor); +/* Validate all events before touching output/state. At equal offsets parameter + * events run in array order before MIDI events. Up to 256 events of each type. + * A callback failure zeros the entire block and resets transient state. + * Parameter values already applied remain applied. No rollback of plugin DSP. */ +xtc_result xtc_processor_process(xtc_processor *processor, const xtc_native_block *block, + const xtc_parameter_event *events, size_t count); +/* Quiescent control thread only. Portable little-endian config: identifier and + * stable parameter IDs/float32 values; no pointers, voices, delays or hidden DSP + * state. Restore prepares a new instance transactionally; old handles are intact. + * Descriptors requiring opaque assets/state are outside this parameter-only ABI. + * Format/ID/range mismatches are errors, never silent clamping. */ +xtc_result xtc_processor_save(const xtc_processor *processor, void *data, size_t capacity, size_t *written); +xtc_result xtc_processor_restore(const xtc_processor_descriptor *descriptor, double rate, size_t frames, + const void *data, size_t size, xtc_processor **out); +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/native/processor.c b/src/native/processor.c new file mode 100644 index 0000000..b9025a4 --- /dev/null +++ b/src/native/processor.c @@ -0,0 +1,166 @@ +#include "xtc/xtc_processor.h" +#include "xtc/xtc_dsp.h" +#include +#include +#include +#include +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 || + e->type > XTC_MIDI_ALL_OFF || e->pitch > 127 || e->velocity > 127 || e->channel > 15 || + (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); +} diff --git a/tests/unit/test_processor.c b/tests/unit/test_processor.c new file mode 100644 index 0000000..255929b --- /dev/null +++ b/tests/unit/test_processor.c @@ -0,0 +1,61 @@ +#include "../../examples/processors.h" +#include +#include +#include +#include +static int destroyed; +static xtc_result fail(double r, size_t n, const float *v, size_t c, void **s) +{ (void)r; (void)n; (void)v; (void)c; *s = NULL; return XTC_ERR_OUT_OF_MEMORY; } +static void cleanup(void *s) { assert(!s); ++destroyed; } +static xtc_result process_fail(void *s, const xtc_native_block *b) +{ (void)s; b->output[0] = 99; return XTC_ERR_UNSUPPORTED; } +int main(void) +{ + xtc_processor_descriptor d = example_effect(), bad; + xtc_processor *p = NULL, *restored = NULL; + float out[16], input[16]; size_t i, size; + unsigned char state[XTC_PROCESSOR_STATE_MAX], broken[XTC_PROCESSOR_STATE_MAX]; + xtc_native_block b = {input, out, 16, 0, NULL, 0}; + xtc_parameter_event e[] = {{0,1,.5f},{7,1,1},{7,1,2}}; + for (i = 0; i < 16; ++i) input[i] = 1; + bad = d; bad.abi_version = 99; assert(xtc_processor_validate(&bad) == XTC_ERR_UNSUPPORTED); + bad = d; bad.parameters[0].default_value = NAN; assert(xtc_processor_validate(&bad) != XTC_OK); + bad = d; bad.prepare = fail; bad.destroy = cleanup; + assert(xtc_processor_create(&bad,48000,16,&p) == XTC_ERR_OUT_OF_MEMORY && !p && destroyed == 1); + assert(xtc_processor_create(&d,48000,16,&p) == XTC_OK); + assert(xtc_processor_process(p,&b,e,3) == XTC_OK); + for (i = 0; i < 16; ++i) assert(out[i] == (i < 7 ? .5f : 2)); + e[1].value = 3; out[0] = 123; + assert(xtc_processor_process(p,&b,e,3) == XTC_ERR_INVALID_ARGUMENT && out[0] == 123); + assert(xtc_processor_save(p,state,1,&size) == XTC_ERR_CAPACITY && size == 88); + assert(xtc_processor_save(p,state,sizeof(state),&size) == XTC_OK); + assert(xtc_processor_restore(&d,48000,16,state,size,&restored) == XTC_OK); + b.input = out; for (i = 0; i < 16; ++i) out[i] = 1; + assert(xtc_processor_process(restored,&b,NULL,0) == XTC_OK && out[15] == 2); + xtc_processor_destroy(restored); restored = NULL; + memcpy(broken,state,size); broken[8] = 'z'; + assert(xtc_processor_restore(&d,48000,16,broken,size,&restored) != XTC_OK && !restored); + memcpy(broken,state,size); broken[80] = 42; + assert(xtc_processor_restore(&d,48000,16,broken,size,&restored) != XTC_OK); + assert(xtc_processor_restore(&d,48000,16,state,size-1,&restored) != XTC_OK); + xtc_processor_destroy(p); + d.process = process_fail; assert(xtc_processor_create(&d,48000,16,&p) == XTC_OK); + assert(xtc_processor_process(p,&b,NULL,0) == XTC_ERR_UNSUPPORTED); + for (i = 0; i < 16; ++i) assert(out[i] == 0); + xtc_processor_destroy(p); + d = example_instrument(); assert(xtc_processor_create(&d,48000,16,&p) == XTC_OK); + { + xtc_midi_event midi[] = {{0,XTC_MIDI_NOTE_ON,0,69,127},{8,XTC_MIDI_NOTE_OFF,0,69,0}}; + b.input = NULL; b.events = midi; b.event_count = 2; + e[0].value = .25f; + assert(xtc_processor_process(p,&b,e,1) == XTC_OK); + assert(out[1] > 0 && out[1] < .25f); + for (i = 8; i < 16; ++i) assert(out[i] == 0); + xtc_processor_reset(p); b.events = NULL; b.event_count = 0; + assert(xtc_processor_process(p,&b,NULL,0) == XTC_OK); + for (i = 0; i < 16; ++i) assert(out[i] == 0); + } + xtc_processor_destroy(p); + puts("Native parameter events, state, effect/instrument and lifecycle errors passed."); + return 0; +}