sOURnd Docs Your engines
On this page
  1. Life of an engine
  2. Rules
  3. Status codes
  4. Header

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

  1. abi_versioncheck it first
  2. create
  3. prepare
  4. load_stateif there is one
  5. save_state
  6. destroy

Audio thread, real-time safe

  1. set_param
  2. processno more frames than prepared
  3. latency, tail_seconds and the readouts

Rules

  • Check sournd_engine_abi_version() equals SOURND_ENGINE_ABI_VERSION before 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_text and the metadata calls.
  • Audio thread, real-time safe: process, process_sidechain, reset, set_param, get_param, latency, tail_seconds and 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.
  • process takes one or two channels, frames no 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_state migrates 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_report counts both for the last load. Only another engine's state or damaged data is refused, and then nothing changes.
  • SOURND_PARAM_LOG marks a log taper: a host proportion p in 0 to 1 is min * (max / min)^p. Without it the range is linear.
  • On SOURND_PANIC the instance is poisoned: only destroy is permitted.

Status codes

CodeNameMeaning
0SOURND_OKSuccess
-1SOURND_INVALIDNull pointer, out-of-range value or bad argument
-2SOURND_NOT_FOUNDUnknown engine id, parameter id or index
-3SOURND_BUFFER_TOO_SMALLOutput buffer too small; query the size first
-4SOURND_NOT_PREPAREDCall prepare before processing
-5SOURND_PANICThe engine failed; destroy the instance
-6SOURND_UNSUPPORTEDLayout or feature not supported by this engine
c
// 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