1
0
Fork 0
forked from hanez/xtc
xtc/docs/NATIVE_API.md

134 lines
8.4 KiB
Markdown

# Native C processor API, version 1
`xtc_native.h` defines a small compile-time extension boundary for trusted C99
sources, effects and instruments. Build `make build/native-gain` and run it for 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.
## Descriptor and ownership
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.
## 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.
The host rejects invalid blocks before changing state/output, and reports
CAPACITY above the prepared block limit. Position arithmetic cannot overflow.
A discontinuous position resets held voices/state before processing. Call reset
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
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.
## 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.
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
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.
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.
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.