2026-11-06 15:20:20 +01:00
|
|
|
# Native C Processor API, Version 1
|
2026-10-27 01:21:04 +01:00
|
|
|
|
|
|
|
|
`xtc_native.h` defines a small compile-time extension boundary for trusted C99
|
2026-11-06 19:05:14 +01:00
|
|
|
sources, effects and instruments. Build `cmake --build build --target native-gain` and run it for
|
2026-11-06 15:20:20 +01:00
|
|
|
a complete graph-effect example in `examples/native_gain.c`. This is a source
|
|
|
|
|
API for this release, not a promise of binary compatibility across
|
|
|
|
|
builds/platforms. There is no module scanner, dynamic loader, SDK dependency or
|
|
|
|
|
sandbox.
|
2026-10-27 01:21:04 +01:00
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Descriptor and Ownership
|
2026-10-27 01:21:04 +01:00
|
|
|
|
|
|
|
|
Set `abi_version = XTC_NATIVE_ABI_VERSION`, `struct_size = sizeof(descriptor)`,
|
|
|
|
|
a terminated printable non-space ASCII identifier (1..63 bytes), kind and all
|
|
|
|
|
four callbacks. Unknown ABI/size returns UNSUPPORTED; invalid fields return
|
|
|
|
|
INVALID_ARGUMENT. The host copies the entire descriptor, including its name.
|
|
|
|
|
Callback code must remain loaded until all graphs, plans and instances are gone.
|
|
|
|
|
No plugin/global registry is consulted or mutated by rendering.
|
|
|
|
|
|
|
|
|
|
`xtc_native_create` validates rate (positive, <=768000), frames (1..65536) and a
|
|
|
|
|
finite scalar parameter. It runs prepare on the control thread with a NULL
|
|
|
|
|
initial state pointer. Prepare may allocate/preload; on failure the host calls
|
|
|
|
|
destroy even for NULL state. Destroy must accept NULL. The host then resets the
|
|
|
|
|
new instance. On failure the output handle is NULL; successful instances belong
|
|
|
|
|
to the caller. Destroy only after the processing owner is stopped/joined.
|
|
|
|
|
|
|
|
|
|
Reset and process have one audio owner (or run while stopped). They must never
|
|
|
|
|
allocate/free, perform I/O/logging, block, call GUI functions or mutate shared
|
|
|
|
|
control data. Reset must be idempotent. Prepare/destroy run only on the control
|
|
|
|
|
thread, including failed compilation and retired-plan collection. Code executed
|
|
|
|
|
inside callbacks is trusted: xtc cannot enforce its realtime behavior or protect
|
|
|
|
|
against memory corruption. C++ processors must use C linkage and never throw
|
|
|
|
|
across this boundary; domain/DSP code in this repository remains C.
|
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
---
|
2026-10-27 01:21:04 +01:00
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
## Block Contract
|
|
|
|
|
|
|
|
|
|
A block contains mono input/output, frames, absolute sample position and
|
|
|
|
|
optional MIDI events. Input NULL means silence; exact input/output aliasing is
|
|
|
|
|
allowed and processors must support it. Output is writable for every frame,
|
|
|
|
|
including silent frames; do not retain any borrowed block pointers. Other
|
|
|
|
|
overlapping buffers are not supported. Only instruments receive events; event
|
|
|
|
|
count <=256, offsets are sorted and strictly less than frames, with equal
|
|
|
|
|
offsets retaining caller order. Pitch/velocity/channel/type bounds follow
|
|
|
|
|
xtc_midi.h. Zero frames is a no-op.
|
2026-10-27 01:21:04 +01:00
|
|
|
|
|
|
|
|
The host rejects invalid blocks before changing state/output, and reports
|
2026-11-06 15:20:20 +01:00
|
|
|
CAPACITY above the prepared block limit. Position arithmetic cannot overflow. A
|
|
|
|
|
discontinuous position resets held voices/state before processing. Call reset
|
2026-10-27 01:21:04 +01:00
|
|
|
for Stop/panic when hosting instruments yourself; send channel ALL_OFF for
|
|
|
|
|
channel-specific panic. The native host does not implement sustain or routing.
|
|
|
|
|
|
|
|
|
|
On successful processing, nonfinite samples become silence. Callback failure
|
2026-11-06 15:20:20 +01:00
|
|
|
silences the block, resets that processor and returns its error. Already
|
|
|
|
|
advanced processors in a graph are not rolled back. Never rely on failure to
|
|
|
|
|
transactionally rewind DSP; prepare fallible resources before publication. There
|
|
|
|
|
is no latency compensation, tail protocol, parameter ramp or dynamic port
|
|
|
|
|
negotiation in ABI 1.
|
2026-10-27 01:21:04 +01:00
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
---
|
2026-10-27 01:21:04 +01:00
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
## Graph and Instrument Integration
|
|
|
|
|
|
|
|
|
|
`xtc_graph_add_native` adds effects (input 0/output 1) or sources (output 1),
|
|
|
|
|
with a scalar immutable parameter. Compilation creates two independent mono
|
|
|
|
|
instances for stereo; no mutable state is shared between sides. Graph-only edits
|
|
|
|
|
retain the existing pending/retired publication contract. Reset clears native
|
|
|
|
|
state and its graph-local frame clock; graph positions count successfully
|
|
|
|
|
rendered frames since compilation/reset, independently of backend
|
|
|
|
|
seek/transport. Sources needing transport time should be hosted explicitly with
|
|
|
|
|
a block position.
|
2026-10-27 01:21:04 +01:00
|
|
|
|
|
|
|
|
For an instrument, use create/process directly with sample-offset events, then
|
|
|
|
|
feed its output to an existing XTC_NODE_INSTRUMENT through process_sources or
|
2026-11-06 15:20:20 +01:00
|
|
|
process_stereo. The host owns event routing and panic/reset delivery.
|
|
|
|
|
Graph-native instrument addition returns UNSUPPORTED because current graph ports
|
|
|
|
|
carry audio, not MIDI. This separation avoids silently dropping instrument
|
|
|
|
|
events. The built-in JACK MIDI sine instrument remains the GUI's instrument;
|
|
|
|
|
custom instrument selection and event graph ports are future work.
|
2026-10-27 01:21:04 +01:00
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
Descriptors survive graph/project cloning and transactional remove/connect
|
|
|
|
|
edits; custom ADD_NODE commands use the dedicated constructor rather than a GUI
|
|
|
|
|
menu. Native nodes have no project-file representation: Save returns UNSUPPORTED
|
|
|
|
|
and preserves the destination. Format v4 still accepts only builtin types 1..6.
|
|
|
|
|
A durable identifier/registry, versioned processor state and missing-module
|
|
|
|
|
policy must precede serialized native extensions or external plugin hosting.
|
2026-10-27 01:21:04 +01:00
|
|
|
|
|
|
|
|
Tests cover ABI/size/field rejection, descriptor copying, aliased buffers,
|
|
|
|
|
prepare/process failures, stereo compile cleanup, retirement/destruction outside
|
2026-11-06 15:20:20 +01:00
|
|
|
processing, event offsets, seek note reset, source ports, instrument-buffer
|
2026-11-06 19:05:14 +01:00
|
|
|
graph routing and unsupported-save preservation. Run `cmake --build build --target check`.
|
2026-10-27 03:48:46 +01:00
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Structured Parameter Processors
|
2026-10-27 03:48:46 +01:00
|
|
|
|
|
|
|
|
`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
|
2026-11-06 19:05:14 +01:00
|
|
|
instrument using only public headers; `cmake --build build --target test-processor` builds and exercises
|
2026-10-27 03:48:46 +01:00
|
|
|
both. The instrument has immediate note-off and no voice stealing/release tail.
|
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
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.
|
2026-10-27 03:48:46 +01:00
|
|
|
|
|
|
|
|
Create prepares a copied descriptor on the control thread. Failed preparation
|
|
|
|
|
calls destroy, including with NULL: callbacks must implement partial cleanup.
|
2026-11-06 15:20:20 +01:00
|
|
|
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.
|
2026-10-27 03:48:46 +01:00
|
|
|
|
|
|
|
|
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.
|
2026-11-06 15:20:20 +01:00
|
|
|
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
|
2026-10-27 03:48:46 +01:00
|
|
|
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.
|
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
`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.
|