C ABI reference
Version 1. A small, versioned C interface to every sournd engine. The JUCE module is a thin wrapper over it; you can call it from any language with a C FFI.
Life of an engine
Control thread, audio stopped
abi_versioncheck it firstcreateprepareload_stateif there is onesave_statedestroy
Audio thread, real-time safe
set_paramprocessno more frames than preparedlatency,tail_secondsand the readouts
Rules
- Check
sournd_engine_abi_version()equalsSOURND_ENGINE_ABI_VERSIONbefore creating an engine. - Serialise every call for one instance. Use one instance per processing thread.
- Control thread, audio stopped:
create,destroy,prepare,prepare_layout,save_state,load_state,value_to_textand the metadata calls. - Audio thread, real-time safe:
process,process_sidechain,reset,set_param,get_param,latency,tail_secondsand the readouts. - Parameter ids are the position of the parameter in the engine's manifest.
- Strings are UTF-8 and NUL terminated. Catalogue strings live for the library lifetime; parameter strings live until
destroy. processtakes one or two channels,framesno more than the prepared maximum. In-place is allowed; output channels must not overlap. Zero frames is a no-op.- Query state size with
save_state(e, NULL, 0, &size), allocate, then save. load_statemigrates across engine versions: saved values are matched by parameter id, parameters the state lacks take their defaults, and ids the engine no longer has are ignored.state_reportcounts both for the last load. Only another engine's state or damaged data is refused, and then nothing changes.SOURND_PARAM_LOGmarks a log taper: a host proportionpin 0 to 1 ismin * (max / min)^p. Without it the range is linear.- On
SOURND_PANICthe instance is poisoned: onlydestroyis permitted.
Status codes
| Code | Name | Meaning |
|---|---|---|
| 0 | SOURND_OK | Success |
| -1 | SOURND_INVALID | Null pointer, out-of-range value or bad argument |
| -2 | SOURND_NOT_FOUND | Unknown engine id, parameter id or index |
| -3 | SOURND_BUFFER_TOO_SMALL | Output buffer too small; query the size first |
| -4 | SOURND_NOT_PREPARED | Call prepare before processing |
| -5 | SOURND_PANIC | The engine failed; destroy the instance |
| -6 | SOURND_UNSUPPORTED | Layout or feature not supported by this engine |
Header
// SPDX-License-Identifier: Apache-2.0
#ifndef SOURND_ENGINE_H
#define SOURND_ENGINE_H
#include <stdint.h>
#include <stddef.h>
#define SOURND_ENGINE_ABI_VERSION 1u
#ifdef __cplusplus
extern "C" {
#endif
typedef struct sournd_engine sournd_engine;
enum { SOURND_OK=0, SOURND_INVALID=-1, SOURND_NOT_FOUND=-2, SOURND_BUFFER_TOO_SMALL=-3,
SOURND_NOT_PREPARED=-4, SOURND_PANIC=-5, SOURND_UNSUPPORTED=-6,
SOURND_QUEUE_FULL=-7 };
/* Descriptor flags. INSTRUMENT: an engine the musician plays with notes (catalogue kind
"instrument"); see the note input section below. */
enum { SOURND_ENGINE_SIDECHAIN=1, SOURND_ENGINE_INSTRUMENT=2 };
/* Note events one instance holds for its next process call. */
#define SOURND_NOTE_QUEUE_CAPACITY 256u
/* LOG (added to ABI version 1 append-only): the parameter has a logarithmic taper, so equal
travel is an equal ratio: value = min * pow(max / min, p) for a proportion p in 0..1. Only
set when 0 < min < max. Without it the taper is linear. Hosts and UIs that ignore the flag
still work; they show a linear range. */
enum { SOURND_PARAM_STEPPED=1, SOURND_PARAM_BOOL=2, SOURND_PARAM_HIDDEN=4, SOURND_PARAM_LOG=8 };
/* UTF-8, NUL terminated. Metadata remains valid until destroy (catalogue info: forever). */
typedef struct { const char *id, *name, *version; uint32_t flags; } sournd_engine_descriptor;
typedef struct { uint32_t id; const char *name; double min, max, default_value;
const char *unit; uint32_t flags; } sournd_param_descriptor;
uint32_t sournd_engine_abi_version(void);
uint32_t sournd_engine_count(void);
int32_t sournd_engine_info(uint32_t index, sournd_engine_descriptor *out);
int32_t sournd_engine_create(const char *id, sournd_engine **out);
int32_t sournd_engine_destroy(sournd_engine *engine);
int32_t sournd_engine_param_count(sournd_engine *engine, uint32_t *out);
int32_t sournd_engine_param_info(sournd_engine *engine, uint32_t index, sournd_param_descriptor *out);
int32_t sournd_engine_value_to_text(sournd_engine *engine, uint32_t id, double value, char *out, size_t capacity);
int32_t sournd_engine_prepare(sournd_engine *engine, double sample_rate, uint32_t max_block);
/* Explicit layout for Processor implementations whose prepare depends on bus width.
prepare() selects stereo and, when declared, stereo sidechain. */
int32_t sournd_engine_prepare_layout(sournd_engine *engine, double sample_rate, uint32_t max_block, uint32_t channels, uint32_t side_channels);
int32_t sournd_engine_reset(sournd_engine *engine);
int32_t sournd_engine_set_param(sournd_engine *engine, uint32_t id, double value);
int32_t sournd_engine_get_param(sournd_engine *engine, uint32_t id, double *out);
/* One or two channels, frames <= prepared max_block. In-place is allowed. Output channels
must not overlap each other. Sidechain can be absent (0 channels), mono or stereo.
Buffers and pointer arrays must be valid for the call. Zero frames is a no-op.
An instrument may be given in=NULL (it hears silence); effects require input. */
int32_t sournd_engine_process(sournd_engine *engine, const float *const *in, float *const *out, uint32_t channels, uint32_t frames);
int32_t sournd_engine_process_sidechain(sournd_engine *engine, const float *const *in, float *const *out, uint32_t channels, const float *const *side, uint32_t side_channels, uint32_t frames);
int32_t sournd_engine_latency(sournd_engine *engine, uint32_t *out);
int32_t sournd_engine_tail_seconds(sournd_engine *engine, double *out);
int32_t sournd_engine_readout_count(sournd_engine *engine, uint32_t *out);
int32_t sournd_engine_readout_value(sournd_engine *engine, uint32_t index, double *out);
/* Note input (instruments). Added to ABI version 1 append-only: effects and existing calls
are unchanged, and a v1 host that never calls these sees no difference.
is_instrument returns 1 for an instrument, 0 for an effect, or a negative status.
note_on/note_off/all_notes_off follow the threading rules of set_param: audio thread (or
any thread that owns the instance), serialised with process, called before the process
call they belong to. They never allocate, lock or block.
Timing: sample_offset is a frame position inside the NEXT process call; 0 is its first
frame. Each event takes effect before the frame at its offset (the engine splits the
block there). Events at the same offset apply in call order; events need not be sent in
offset order. An offset at or past the frame count of the next process call is carried,
not dropped: it takes effect at frame 0 of the call after that. A zero-frame process call
applies and carries nothing.
Arguments: note is a MIDI note number 0..127; velocity is 0..1 (a note_on at velocity 0 is a
note_off, as in MIDI); sample_offset must be below the prepared max_block. Status codes:
SOURND_INVALID for a NULL engine or a bad note, velocity (NaN, infinite, outside 0..1) or
offset; SOURND_UNSUPPORTED on an effect; SOURND_NOT_PREPARED before prepare;
SOURND_QUEUE_FULL when SOURND_NOTE_QUEUE_CAPACITY events already wait for the next process
call: that event is refused and nothing else changes (send all_notes_off to recover).
all_notes_off ends every note at once and drops every queued event; it works prepared or
not (SOURND_UNSUPPORTED on an effect). prepare, prepare_layout and reset also drop queued
events. */
int32_t sournd_engine_is_instrument(sournd_engine *engine);
int32_t sournd_engine_note_on(sournd_engine *engine, uint32_t note, float velocity, uint32_t sample_offset);
int32_t sournd_engine_note_off(sournd_engine *engine, uint32_t note, uint32_t sample_offset);
int32_t sournd_engine_all_notes_off(sournd_engine *engine);
/* Control thread, audio stopped. Query required length using out=NULL, capacity=0.
State is engine-specific and versioned; it preserves settings, not delay history.
Loading migrates across engine versions: saved parameters are matched by id (never by
position or count), a current parameter the state lacks takes its default, and a saved id
this version does not have is ignored. Loading refuses the whole state (SOURND_INVALID,
nothing changes) only on a genuine clash: another engine's id, a value that is not finite,
outside the parameter's range or fractional for a stepped or switch parameter, an id saved
twice, or a truncated or malformed envelope.
state_report (added to ABI version 1 append-only) gives what the last successful load
migrated: ignored = saved ids this version does not have, defaulted = parameters set to
their default. Both are 0 before any load. */
int32_t sournd_engine_save_state(sournd_engine *engine, uint8_t *out, size_t capacity, size_t *written);
int32_t sournd_engine_load_state(sournd_engine *engine, const uint8_t *data, size_t length);
int32_t sournd_engine_state_report(sournd_engine *engine, uint32_t *ignored, uint32_t *defaulted);
/* Ownership: serialise ALL calls for an instance. create/destroy/prepare/state/text/metadata
are control-thread operations; process/reset/set/get/latency/tail/readouts and the note
calls are RT safe for conforming engines. Null is rejected (destroy(NULL) succeeds). Dangling, fabricated,
misaligned pointers and concurrent access violate the contract and cannot be checked.
On PANIC the instance is poisoned: only destroy is permitted. Build with panic=unwind. */
#ifdef __cplusplus
}
#endif
#endif