C ABI (language bindings)

Contents

C ABI (language bindings)#

libYSE ships a flat extern "C" API alongside the C++ one, folded into the same shared library by the YSE_BUILD_C_API=ON CMake option (default on). It exists so language bindings — Dart FFI, Python ctypes, etc. — can consume libyse.dll / libyse.so without C++ ABI compatibility.

Single entry point:

#include "yse_c/yse_all.h"

This umbrella header pulls in every subsystem header below. The headers compile as C99 or later, and as C++. Several of them forward-declare the same handle type; each of those typedefs sits behind a YSE_C_HANDLE_<Type> guard, so a strict C99 compiler never sees one twice. The test build compiles every header, and yse_all.h, as -std=c99 -pedantic-errors.

The first half of this page is a guide: the rules every function follows, in one place, so a binding can be written against them. The second half is the reference, generated from the headers. The patcher has its own guide, Embedding through the C API.

Guide#

A first session#

#include <stdio.h>
#include "yse_c/yse_all.h"

int main(void) {
  YseSystem* sys = yse_system_get();           /* borrowed: never destroyed */

  yse_system_request_sample_rate(sys, 48000);  /* before init; 0 clears it */
  if (yse_system_init(sys) != YSE_OK) {         /* YSE_ERR_AUDIO_DEVICE: no device opened */
    fprintf(stderr, "init: %s\n", yse_last_error());
    return 1;
  }

  YseSound* snd = yse_sound_create();          /* owned: a handle, not yet a sound */
  YseStatus st = yse_sound_load_file(snd, "drums.ogg", yse_channel_master(), 1, 0.8f, 0);
  if (st != YSE_OK) {
    /* snd is still a live handle: its calls are no-ops until a load works */
    fprintf(stderr, "load (%d): %s\n", (int)st, yse_last_error());
  } else {
    yse_sound_play(snd);
  }

  for (int frame = 0; frame < 600; ++frame) {
    yse_system_update(sys);                    /* same thread as init, once per frame */
    yse_system_sleep(sys, 16);
  }

  yse_sound_destroy(snd);
  yse_system_close(sys);
  return 0;
}

yse_system_init() returns YSE_OK only when a stream on the default output device is running. With no device, or one that refuses the stream, it returns YSE_ERR_AUDIO_DEVICE and leaves no session behind; call yse_system_init_offline() to run the engine without a device. Sessions and devices covers sessions, sample rates and devices, and Threading model explains what yse_system_update() does on your thread.

Handles and ownership#

Every object crosses the ABI as a pointer to an opaque struct (YseSound*, YseChannel*, …). The comment on each typedef says which of these kinds it is:

Kind

Rule

Owned

You created it with a yse_<type>_create* function, and you release it with the matching yse_<type>_destroy. The library never frees it for you. Every destroy function accepts NULL.

Borrowed

The engine owns it: the singletons (yse_system_get, yse_listener_get, yse_log_get), the pre-built channels (yse_channel_master and its siblings), the global reverb, and the device descriptors from yse_system_get_device. Never destroy one.

Owned by a parent

A YsePHandle belongs to its YsePatcher. Remove it with yse_patcher_delete_object; it goes away with the patcher.

Reference-counted asset

YseSfzInstrument and YseDx7Bank. A synth keeps its own share, so you may destroy the handle right after handing it over, and before or after yse_system_close(). A double destroy, or a call on a destroyed handle, is logged and ignored instead of crashing. No other handle type has this protection.

Destroy a handle with its own type’s function. A YseSound* goes to yse_sound_destroy, a YseDspObject* to yse_dsp_object_destroy, and so on. The handles are distinct C types, so passing one to another type’s destroy needs a cast and is undefined behaviour. Two handle types cover a family, and each family has one destroy for all its members:

  • yse_dsp_buffer_destroy releases every YseDspBuffer, whichever of the four constructors made it. The handle remembers its constructor, so destroy frees the right object (#662).

  • yse_dsp_object_destroy releases every effect, including a patcher insert. Destroying an insert never destroys the patcher it wraps.

Lifetimes that you enforce. Several calls keep a pointer to an object you own, and the library does not track it. The object must outlive its user:

  • A YseDspBuffer, YseDspMultiBuffer or YsePatcher loaded into a sound must outlive the sound. Destroy the sound first.

  • A synth attached to a sound, or connected to a YseMidiIn or MIDI file, must outlive that connection.

  • An effect set with yse_sound_set_dsp or yse_channel_set_dsp is borrowed by the sound or channel: the audio thread reads it. Detach it (set_dsp(..., NULL)) or destroy the owner before destroying the effect.

  • A patcher insert borrows its patcher.

yse_system_close() frees the engine’s side of every sound and channel, but never your handles. Bus taps and subscriptions stop firing at close and do not come back after a new init; release them and create new ones.

Handles and loaded objects#

A handle that is not NULL is not necessarily usable yet. A YseSound* from yse_sound_create is an empty shell until a yse_sound_load_* call succeeds. Until then, and after a failed load, its setters and transport calls do nothing and its queries return 0 (#579). They never crash. yse_sound_is_valid() returns 1 only once a load has succeeded, so it tells the two states apart. Channels, synths, reverbs and SFZ instruments have an _is_valid function too; it returns 1 only when a live engine object stands behind the handle.

The general rule for a NULL handle is the same:

  • A void function (a setter, play, stop) does nothing.

  • A query returns 0, false or NULL. Where 0 is itself a real answer the header names a sentinel instead, for example YSE_PATCHER_ID_NONE.

  • A function that returns YseStatus returns YSE_ERR_INVALID_HANDLE.

A dangling handle (one you already destroyed) is not detected, except for the reference-counted assets above. Clear your own pointer when you destroy.

The DSP subclass contract#

YseDspBuffer stands for four engine classes that form one chain: buffer ← drawableBuffer ← fileBuffer ← wavetable. The chain has no virtual functions, so the library cannot check at run time which one a handle is. Functions for a subclass (yse_dsp_buffer_draw_line, yse_dsp_buffer_load_file, yse_dsp_wavetable_create_saw, …) static_cast the handle and trust you (#582):

  • A handle works with the functions of its own class and of every class above it. A wavetable handle may be drawn on and filled; a file buffer handle may be drawn on.

  • Passing a handle to a function of a class below it (a plain yse_dsp_buffer_create handle to yse_dsp_wavetable_create_saw) is undefined behaviour. The call returns YSE_OK and writes through the wrong type.

YseDspBuffer* wt = yse_dsp_wavetable_create(2048);
yse_dsp_wavetable_create_saw(wt, 32, 2048);  /* wavetable entry point: fine */
yse_dsp_buffer_draw_flat(wt, 0, 16, 0.0f);   /* drawableBuffer is above it: fine */
yse_dsp_buffer_fill(wt, 0.0f);               /* buffer is above it: fine */
yse_dsp_buffer_destroy(wt);                  /* one destroy for all four */

YseDspBuffer* plain = yse_dsp_buffer_create(2048, 0);
/* yse_dsp_wavetable_create_saw(plain, 32, 2048);   undefined behaviour */
yse_dsp_buffer_destroy(plain);

A binding enforces this by remembering which constructor made each handle, for example with one wrapper class per subclass. The effect handles in yse_dsp_modules.h follow the same contract: yse_dsp_object_* functions accept any effect, and a module’s own setters (such as yse_dsp_lowpass_set_frequency) need a handle from that module’s constructor.

Errors#

Functions that can fail in a way you need to know about return YseStatus. Loaders and constructors that return a handle return NULL on failure instead.

Status

Meaning

YSE_OK (0)

Success.

YSE_ERR_INVALID_HANDLE

The handle argument was NULL. That is all it means: it is never a type check, and a handle of the wrong subclass is not reported (see above). One call adds a case, and says so: yse_synth_fm_set_patch also returns it for a synth that has no FM voice group.

YSE_ERR_INVALID_ARGUMENT

A NULL or empty string, an out-of-range index, a duplicate name, lengths that do not match.

YSE_ERR_NOT_INITIALIZED

The call needs a running engine, for example a bus publish before yse_system_init.

YSE_ERR_FILE_NOT_FOUND

A sound file could not be loaded.

YSE_ERR_AUDIO_DEVICE

yse_system_open_device could not open the setup you gave it. The running stream is left alone.

YSE_ERR_EXCEPTION

The engine threw a C++ exception. The library catches it at the ABI boundary and reports it as this status.

YSE_ERR_GENERIC

Any other failure.

The last error. yse_last_error() returns a message about the most recent failure:

  • Every error status except YSE_ERR_INVALID_HANDLE comes with a message, and so does a constructor or loader that fails and returns NULL. A NULL handle may return YSE_ERR_INVALID_HANDLE without one. Test the status first, and read the message second.

  • A void function that catches an exception also records the message, though it has no status to return.

  • The message is kept per thread. A failure on another thread never overwrites yours.

  • A successful call does not clear it. The message stays until the next failure on the same thread, or until you call yse_clear_last_error(). Reading yse_last_error() after a call that returned YSE_OK can show an old message.

  • The returned pointer belongs to the library and is valid until the next yse_* call on the same thread. Copy the text if you keep it.

Strings#

Strings are NUL-terminated UTF-8 in both directions. Strings you pass in are copied before the call returns unless the header says otherwise. Strings come back in one of three ways:

  • Into your buffer, snprintf-style. Functions shaped size_t f(..., char* buf, size_t cap) write at most cap - 1 bytes and a NUL, and return the full length. Pass buf = NULL, cap = 0 to ask for the size first. A return value of cap or more means the text was cut short. On a NULL handle they write an empty string and return 0.

  • Owned by the library. yse_version(), yse_last_error() and the patcher metadata getters return const char* that you never free.

  • Owned by you. yse_patcher_get_metadata_json() returns a char* that you release with yse_free_string(). Callbacks that pass you a string you own say so, and name the function that frees it (see Callbacks).

Bound-checked getters#

Getters that take an index check it against the matching count, so a count that changed since you read it cannot make them read out of bounds (#565, #581):

  • yse_system_get_device(sys, i) returns NULL and sets the last error when i is not below yse_system_num_devices(). An offline session lists no devices until yse_system_refresh_devices() enumerates them (#1026), and a refresh replaces the list, so device pointers from before it are stale.

  • The device descriptor getters (channel names, sample rates, buffer sizes) return an empty string or 0 for an index out of range, the same as for a NULL descriptor.

  • The MIDI device name getters return an empty string for an id with no device, so a name that is not empty always means a real port.

A NULL descriptor is accepted too, so a loop over devices needs no extra checks:

unsigned int n = yse_system_num_devices(sys);
for (unsigned int i = 0; i < n; ++i) {
  YseDevice* dev = yse_system_get_device(sys, i);  /* borrowed; NULL if i is stale */
  char name[128];
  size_t len = yse_device_get_name(dev, name, sizeof name);
  printf("%u: %s%s\n", i, name, len >= sizeof name ? " (truncated)" : "");
}

Threading#

The C API follows the engine’s threading model, described in Threading model. In short:

  • Call yse_system_init, yse_system_update and yse_system_close from one thread, the control thread, and make the object calls from there too. The functions marked “control thread” in the headers (clocks, clips, bus subscriptions, yse_midi_in_connect_synth, yse_patcher_set_name, …) must be called from it.

  • The patcher is the exception: several of your threads may drive one patcher at once. See Real-time model.

  • Never call the library from a real-time audio callback of your own.

  • yse_last_error() is per thread, so read it on the thread that made the failed call.

Callbacks#

Callback typedefs carry YSE_C_CALLBACK, which fixes the calling convention (__cdecl on Windows, nothing elsewhere). Declare your functions the same way or use the typedef. Except for the synth note hook, every callback comes with a void* user_data that the library passes back untouched. Installing a new callback replaces the old callback and user_data together: no call ever receives one install’s callback with another’s user_data. A call already in progress on another thread may still finish with the old pair, so keep the old user_data alive until it has.

Each callback runs on a known thread:

Callback

Thread

Rules

YseLogCallback (yse_log_set_callback)

Whichever engine thread logged: the control thread, the background pool, a MIDI thread, a render worker while it starts. Never the audio thread.

You own msg; release it with yse_log_free_message. Calls are one at a time, under the engine’s log lock, so they never overlap. Do not call the library from inside it (see below). Do not block.

YseOcclusionCallback (yse_system_set_occlusion_callback)

The thread that calls yse_system_update().

May lock, allocate and query a physics engine. The positions are copies, valid for the call only.

YseBusTapCallback, YseBusSubCallback

The control thread: inline for a publish made on it, otherwise during yse_system_update().

Every pointer is valid for the call only. Copy what you keep. Create and release taps and subscriptions on the control thread; then none fires after the release returns.

YseScriptErrorCallback

The thread that calls yse_system_update(). On a build without Python, inside yse_python_run_script.

The traceback is valid for the call only.

YsePatcherSendCallback

The thread that sent the message: yours for a yse_patcher_pass_* call, the patcher timer thread for a .metro. Never the audio thread.

You own address (and s with it); release it with yse_patcher_free_message(address). yse_patcher_destroy waits for calls in progress, so do not destroy the patcher from inside its own callback.

YseMidiInRawCallback, YseMidiInParsedCallback

RtMidi’s input thread for that port.

Return quickly. You own the raw callback’s bytes; release them with yse_midi_in_free_message. The parsed callback transfers nothing. Do not call yse_midi_in_connect_synth from inside either.

YseSynthNoteCallback (yse_synth_set_note_callback)

The thread that renders the synth: the audio thread or a render worker.

Real-time rules: no allocation, no locks, no I/O, no library calls. There is no user_data; reach your state through globals.

The log callback must not call back into the library. The engine holds its log lock while your callback runs. A call that logs, or one that takes the same lock (yse_log_send_message, yse_log_set_callback, yse_log_set_logfile), waits for that lock on the thread that already holds it, and the program hangs. Most yse_* functions can log, so the safe rule is: copy or queue the message, free it, and return.

static void on_log(char* msg, void* user_data) {
  FILE* out = (FILE*)user_data;
  fprintf(out, "%s\n", msg);    /* no yse_* call in here except the free below */
  yse_log_free_message(msg);    /* the callback owns msg */
}

yse_log_set_callback(yse_log_get(), on_log, stderr);

Language bindings that marshal callbacks asynchronously (Dart’s NativeCallable.listener, for one) are the reason several callbacks hand you an allocated copy: the string is still valid when your handler runs later, and you free it there.

MIDI channel numbers#

The API counts MIDI channels in two ways. yse_midi_out_* and the parsed MIDI input callback use the wire value, 0 to 15. The synth calls, clip events and the channel_filter of yse_midi_in_connect_synth use 1 to 16, and 0 there means every channel. MIDI channel 1 is therefore 0 on a yse_midi_out_* call and 1 on a synth or clip call.

Changes since v2.4.0#

The C ABI breaks once, with v3.0. Update a v2.4.0 binding for these changes.

Signatures that changed:

Function or type

Change

yse_patcher_init, yse_patcher_parse_json

void → YseStatus. Malformed JSON now returns an error, and parsing before init returns YSE_ERR_NOT_INITIALIZED.

yse_system_create_clock, yse_clip_bind

int (1 = success) → YseStatus (0 = success). The meaning of 0 is reversed.

yse_channel_get_name

Returned const char*; now copies into your buffer, snprintf-style.

yse_run_script, yse_set_script_error_callback

Renamed yse_python_run_script, yse_python_set_script_error_callback.

yse_bus_tap_cb, yse_bus_sub_cb, yse_script_error_cb

Renamed YseBusTapCallback, YseBusSubCallback, YseScriptErrorCallback.

yse_free_string

Moved from yse_patcher.h to yse_common.h. Same symbol.

yse_system_open_device

Returns YSE_ERR_AUDIO_DEVICE when the engine refuses the setup; it used to return YSE_OK.

Functions added:

Area

Functions

Session

yse_system_request_sample_rate, yse_system_get_requested_sample_rate, yse_system_set_channel_configuration, yse_system_set_render_threads, yse_system_get_render_threads, yse_system_get_active_render_threads, yse_block_size, yse_randomize, yse_random_seed, yse_system_set_occlusion_callback, yse_device_setup_get_output_channels

Named bus

yse_bus_publish_bang / _int / _float / _string / _list, yse_bus_subscribe, yse_bus_unsubscribe, yse_sound_set_name, yse_channel_set_name

Sounds and buffers

yse_sound_load_multi_buffer, yse_dsp_multi_buffer_create / _destroy / _get_channel_count, yse_dsp_buffer_add_buffer / _sub_buffer / _mul_buffer / _div_buffer, yse_dsp_buffer_copy_from, yse_dsp_buffer_swap, yse_dsp_buffer_get_file_sample_rate, yse_buffer_io_exists, yse_buffer_io_remove

Effects and reverb

yse_dsp_underwater_create / _set_depth / _get_depth, yse_dsp_object_get_next, yse_reverb_preset_get_values, yse_reverb_preset_morph

Patcher

yse_patcher_set_name, yse_patcher_get_name, yse_patcher_set_container, yse_patcher_get_container, yse_patcher_subpatcher_inlets / _outlets, yse_patcher_set_send_callback, yse_patcher_free_message, yse_phandle_get_gui_value_count, yse_phandle_get_gui_value_at, yse_phandle_gui_value_is_settable

Synth, MIDI and music

yse_synth_note_on_note, yse_synth_note_off_note, yse_dx7_import_sysex_memory, yse_dx7_find_patch, yse_midi_out_raw, yse_pnote_set_channel, yse_pnote_get_channel

Enums. YsePCategory gained values 8 to 18. Existing values keep their numbers; enum values are only ever appended.

Reference#

Umbrella header#

yse_all.h — umbrella header for the C ABI surface.

Single entry-point consumed by Dart ffigen and any other C ABI client.

System and listener#

yse_system.h — engine lifecycle, audio device control, global settings.

C ABI mirror of YseEngine/system.hpp (YSE::system class + YSE::System() singleton accessor).

Typedefs

typedef struct YseSystem YseSystem#

Borrowed singleton — owned by the engine, never destroy.

Obtain via yse_system_get().

typedef float (*YseOcclusionCallback)(const yse_pos_t *src, const yse_pos_t *listener, void *user_data)#

Sound occlusion (issue #906).

The callback returns how much geometry sits between src and listener, 0 (clear line of sight) to 1 (fully blocked); the result is clamped to that range and applied as a gain duck (gain *= 1 - occlusion) to every sound with yse_sound_set_occlusion(s, 1). Typical hosts raycast through their world geometry here.

Threading: the callback runs on the thread that calls yse_system_update(), once per occlusion-enabled sound per update — never on the audio callback thread, so it may lock, allocate or query a physics engine. src and listener point to stack copies valid only for the duration of the call; positions are already multiplied by the engine’s distance factor.

There is one occlusion callback per process. Installing replaces the previous callback + user_data as one unit; NULL cb removes it (sounds then keep the last occlusion value they received). Install and remove may be called from any host thread, including from inside the callback itself. A dispatch never pairs one install’s callback with another’s user_data, and every update that starts after the install returns uses the new pair; an update already running on another thread may finish its current call with the old one, so keep old user_data alive until that update has returned. A NULL sys is a no-op.

Functions

YseSystem *yse_system_get(void)#

Borrowed singleton pointer — never destroy.

YseStatus yse_system_init(YseSystem *sys)#

Lifecycle.

yse_system_init() returns YSE_OK only when a stream on the default output device is running. With no default device, or one that refuses to open or start the stream, it returns YSE_ERR_AUDIO_DEVICE and leaves the engine closed; yse_system_init_offline() runs the engine without a device (issue #973).

YseStatus yse_system_init_offline(YseSystem *sys)#
void yse_system_render_offline(YseSystem *sys, int blocks)#
void yse_system_update(YseSystem *sys)#
void yse_system_close(YseSystem *sys)#
void yse_system_pause(YseSystem *sys)#
void yse_system_resume(YseSystem *sys)#

Restarts the device this session already had.

A session brought up with yse_system_init_offline() has none, so this is a no-op there rather than an open of the platform default; yse_system_open_device() is the deliberate way to give such a session a device (issue #719).

int yse_system_missed_callbacks(YseSystem *sys)#

Diagnostics.

yse_system_missed_callbacks() counts consecutive yse_system_update() ticks during which the device delivered no audio callback; 0 means audio is flowing right now. A stream that has just been started reads non-zero until its first callback lands (the backend’s start call returns before the device runs), so poll this until it reaches 0 to wait for a device to come up — a value that keeps climbing is a starved audio thread or a disconnected device. yse_system_auto_reconnect() does not act on this counter; it distinguishes start-up from a stall itself (issue #681).

float yse_system_cpu_load(YseSystem *sys)#
void yse_system_request_sample_rate(YseSystem *sys, unsigned int rate_hz)#

Request the audio sample rate for the next engine session (issue #646).

The sample rate is an application setting, fixed per session: call this before yse_system_init / yse_system_init_offline. The device may refuse or negotiate a different rate — the negotiated result stays authoritative and is reported by yse_system_get_sample_rate(); a refused request is a log line plus the negotiated fallback, never an error. Offline / headless sessions run exactly at the requested rate. Pass 0 to clear the request (the backend then opens at the device default; the pre-negotiation initial value is 48000). Changing a running session’s rate means yse_system_close() followed by a new init; calling this mid-session only stores the request for the next session. The request survives close.

unsigned int yse_system_get_requested_sample_rate(YseSystem *sys)#

Currently requested sample rate in Hz, or 0 when no request is set.

YseStatus yse_system_set_render_threads(YseSystem *sys, int count)#

Render thread count (issue #861): how many worker threads help the audio thread render the mix.

count < 0 (default -1) is auto — physical cores minus one, capped at 8; 0 is serial — the audio thread renders alone and no worker is started (constrained hardware, Android); n > 0 is exactly n workers (at most 64). Whatever the count, no worker is woken for a block estimated to cost less than the wake (in real-time rendering the audio thread renders it alone); the setting decides only which thread renders each part of the mix, never how it is summed. Call before yse_system_init / yse_system_init_offline: with no session or an offline one it applies at once; during a device session it is stored for the next init. The setting survives close. Returns YSE_ERR_EXCEPTION (see yse_last_error) if the workers could not be started.

yse_system_get_render_threads() returns the setting (-1 = auto); yse_system_get_active_render_threads() the workers currently running. Both return 0 on a NULL handle.

int yse_system_get_render_threads(YseSystem *sys)#
int yse_system_get_active_render_threads(YseSystem *sys)#
double yse_system_get_sample_rate(YseSystem *sys)#

Engine session sample rate in Hz.

Stays constant for the lifetime of an init()/close() session, including across pause/resume cycles where the live “active” rate transiently drops to 0. Returns 0 before init(). Use this for sample-count-driven scheduling that must outlive a pause; use yse_system_get_active_sample_rate() for live device-state UI.

double yse_system_get_active_sample_rate(YseSystem *sys)#

Live state of the currently open audio device.

Returns 0 when no device is open (pre-init, after close, or initOffline path). Buffer size is the device’s frames-per-callback, NOT the engine block size. Output latency is in samples; convert to ms with (latency / sample_rate) * 1000.

int yse_system_get_active_buffer_size(YseSystem *sys)#
int yse_system_get_active_output_latency(YseSystem *sys)#
void yse_system_sleep(YseSystem *sys, unsigned int ms)#

Convenience.

void yse_system_set_max_sounds(YseSystem *sys, int value)#

Settings.

int yse_system_get_max_sounds(YseSystem *sys)#
void yse_system_audio_test(YseSystem *sys, int on)#
void yse_system_auto_reconnect(YseSystem *sys, int on, int delay_ms)#

Re-open the audio output stream when the device stops delivering callbacks (headphones unplugged, device removed).

delay_ms is what its name has always said and now is: milliseconds of silence before an attempt, and the interval between further attempts while the device stays unavailable. Negative values are clamped to 0. Up to and including v2.4.0 the value was compared against a count of yse_system_update() calls instead, so the same number meant a different wait per host (issue #681).

A stream that has been started but has not delivered its first callback yet is not stalled, and is never torn down for it: it gets a half-second start-up grace regardless of delay_ms.

The stream is reopened on the device the session had (issue #1027): the device of the last successful yse_system_open_device(), looked up again by name and host, with the channel count and buffer size it was opened with. A session that never opened a device reopens the current default device. When the session’s device cannot be reopened nothing is opened and the watchdog tries again later. Same as yse_system_auto_reconnect_config() with no fallback and no attempt limit.

void yse_system_auto_reconnect_config(YseSystem *sys, int on, int delay_ms, int fallback_to_default, int max_attempts)#

yse_system_auto_reconnect() with a fallback policy and an attempt limit (issue #1027).

fallback_to_default != 0 opens the default output device when the session’s own device cannot be reopened (the speaker layout is narrowed if the default device has fewer outputs); the session does not move back by itself when its device returns. max_attempts > 0 stops the watchdog after that many consecutive failed attempts, with the state then reading YSE_RS_FAILED; 0 or less retries forever. Every call resets the state to YSE_RS_NONE and re-arms a watchdog that gave up.

On desktop the device is looked up in the audio backend’s enumeration as it stands; a device unplugged and plugged back in may only be found after yse_system_refresh_devices(), which a host can call while the state reads YSE_RS_RECONNECTING (no stream is open then).

YseReconnectState yse_system_get_reconnect_state(YseSystem *sys)#

What the reconnection watchdog last did, and how many attempts it has made this session (successful or not; reset by init).

The state goes back to YSE_RS_NONE on init, a successful yse_system_open_device() and every auto_reconnect call. Both may be read from any thread. A NULL handle reads YSE_RS_NONE / 0.

unsigned int yse_system_get_reconnect_count(YseSystem *sys)#
size_t yse_system_active_device(YseSystem *sys, char *buf, size_t cap)#

Name and host API of the device whose output stream is open right now — empty when none is.

Compare with yse_device_get_name() / yse_device_get_type_name() to see which device the session is actually on, e.g. after a fallback (issue #1027). snprintf-style: returns the full length, writes at most cap - 1 bytes plus NUL. Call from the thread that drives yse_system_update().

size_t yse_system_active_host(YseSystem *sys, char *buf, size_t cap)#
unsigned int yse_system_num_devices(YseSystem *sys)#

Devices.

Returned YseDevice* pointers are borrowed from the engine and must not be destroyed. See yse_device.h for the descriptor accessors.

yse_system_get_device() is bound-checked: an idx at or beyond yse_system_num_devices() returns NULL and sets the last error, so probing or iterating with a stale count is safe. yse_system_init() enumerates the devices; yse_system_init_offline() enumerates none, so an offline session lists nothing until yse_system_refresh_devices() is called, and a headless host may list nothing at all. An empty list makes index 0 out of range.

YseDevice *yse_system_get_device(YseSystem *sys, unsigned int idx)#
YseStatus yse_system_refresh_devices(YseSystem *sys)#

Enumerate the audio devices without opening a stream (issue #1026).

Valid before yse_system_init(), on an offline session and on a running device session. On desktop it starts the audio backend if it is not running, which also lets yse_system_open_device() work on an offline session in a process that never called yse_system_init(). With no stream open the backend is restarted so hot-plugged devices show up; an open stream is never touched, and the list is then re-read from the backend’s current enumeration.

The list is replaced: YseDevice pointers obtained earlier, and setups built from them, are invalid afterwards, and an index may now name a different device. Resolve a stored device by name and host again.

Returns YSE_OK when the list is current (it may be empty), or YSE_ERR_AUDIO_DEVICE when the backend could not start, with an empty list, the failure in yse_last_error() and the backend’s own error text in the log. Starting PortAudio can hang a bare headless Linux runner, so headless tools should not call this.

YseStatus yse_system_open_device(YseSystem *sys, const YseDeviceSetup *setup, YseChannelType layout)#

yse_system_open_device() returns YSE_OK only when a stream was opened for setup.

A setup the engine refuses — no output device, an output device whose id is -1 or no longer resolves, a stream open error, or an offline session with no audio backend — returns YSE_ERR_AUDIO_DEVICE and sets yse_last_error(); the running stream and the speaker layout are left untouched (issue #900).

On desktop, yse_system_init_offline() does not start the audio backend and open_device does not start it on demand: call yse_system_refresh_devices() first to start it and pick the device from a current list. The stream opens at the locked session rate; a device that refuses that rate is a logged YSE_ERR_AUDIO_DEVICE (issue #972).

void yse_system_close_current_device(YseSystem *sys)#
void yse_system_set_channel_configuration(YseSystem *sys, YseChannelType layout, int outputs)#

Set the speaker layout without opening a device (issue #668).

yse_system_open_device() derives the layout from the device it actually opened, so a session with no device — yse_system_init_offline(), headless CI, yse_system_render_offline() benchmarks — otherwise stays on the stereo layout init installs. Call this after init/init_offline (initialisation installs the stereo default itself, so an earlier call is overwritten); the next audio callback or render_offline block picks the new layout up and resizes the mixer to match.

outputs is the number of output channels and must be at least 1; a zero-output layout would silence the engine, so it is refused with a log line. YSE_CT_AUTO derives a layout from outputs; YSE_CT_CUSTOM allocates outputs speakers whose positions are set afterwards.

size_t yse_system_default_device(YseSystem *sys, char *buf, size_t cap)#
size_t yse_system_default_host(YseSystem *sys, char *buf, size_t cap)#
unsigned int yse_system_num_midi_in_devices(YseSystem *sys)#

MIDI devices (Windows / Linux only — Android builds report 0).

The name getters write an empty, NUL-terminated buffer and return 0 when there is no such device: an id at or past the matching count, or any id at all on a host whose MIDI backend failed to initialise (issue #585). A non-empty name therefore always means a real port.

unsigned int yse_system_num_midi_out_devices(YseSystem *sys)#
size_t yse_system_midi_in_device_name(YseSystem *sys, unsigned int id, char *buf, size_t cap)#
size_t yse_system_midi_out_device_name(YseSystem *sys, unsigned int id, char *buf, size_t cap)#
YseStatus yse_system_create_clock(YseSystem *sys, const char *name, float initial_tempo)#

Domain clocks (issue #249).

A set of named musical (beat) clocks, each a beat accumulator derived from the audio callback: every audio block a clock advances by blockSeconds * tempo / 60 at its current tempo, so beat position is the running integral of tempo (no absolute-time schedule). All clocks derive from the single sample clock, keeping polytemporal relationships exact. Tempo is a playable, rampable control and is not clamped: 0 pauses a clock, a negative tempo runs it backwards.

Threading: create/destroy/set_tempo are control-thread calls; beat_position and current_tempo may be read from the UI thread at frame rate. None run on or block the audio callback. name is a NUL-terminated UTF-8 string. YSE_OK on success; YSE_ERR_INVALID_HANDLE on a NULL sys, YSE_ERR_INVALID_ARGUMENT when name is NULL or empty or a live clock already owns it (first registration wins — the existing clock is left unchanged). Every failure leaves its reason in yse_last_error().

void yse_system_destroy_clock(YseSystem *sys, const char *name)#

Destroy the named clock.

No-op for an unknown name or NULL args.

int yse_system_clock_exists(YseSystem *sys, const char *name)#

Returns 1 if a live clock with name exists, else 0.

void yse_system_set_tempo(YseSystem *sys, const char *name, float bpm, float ramp_seconds)#

Ramp the named clock’s tempo toward bpm over ramp_seconds (0 = instant).

No-op for an unknown name or NULL args.

double yse_system_beat_position(YseSystem *sys, const char *name)#

Current beat position (running integral of tempo) of the named clock, or 0 for an unknown name or NULL args.

float yse_system_current_tempo(YseSystem *sys, const char *name)#

Current tempo in BPM of the named clock, or 0 for an unknown name or NULL args.

YseReverb *yse_system_get_global_reverb(YseSystem *sys)#

Global reverb — fallback wherever no positioned reverb zone reaches.

Returned pointer is borrowed; never destroy.

void yse_system_underwater_fx(YseSystem *sys, const YseChannel *target)#

Underwater effect: attaches the engine’s underwater insert module to the channel’s insert slot (the same slot yse_channel_set_dsp uses; the two replace each other).

Depth is the listener’s distance below the water surface in world units: <= 0 disables the effect, the low-passed position-neutral treatment fades in above 1 and saturates at 5. A positive depth also enables the built-in underwater reverb zone.

void yse_system_set_underwater_depth(YseSystem *sys, float depth)#
void yse_system_set_occlusion_callback(YseSystem *sys, YseOcclusionCallback cb, void *user_data)#
unsigned int yse_block_size(void)#

Engine block size in frames (issue #908): the fixed number of samples the engine renders per block (128).

yse_system_render_offline(sys, blocks) renders blocks * yse_block_size() frames. Independent of the device’s frames-per-callback reported by yse_system_get_active_buffer_size().

void yse_randomize(void)#

Engine random generator (issue #908) — the one behind the patcher’s .random object, granulator jitter, the LFO random shapes and the seed-0 stream of the seedable patcher objects.

These run inside the engine library, so they seed the engine’s own generator even when the host links it as a shared library (the C++ YSE::Randomize() inline would seed the host’s copy).

Without either call the engine is reproducible by default. yse_randomize() seeds from the system clock; yse_random_seed() seeds with a fixed value, so the same seed replays the same sequence on the n-th thread to draw after the call — in particular on the audio thread when it is the only one drawing. Every engine thread re-seeds on its next draw, so the audio thread’s running stream is affected too. Call from a control thread (not from an audio callback), typically before creating the patch or sounds whose output should be reproducible; objects that drew their stream at construction keep it.

void yse_random_seed(unsigned int seed)#

yse_listener.h — 3D listener position and orientation.

C ABI mirror of YseEngine/listener.hpp (YSE::listener + YSE::Listener() singleton).

Typedefs

typedef struct YseListener YseListener#

Borrowed singleton — owned by the engine, never destroy.

Obtain via yse_listener_get().

Functions

YseListener *yse_listener_get(void)#

Borrowed singleton pointer — never destroy.

void yse_listener_set_pos(YseListener *l, const yse_pos_t *p)#
yse_pos_t yse_listener_get_pos(YseListener *l)#
yse_pos_t yse_listener_get_vel(YseListener *l)#
yse_pos_t yse_listener_get_forward(YseListener *l)#
yse_pos_t yse_listener_get_upward(YseListener *l)#
void yse_listener_set_orient(YseListener *l, const yse_pos_t *forward, const yse_pos_t *up)#

Sound objects, channels, and reverb#

yse_sound.h — playable audio source.

C ABI mirror of YseEngine/sound/soundInterface.hpp (YSE::sound). A sound loads from a file, a YseDspBuffer, a YseDspMultiBuffer, or a YsePatcher. The dspSourceObject overload (a user-written DSP source) is not wrapped: its callback runs on the audio thread and needs a design of its own.

Convention: every void-returning function in this header is a null-safe no-op when called with a NULL handle. Status queries return 0 / false on NULL. Loads that can fail return YseStatus and populate yse_last_error() on failure.

The same rule covers a valid handle that has not been loaded yet, or whose yse_sound_load_* call failed: setters and transport calls are no-ops and queries return 0 / false until a load succeeds (issue #579). Check yse_sound_is_valid() to tell the two apart.

Typedefs

typedef struct YseSound YseSound#

Owned — release with yse_sound_destroy.

Forward declarations — see yse_sound.h / yse_channel.h / yse_music.h for ownership.

Functions

YseSound *yse_sound_create(void)#
void yse_sound_destroy(YseSound *s)#
YseStatus yse_sound_load_file(YseSound *s, const char *filename, YseChannel *ch, int loop, float volume, int streaming)#

Initialize a sound from a file on disk.

Must be called once after yse_sound_create() and before any other method.

The loop and volume arguments of the load functions below seed the sound’s state: after a successful load, yse_sound_get_looping() and yse_sound_get_volume() report them back until a setter overrides them (issue #583). A failed load leaves the sound at its defaults.

YseStatus yse_sound_load_buffer(YseSound *s, YseDspBuffer *buf, YseChannel *ch, int loop, float volume)#

Initialize a sound from an in-memory DSP buffer.

The buffer must outlive the sound — the engine keeps a reference to it for as long as the sound is live. Same lifetime contract as the C++ overload.

YseStatus yse_sound_load_multi_buffer(YseSound *s, YseDspMultiBuffer *mb, YseChannel *ch, int loop, float volume)#

Initialize a sound from an in-memory multichannel buffer (the sound::create(MULTICHANNELBUFFER&) overload, issue #909): channel i of mb feeds the sound’s channel i, so a two-channel buffer plays as a stereo source.

Build mb with yse_dsp_multi_buffer_create() (see yse_dsp.h). The engine reads mb for as long as the sound plays, so it must outlive the sound — the same lifetime contract as yse_sound_load_buffer().

YseStatus yse_sound_load_patcher(YseSound *s, YsePatcher *patch, YseChannel *ch, float volume)#

Initialize a sound from a patcher graph.

The patcher must outlive the sound.

int yse_sound_is_valid(YseSound *s)#
int yse_sound_is_ready(YseSound *s)#
int yse_sound_is_streaming(YseSound *s)#
void yse_sound_set_name(YseSound *s, const char *name)#

Assign a bus-addressable name to the sound (mirrors YSE::sound::name, issue #905).

Once named “foo”, the sound subscribes to the global named bus addresses sound.foo.volume and sound.foo.speed (float, also accept int) and sound.foo.position (list of 3 floats), so a host can drive it with yse_bus_publish_*. Values that arrive before a load succeeds are dropped. NULL or “” clears the name and removes the subscriptions; renaming re-subscribes under the new name. Names are unique per sound: a duplicate is rejected and logged engine-side (first registration wins — no error return, matching yse_synth_set_name). Only effective while the engine is between init and close.

void yse_sound_play(YseSound *s)#

Transport.

void yse_sound_pause(YseSound *s)#
void yse_sound_stop(YseSound *s)#
void yse_sound_toggle(YseSound *s)#
void yse_sound_restart(YseSound *s)#
int yse_sound_is_playing(YseSound *s)#
int yse_sound_is_paused(YseSound *s)#
int yse_sound_is_stopped(YseSound *s)#
void yse_sound_set_pos(YseSound *s, const yse_pos_t *p)#

3D + mixing.

yse_pos_t yse_sound_get_pos(YseSound *s)#
void yse_sound_set_volume(YseSound *s, float v, unsigned int fade_ms)#
float yse_sound_get_volume(YseSound *s)#
void yse_sound_set_speed(YseSound *s, float v)#
float yse_sound_get_speed(YseSound *s)#
void yse_sound_set_size(YseSound *s, float v)#
float yse_sound_get_size(YseSound *s)#
void yse_sound_set_spread(YseSound *s, float v)#
float yse_sound_get_spread(YseSound *s)#
void yse_sound_set_looping(YseSound *s, int v)#
int yse_sound_get_looping(YseSound *s)#
void yse_sound_set_relative(YseSound *s, int v)#
int yse_sound_get_relative(YseSound *s)#
void yse_sound_set_doppler(YseSound *s, int v)#
int yse_sound_get_doppler(YseSound *s)#
void yse_sound_set_pan2d(YseSound *s, int v)#
int yse_sound_get_pan2d(YseSound *s)#
void yse_sound_set_occlusion(YseSound *s, int v)#
int yse_sound_get_occlusion(YseSound *s)#
void yse_sound_fade_and_stop(YseSound *s, unsigned int time_ms)#
void yse_sound_set_time(YseSound *s, float samples)#

Playhead.

float yse_sound_get_time(YseSound *s)#
unsigned int yse_sound_length(YseSound *s)#
void yse_sound_move_to(YseSound *s, YseChannel *target)#
void yse_sound_set_dsp(YseSound *s, YseDspObject *dsp)#

DSP effect chain.

The dspObject pointer is borrowed; the engine reads it on the audio thread. Must outlive the sound. Pass NULL to clear.

YseDspObject *yse_sound_get_dsp(YseSound *s)#

yse_channel.h — mixer-tree channels.

C ABI mirror of YseEngine/channel/channelInterface.hpp (YSE::channel + pre-built free-function accessors ChannelMaster, ChannelFX, ChannelMusic, ChannelAmbient, ChannelVoice, ChannelGui).

Convention: every void-returning function in this header is a null-safe no-op when called with a NULL handle. Status queries (is_valid, get_*) return 0 / false / NULL on NULL.

Typedefs

typedef struct YseChannel YseChannel#

Owned via yse_channel_create / _create_with_sends / _create_return — release with yse_channel_destroy.

Forward declarations — see yse_channel.h / yse_reverb.h / yse_device.h for ownership semantics.

Forward declarations — see yse_channel.h / yse_dsp.h / yse_dsp_modules.h / yse_patcher.h for ownership semantics.

Borrowed via the yse_channel_master / _fx / _music / _ambient / _voice / _gui pre-built accessors — never destroy those.

typedef struct YseDspObject YseDspObject#

Forward declaration — see yse_dsp_modules.h for ownership (a channel insert effect is owned by the caller, never by the channel).

Owned — release with yse_dsp_object_destroy.

Covers every effect subclass via the shared handle type.

Functions

YseChannel *yse_channel_master(void)#

Pre-built channels — borrowed pointers, never destroy.

YseChannel *yse_channel_fx(void)#
YseChannel *yse_channel_music(void)#
YseChannel *yse_channel_ambient(void)#
YseChannel *yse_channel_voice(void)#
YseChannel *yse_channel_gui(void)#
YseChannel *yse_channel_create(const char *name, YseChannel *parent)#

User-created channels.

yse_channel_create allocates a channel with the default 4 aux-send slots; yse_channel_create_with_sends chooses the slot count (sized once off the audio thread and never resized — raise it for a channel that fans out to many return buses). Both return NULL and set yse_last_error() on a NULL name/parent.

YseChannel *yse_channel_create_with_sends(const char *name, YseChannel *parent, int send_slots)#
void yse_channel_destroy(YseChannel *ch)#
YseChannel *yse_channel_create_return(const char *name, int send_slots)#

─── send/return buses (issue #165; design docs/design/send_return_buses.md) ──

A return bus is an ordinary channel (it keeps set_dsp inserts, attach_reverb, set_volume, and metering) excluded from the normal mix tree; other channels route scaled copies of their signal into it and its output folds into the master mix after the source tree — the classic aux-send topology. A return may itself send into another return (an acyclic delay→reverb chain); cycles are rejected at wiring time.

yse_channel_create_return allocates and flags a return in one call (the C mirror of makeReturn) — call it INSTEAD of yse_channel_create. Destroy it with yse_channel_destroy like any channel.

int yse_channel_is_return(YseChannel *ch)#

1 if the channel is a send/return bus, else 0 (also 0 on NULL).

void yse_channel_send(YseChannel *ch, int slot, YseChannel *return_bus, float level, int pre_fader)#

Wire send slot slot (in [0, send_slots)) of ch to return_bus at level.

Post-fader by default; pass pre_fader != 0 for a cue-style send independent of the channel fader. Illegal wirings (target not a return, a self-send, a return→return edge that would close a cycle, an out-of-range slot) are rejected on the calling thread and logged — never reaching the audio thread. Null-safe no-op.

void yse_channel_set_send_level(YseChannel *ch, int slot, float level)#

Set a send slot’s level, ramped and click-free.

Safe to call every control tick — send levels are designed as modulation targets, so continuous writes fuse into the per-block ramp without zippering. Null-safe no-op.

void yse_channel_clear_send(YseChannel *ch, int slot)#

Detach send slot slot, fully disconnecting it from its return.

Null-safe no-op.

float yse_channel_get_send_level(YseChannel *ch, int slot)#

Current target level of send slot slot, or 0 if unset/invalid/NULL.

void yse_channel_set_dsp(YseChannel *ch, YseDspObject *dsp)#

─── channel insert DSP (issue #159) ─────────────────────────────────────

Attach a pre-fader insert effect chain to this channel — the DAW “insert” slot, mirroring yse_sound_set_dsp at the channel level. The effect processes the channel’s summed output in place, before reverb and the channel volume. Chain effects with yse_dsp_object_link. Pass NULL to detach. The channel takes no ownership: the YseDspObject must outlive the channel or be detached first. Null-safe no-op.

YseDspObject *yse_channel_get_dsp(YseChannel *ch)#

The currently attached insert effect chain head, or NULL if none/NULL.

void yse_channel_set_volume(YseChannel *ch, float value)#
float yse_channel_get_volume(YseChannel *ch)#
void yse_channel_move_to(YseChannel *ch, YseChannel *parent)#
void yse_channel_attach_reverb(YseChannel *ch)#
void yse_channel_set_virtual(YseChannel *ch, int value)#
int yse_channel_get_virtual(YseChannel *ch)#
int yse_channel_is_valid(YseChannel *ch)#
size_t yse_channel_get_name(YseChannel *ch, char *buf, size_t cap)#

Copy the channel’s log name (the name passed at create — not the bus name set by yse_channel_set_name) into buf, snprintf-style: writes at most cap-1 bytes plus a NUL, returns the full length.

Pass buf=NULL/cap=0 to size the buffer first. Returns 0 (and clears buf) on a NULL handle.

void yse_channel_set_name(YseChannel *ch, const char *name)#

Assign a bus-addressable name to the channel (mirrors YSE::channel::name, issue #905).

Once named “foo”, the channel subscribes to the global named bus address channel.foo.volume (float, also accepts int), so a host can set its volume with yse_bus_publish_float(“channel.foo.volume”, v). Independent of the log name. NULL or “” clears the name and removes the subscription; renaming re-subscribes under the new name. Names are unique per channel: a duplicate is rejected and logged engine-side (first registration wins — no error return, matching yse_synth_set_name). Only effective while the engine is between init and close.

int yse_channel_get_num_outputs(YseChannel *ch)#

Output peak metering — see channelInterface.hpp for semantics.

“Pre” reads the peak measured at the end of dsp() (after inserts and reverb, before the channel volume is applied); “Post” reads the peak measured immediately after adjustVolume() — what listeners hear. Per-output overloads take an index in [0, yse_channel_get_num_outputs()); out-of-range indices return 0. dB getters convert the linear value on the fly with a -120 dB floor for silence. All getters return 0 on a NULL or invalid channel.

float yse_channel_get_peak_linear_pre(YseChannel *ch)#
float yse_channel_get_peak_linear_post(YseChannel *ch)#
float yse_channel_get_peak_db_pre(YseChannel *ch)#
float yse_channel_get_peak_db_post(YseChannel *ch)#
float yse_channel_get_peak_linear_pre_output(YseChannel *ch, int output_idx)#
float yse_channel_get_peak_linear_post_output(YseChannel *ch, int output_idx)#
float yse_channel_get_peak_db_pre_output(YseChannel *ch, int output_idx)#
float yse_channel_get_peak_db_post_output(YseChannel *ch, int output_idx)#

yse_reverb.h — positioned reverb zones + global reverb.

C ABI mirror of YseEngine/reverb/reverbInterface.hpp.

Two kinds of reverb handles exist:

Convention: every void-returning function in this header is a null-safe no-op when called with a NULL handle. Status queries return 0 / false on NULL.

Typedefs

typedef struct YseReverb YseReverb#

Owned via yse_reverb_create — release with yse_reverb_destroy.

Borrowed via yse_system_get_global_reverb — never destroy that.

typedef struct YseReverbPresetValues YseReverbPresetValues#

Plain-old-data mirror of YSE::REVERB::presetValues — one complete reverb parameter set: the payload of a named preset and the custom endpoint type of the morphing reverb.

Fields are copied one by one across the ABI; the layout is not assumed to match the engine struct.

Functions

YseReverb *yse_reverb_create(void)#

Owned reverb zone — yse_reverb_create() runs both the C++ constructor and reverb::create() so the handle is ready to configure immediately.

void yse_reverb_destroy(YseReverb *rev)#
int yse_reverb_is_valid(YseReverb *rev)#
void yse_reverb_set_position(YseReverb *rev, const yse_pos_t *p)#

Position + audible footprint.

yse_pos_t yse_reverb_get_position(YseReverb *rev)#
void yse_reverb_set_size(YseReverb *rev, float v)#
float yse_reverb_get_size(YseReverb *rev)#
void yse_reverb_set_roll_off(YseReverb *rev, float v)#
float yse_reverb_get_roll_off(YseReverb *rev)#
void yse_reverb_set_active(YseReverb *rev, int on)#
int yse_reverb_get_active(YseReverb *rev)#
void yse_reverb_set_room_size(YseReverb *rev, float v)#

Tail shape.

float yse_reverb_get_room_size(YseReverb *rev)#
void yse_reverb_set_damping(YseReverb *rev, float v)#
float yse_reverb_get_damping(YseReverb *rev)#
void yse_reverb_set_dry_wet_balance(YseReverb *rev, float dry, float wet)#
float yse_reverb_get_dry(YseReverb *rev)#
float yse_reverb_get_wet(YseReverb *rev)#
void yse_reverb_set_modulation(YseReverb *rev, float frequency, float width)#
float yse_reverb_get_modulation_frequency(YseReverb *rev)#
float yse_reverb_get_modulation_width(YseReverb *rev)#
void yse_reverb_set_reflection(YseReverb *rev, int reflection, int time, float gain)#

Early reflections (4 slots, index 0..3).

int yse_reverb_get_reflection_time(YseReverb *rev, int reflection)#
float yse_reverb_get_reflection_gain(YseReverb *rev, int reflection)#
void yse_reverb_set_preset(YseReverb *rev, YseReverbPreset preset)#
void yse_reverb_preset_get_values(YseReverbPreset preset, YseReverbPresetValues *out)#

Write the parameter set of a named preset into *out — the exact values yse_reverb_set_preset() applies.

A preset outside the enum yields the YSE_REVERB_OFF values. NULL out is a no-op.

void yse_reverb_preset_morph(const YseReverbPresetValues *a, const YseReverbPresetValues *b, float t, YseReverbPresetValues *out)#

Linear blend of two parameter sets into *out: every field is a + (b - a) * t with t clamped to [0, 1], so t = 0 gives a and t = 1 gives b.

out may alias a or b. NULL out is a no-op; a NULL a or b zero-fills *out.

struct YseReverbPresetValues

Plain-old-data mirror of YSE::REVERB::presetValues — one complete reverb parameter set: the payload of a named preset and the custom endpoint type of the morphing reverb.

Fields are copied one by one across the ABI; the layout is not assumed to match the engine struct.

Public Members

float roomsize#

simulated room size, [0, 1]

float damp#

high-frequency damping, [0, 1]

float dry#

unprocessed level, [0, 1]

float wet#

reverberated level, [0, 1]

float mod_frequency#

tail modulation rate, Hz (0 = off)

float mod_width#

tail modulation depth (0 = off)

float early_time[4]#

early reflection delays, samples, [0, 2999]

float early_gain[4]#

early reflection gains, [0, 1]

Audio device#

yse_device.h — audio device descriptors + device-open configuration.

C ABI mirror of YseEngine/device/deviceInterface.hpp + deviceSetup.hpp.

Device descriptors are read-only and borrowed from the engine; never destroy one returned by yse_system_get_device(). deviceSetup objects are owned by the caller — pair yse_device_setup_create() with yse_device_setup_destroy().

String-out functions use the snprintf convention: at most (cap - 1) bytes are copied into the buffer and a NUL terminator is appended. The return value is the full length of the string (excluding the NUL); pass cap=0 to query the required size before allocating.

The indexed getters (channel names, sample rates, buffer sizes) are bound-checked: an index at or beyond the matching yse_device_num_*() count yields the same result as a NULL handle — an empty out buffer and a return of 0 / 0.0 — so iterating with a stale count is safe.

The scalar getters below always report a defined value: a descriptor starts with the three size/latency fields at 0 and its device id at -1, and the engine only ever overwrites them with real data, so there is no indeterminate read to guard against.

Typedefs

typedef struct YseDevice YseDevice#

Borrowed — read-only descriptor enumerated from the engine via yse_system_get_device().

Never destroy.

typedef struct YseDeviceSetup YseDeviceSetup#

Owned — release with yse_device_setup_destroy.

Functions

size_t yse_device_get_name(YseDevice *dev, char *buf, size_t cap)#

Device descriptor — read-only.

size_t yse_device_get_type_name(YseDevice *dev, char *buf, size_t cap)#
unsigned int yse_device_num_output_channels(YseDevice *dev)#
size_t yse_device_get_output_channel_name(YseDevice *dev, unsigned int idx, char *buf, size_t cap)#
unsigned int yse_device_num_input_channels(YseDevice *dev)#
size_t yse_device_get_input_channel_name(YseDevice *dev, unsigned int idx, char *buf, size_t cap)#
unsigned int yse_device_num_sample_rates(YseDevice *dev)#
double yse_device_get_sample_rate(YseDevice *dev, unsigned int idx)#
unsigned int yse_device_num_buffer_sizes(YseDevice *dev)#
int yse_device_get_buffer_size(YseDevice *dev, unsigned int idx)#
int yse_device_default_buffer_size(YseDevice *dev)#

0 from yse_device_default_buffer_size()

means “the host does not advertise

one”, not “zero frames”: passed on to yse_device_setup_set_buffer_size() it asks the backend to choose.

The PortAudio enumerator reports 0 for every device (PaDeviceInfo has no equivalent field), so read the negotiated size back from yse_system_get_active_buffer_size() once the device is open.

int yse_device_output_latency(YseDevice *dev)#
int yse_device_input_latency(YseDevice *dev)#
int yse_device_get_id(YseDevice *dev)#

-1 from yse_device_get_id() means “no device”, not “device index -1”: it is PortAudio’s paNoDevice, what a descriptor reports before the engine fills it in, and what a NULL handle reports.

Every device from yse_system_get_device() carries a real, non-negative index. Handing a setup whose output device still reads -1 to yse_system_open_device() is refused: it returns YSE_ERR_AUDIO_DEVICE, logs the reason and leaves the running stream alone.

YseDeviceSetup *yse_device_setup_create(void)#

deviceSetup — owned configuration object passed to yse_system_open_device.

void yse_device_setup_destroy(YseDeviceSetup *setup)#
void yse_device_setup_set_input(YseDeviceSetup *setup, const YseDevice *dev)#
void yse_device_setup_set_output(YseDeviceSetup *setup, const YseDevice *dev)#
void yse_device_setup_set_sample_rate(YseDeviceSetup *setup, double value)#

Sample rate to open the device at, in Hz; 0 (the default) means no request.

The session rate is fixed while a session runs: when it differs from the session rate, yse_system_open_device() logs a warning and opens the stream at the session rate. Choose the rate with yse_system_request_sample_rate() before yse_system_init() instead.

void yse_device_setup_set_buffer_size(YseDeviceSetup *setup, int value)#
int yse_device_setup_get_output_channels(const YseDeviceSetup *setup)#

Output channel count of the device attached with yse_device_setup_set_output() — the count yse_system_open_device() opens the stream with.

0 before an output device is set, and for a NULL setup.

DSP and patcher#

yse_dsp.h — single-channel audio buffers + buffer subclasses.

C ABI mirror of YseEngine/dsp/{buffer,drawableBuffer,fileBuffer,wavetable}.hpp.

One opaque handle type YseDspBuffer covers all four subclasses, which form a single non-polymorphic inheritance chain:

buffer <- drawableBuffer <- fileBuffer <- wavetable

DSP::buffer has no virtual members, so there is no runtime type check to perform: the subclass-specific entry points (drawLine, load/save, createSaw, …) static_cast the handle and trust the caller. On those functions YSE_ERR_INVALID_HANDLE means the handle was NULL and nothing else — it is never a type-confusion guard, so do not build one on top of it.

Because the chain is linear, a handle is valid for its own entry points and for those of every base: a wavetable handle may be passed to the drawable and fileBuffer functions, and a fileBuffer handle to the drawable ones. Narrowing the other way is undefined behaviour — a plain yse_dsp_buffer_create() handle passed to yse_dsp_buffer_draw_line(), or a fileBuffer handle passed to yse_dsp_wavetable_create_saw(), returns YSE_OK and writes through the static_cast, exactly as reinterpret_cast<wavetable*>() on a plain buffer would in C++. Bindings must enforce the subclass themselves by remembering which constructor produced the handle.

Custom DSP source objects (subclassing dspSourceObject) are not wrapped: that surface runs a user callback on the audio thread and needs a design of its own (preallocated, allocation-free dispatch) before it can cross the C ABI. A patcher (yse_patcher.h) is the supported way to build a custom source from C.

Typedefs

typedef struct YseDspBuffer YseDspBuffer#

Owned — release with yse_dsp_buffer_destroy, whichever of the four constructors produced it.

The handle remembers which one did: internally it owns the engine object through a polymorphic wrapper, so destroy runs the most-derived destructor and frees the size that was really allocated. No type check is exposed and none is possible on the engine chain itself, which stays non-polymorphic (issue #662).

typedef struct YseDspMultiBuffer YseDspMultiBuffer#

Owned — release with yse_dsp_multi_buffer_destroy, and only once every sound loaded from it has been destroyed.

Functions

YseDspBuffer *yse_dsp_buffer_create(unsigned int length, unsigned int overflow)#

Constructors — one per subclass.

The returned handle owns its native storage; pair with yse_dsp_buffer_destroy.

YseDspBuffer *yse_dsp_drawable_buffer_create(unsigned int length, unsigned int overflow)#
YseDspBuffer *yse_dsp_file_buffer_create(unsigned int length, unsigned int overflow)#
YseDspBuffer *yse_dsp_wavetable_create(unsigned int length)#
void yse_dsp_buffer_destroy(YseDspBuffer *buf)#
unsigned int yse_dsp_buffer_length(YseDspBuffer *buf)#

Common buffer accessors.

unsigned int yse_dsp_buffer_length_ms(YseDspBuffer *buf)#
float yse_dsp_buffer_length_sec(YseDspBuffer *buf)#
int yse_dsp_buffer_is_silent(YseDspBuffer *buf)#
float yse_dsp_buffer_max_value(YseDspBuffer *buf)#
float yse_dsp_buffer_get_back(YseDspBuffer *buf)#
float yse_dsp_buffer_sample_rate_adjustment(YseDspBuffer *buf)#
void yse_dsp_buffer_set_sample_rate_adjustment(YseDspBuffer *buf, float v)#
void yse_dsp_buffer_resize(YseDspBuffer *buf, unsigned int length, float value)#

Resize.

value is used to initialise newly added samples.

unsigned int yse_dsp_buffer_read(YseDspBuffer *buf, unsigned int offset, float *out, unsigned int count)#

Bulk sample I/O.

Copies count samples between the host array and the buffer storage starting at offset. Returns the actual number of samples copied (clamped to the buffer length).

unsigned int yse_dsp_buffer_write(YseDspBuffer *buf, unsigned int offset, const float *in, unsigned int count)#
void yse_dsp_buffer_fill(YseDspBuffer *buf, float value)#

Scalar fill / scalar math (operator= and operator+= et al on Flt).

void yse_dsp_buffer_add_scalar(YseDspBuffer *buf, float value)#
void yse_dsp_buffer_mul_scalar(YseDspBuffer *buf, float value)#
void yse_dsp_buffer_add_buffer(YseDspBuffer *buf, YseDspBuffer *other)#

Sample-wise buffer math (operator+= / -= / *= / /= on a buffer, issue #909): buf[i] op= other[i] over the first min(length(buf), length(other)) samples; samples past the shorter length are left untouched.

Division by a zero sample yields 0, not inf. buf and other may be the same handle. A NULL buf or other is a no-op.

void yse_dsp_buffer_sub_buffer(YseDspBuffer *buf, YseDspBuffer *other)#
void yse_dsp_buffer_mul_buffer(YseDspBuffer *buf, YseDspBuffer *other)#
void yse_dsp_buffer_div_buffer(YseDspBuffer *buf, YseDspBuffer *other)#
YseStatus yse_dsp_buffer_copy_from(YseDspBuffer *dst, YseDspBuffer *src, unsigned int src_pos, unsigned int dst_pos, unsigned int count)#

Copy count samples from src[src_pos…] into dst[dst_pos…] (buffer::copyFrom, issue #909).

Both ranges must lie inside their buffer’s length: a range that runs past the end copies nothing and returns YSE_ERR_INVALID_ARGUMENT (the engine call would silently skip it). src and dst may be the same handle only for ranges that do not overlap. Returns YSE_ERR_INVALID_HANDLE for a NULL handle.

YseStatus yse_dsp_buffer_swap(YseDspBuffer *a, YseDspBuffer *b)#

Exchange the samples of two buffers of the same length (buffer::swap, issue #909).

Each buffer keeps its own storage, sample-rate adjustment and subclass; only the sample values move. Returns YSE_ERR_INVALID_ARGUMENT (nothing swapped) when the lengths differ and YSE_ERR_INVALID_HANDLE for a NULL handle. Swapping a handle with itself is a successful no-op.

YseStatus yse_dsp_buffer_draw_line(YseDspBuffer *buf, unsigned int start, unsigned int stop, float start_value, float stop_value)#

drawableBuffer-only.

YseStatus yse_dsp_buffer_draw_flat(YseDspBuffer *buf, unsigned int start, unsigned int stop, float value)#
YseStatus yse_dsp_buffer_load_file(YseDspBuffer *buf, const char *filename, unsigned int channel)#

fileBuffer-only.

YseStatus yse_dsp_buffer_save_file(YseDspBuffer *buf, const char *filename)#

Writes the buffer to a mono 32-bit float WAV file.

filename is used verbatim (no extension is appended). The file is written at the sample rate of the file this buffer was last loaded from, or at the engine rate if it was never loaded from one. Returns YSE_ERR_GENERIC (reason in yse_last_error) when the file cannot be created, the buffer is empty, or a custom BufferIO backend is active (yse_buffer_io_set_active(io, 1)): that backend is read-only, so save refuses rather than bypass it (#580, #637). File I/O — never call it from the audio callback.

float yse_dsp_buffer_get_file_sample_rate(YseDspBuffer *buf)#

Native sample rate, in Hz, of the file this buffer was last loaded from; 0 before a successful yse_dsp_buffer_load_file() and for a NULL handle.

Unlike yse_dsp_buffer_sample_rate_adjustment() — a ratio against the engine rate in effect at load time — this does not change when the engine is re-initialised at another rate, so hosts can re-derive the playback speed against the live rate (#637).

YseStatus yse_dsp_wavetable_create_saw(YseDspBuffer *buf, int harmonics, int length)#

wavetable-only.

YseStatus yse_dsp_wavetable_create_square(YseDspBuffer *buf, int harmonics, int length)#
YseStatus yse_dsp_wavetable_create_triangle(YseDspBuffer *buf, int harmonics, int length)#
YseDspMultiBuffer *yse_dsp_multi_buffer_create(YseDspBuffer *const *channels, unsigned int count)#

Build a multichannel buffer holding a COPY of count channel buffers, in order (channels[0] is the first output channel).

The source handles are not retained: change or destroy them afterwards without affecting this one. Channels may differ in length; a sound plays the length of the shortest. Returns NULL with yse_last_error() set when channels is NULL, count is 0, or any entry is NULL.

void yse_dsp_multi_buffer_destroy(YseDspMultiBuffer *mb)#
unsigned int yse_dsp_multi_buffer_get_channel_count(YseDspMultiBuffer *mb)#

Number of channels; 0 on NULL.

yse_dsp_modules.h — chainable DSP effects (dspObject subclasses).

C ABI mirror of YseEngine/dsp/modules/ headers + ringModulator + the inherited dspObject control surface from YseEngine/dsp/dspObject.hpp.

Every effect handle is a YseDspObject* — all the inherited methods (bypass, impact, lfo type/frequency, link into a chain) accept the shared handle type. Subclass-specific setters trust the caller and static_cast (same contract as DSP buffers).

Attach a chain to a sound with yse_sound_set_dsp() (see yse_sound.h).

Typedefs

typedef struct YsePatcher YsePatcher#

Forward declaration — see yse_patcher.h for ownership.

Owned — release with yse_patcher_destroy.

Functions

YseDspObject *yse_dsp_lowpass_create(void)#
YseDspObject *yse_dsp_highpass_create(void)#
YseDspObject *yse_dsp_bandpass_create(void)#
YseDspObject *yse_dsp_sweep_create(YseDspSweepShape shape)#
YseDspObject *yse_dsp_basic_delay_create(void)#
YseDspObject *yse_dsp_lowpass_delay_create(void)#
YseDspObject *yse_dsp_highpass_delay_create(void)#
YseDspObject *yse_dsp_phaser_create(void)#
YseDspObject *yse_dsp_ring_modulator_create(void)#
YseDspObject *yse_dsp_difference_create(void)#
YseDspObject *yse_dsp_granulator_create(unsigned int pool_size, unsigned int max_grains)#
YseDspObject *yse_dsp_feedback_delay_create(void)#

Mix-grade effect modules (issues #160-#163).

Each is a chainable insert or send-return effect; drop one on a channel with yse_channel_set_dsp() or a sound with yse_sound_set_dsp(). Their wet/dry balance is the inherited yse_dsp_object_set_impact(). #160

YseDspObject *yse_dsp_chorus_create(void)#

#161

YseDspObject *yse_dsp_plate_reverb_create(void)#

#162

YseDspObject *yse_dsp_eq_create(void)#

#163

YseDspObject *yse_dsp_compressor_create(void)#

#163

YseDspObject *yse_dsp_morphing_reverb_create(void)#

#326 module, #369 C API

YseDspObject *yse_dsp_underwater_create(void)#

#327 module, #909 C API

YseDspObject *yse_dsp_patcher_insert_create(YsePatcher *patcher)#

Patcher-as-insert (#167 module, #370 C API).

Wraps a YsePatcher graph as a chainable insert effect (YSE::DSP::patcherInsert): a hand-patched network (a filter-delay, a custom EQ, …) drives a sound or channel insert slot, feeding the host buffer to the graph’s ~adc objects and copying the summed ~dac output back over it. The patcher should contain at least one ~adc and one ~dac, and it must outlive the insert — the insert BORROWS the patcher, it never owns or destroys it. Returns NULL and sets yse_last_error() when patcher is NULL (the insert needs a graph to wrap). The returned handle is owned: release it with yse_dsp_object_destroy(), drive it with the inherited yse_dsp_object_* surface (bypass, impact, …), and attach it with yse_sound_set_dsp() or yse_channel_set_dsp().

void yse_dsp_object_destroy(YseDspObject *obj)#
void yse_dsp_object_set_bypass(YseDspObject *obj, int on)#
int yse_dsp_object_get_bypass(YseDspObject *obj)#
void yse_dsp_object_set_impact(YseDspObject *obj, float value)#
float yse_dsp_object_get_impact(YseDspObject *obj)#
void yse_dsp_object_set_lfo_type(YseDspObject *obj, YseLfoType type)#
YseLfoType yse_dsp_object_get_lfo_type(YseDspObject *obj)#
void yse_dsp_object_set_lfo_frequency(YseDspObject *obj, float value)#
float yse_dsp_object_get_lfo_frequency(YseDspObject *obj)#
void yse_dsp_object_link(YseDspObject *head, YseDspObject *next)#

Insert next after head in a processing chain (dspObject::link): with head->X already linked, the result is head->next->X.

Pass NULL as next

to detach the forward edge instead (dspObject::unlink, #391): head’s next pointer is cleared and the detached neighbour’s back-pointer severed, so the neighbour becomes a standalone chain again. Detaching never touches head’s own attachment to a sound/channel (that is yse_sound_set_dsp / yse_channel_set_dsp territory). RT-safety: the audio thread walks these links lock-free every block. To restructure a chain attached to a live sound or channel, detach the chain from its owner first (yse_channel_set_dsp(ch, NULL) / yse_sound_set_dsp(s,

NULL) — a pointer swap applied on the audio thread), re-link the objects into the new order, then re-attach the new head. That keeps the audio thread from ever observing a half-rewired chain; effect DSP state survives because the objects themselves are untouched.

YseDspObject *yse_dsp_object_get_next(YseDspObject *obj)#

The object linked after obj (the dspObject::link() getter, issue #909), or NULL at the end of a chain / on a NULL handle — so a host can walk a chain it built.

The result is the very handle the host linked in: borrowed here, and still owned (and destroyed) through whichever handle created it.

void yse_dsp_lowpass_set_frequency(YseDspObject *obj, float hz)#
float yse_dsp_lowpass_get_frequency(YseDspObject *obj)#
void yse_dsp_highpass_set_frequency(YseDspObject *obj, float hz)#
float yse_dsp_highpass_get_frequency(YseDspObject *obj)#
void yse_dsp_bandpass_set_frequency(YseDspObject *obj, float hz)#
float yse_dsp_bandpass_get_frequency(YseDspObject *obj)#
void yse_dsp_bandpass_set_q(YseDspObject *obj, float q)#
float yse_dsp_bandpass_get_q(YseDspObject *obj)#
void yse_dsp_sweep_set_speed(YseDspObject *obj, float hz)#
float yse_dsp_sweep_get_speed(YseDspObject *obj)#
void yse_dsp_sweep_set_depth(YseDspObject *obj, int v0_to_100)#
int yse_dsp_sweep_get_depth(YseDspObject *obj)#
void yse_dsp_sweep_set_frequency(YseDspObject *obj, int v0_to_100)#
int yse_dsp_sweep_get_frequency(YseDspObject *obj)#
void yse_dsp_basic_delay_set_tap(YseDspObject *obj, YseDspDelayTap tap, float time_ms, float gain)#
float yse_dsp_basic_delay_get_time(YseDspObject *obj, YseDspDelayTap tap)#
float yse_dsp_basic_delay_get_gain(YseDspObject *obj, YseDspDelayTap tap)#
void yse_dsp_lowpass_delay_set_frequency(YseDspObject *obj, float hz)#
float yse_dsp_lowpass_delay_get_frequency(YseDspObject *obj)#
void yse_dsp_highpass_delay_set_frequency(YseDspObject *obj, float hz)#
float yse_dsp_highpass_delay_get_frequency(YseDspObject *obj)#
void yse_dsp_phaser_set_frequency(YseDspObject *obj, float hz)#
float yse_dsp_phaser_get_frequency(YseDspObject *obj)#
void yse_dsp_phaser_set_range(YseDspObject *obj, float value)#
float yse_dsp_phaser_get_range(YseDspObject *obj)#
void yse_dsp_ring_modulator_set_frequency(YseDspObject *obj, float hz)#
float yse_dsp_ring_modulator_get_frequency(YseDspObject *obj)#
void yse_dsp_difference_set_frequency(YseDspObject *obj, float hz)#
float yse_dsp_difference_get_frequency(YseDspObject *obj)#
void yse_dsp_difference_set_amplitude(YseDspObject *obj, float value)#
float yse_dsp_difference_get_amplitude(YseDspObject *obj)#
void yse_dsp_granulator_set_grain_frequency(YseDspObject *obj, unsigned int per_second)#
unsigned int yse_dsp_granulator_get_grain_frequency(YseDspObject *obj)#
void yse_dsp_granulator_set_grain_length(YseDspObject *obj, unsigned int samples, unsigned int random_samples)#
unsigned int yse_dsp_granulator_get_grain_length(YseDspObject *obj)#
void yse_dsp_granulator_set_grain_transpose(YseDspObject *obj, float pitch, float random)#
float yse_dsp_granulator_get_grain_transpose(YseDspObject *obj)#
void yse_dsp_granulator_set_gain(YseDspObject *obj, float value)#
float yse_dsp_granulator_get_gain(YseDspObject *obj)#
void yse_dsp_feedback_delay_set_time(YseDspObject *obj, float ms)#

─── feedback delay (#160) ────────────────────────────────────────────── A recirculating delay for a channel insert or a send return: per-channel delay line, a damping low-pass in the feedback path, and cross-feed between channel pairs for ping-pong.

impact(1) is echoes only (send use).

float yse_dsp_feedback_delay_get_time(YseDspObject *obj)#
void yse_dsp_feedback_delay_set_feedback(YseDspObject *obj, float amount)#
float yse_dsp_feedback_delay_get_feedback(YseDspObject *obj)#
void yse_dsp_feedback_delay_set_damping(YseDspObject *obj, float hz)#
float yse_dsp_feedback_delay_get_damping(YseDspObject *obj)#
void yse_dsp_feedback_delay_set_crossfeed(YseDspObject *obj, float amount)#
float yse_dsp_feedback_delay_get_crossfeed(YseDspObject *obj)#
void yse_dsp_chorus_set_mode(YseDspObject *obj, YseChorusMode mode)#

─── chorus / flanger (#161) ──────────────────────────────────────────── One modulated-delay module with a mode() switch between chorus and flanger.

spread() fans a per-channel LFO phase offset for stereo width.

YseChorusMode yse_dsp_chorus_get_mode(YseDspObject *obj)#
void yse_dsp_chorus_set_rate(YseDspObject *obj, float hz)#
float yse_dsp_chorus_get_rate(YseDspObject *obj)#
void yse_dsp_chorus_set_depth(YseDspObject *obj, float value)#
float yse_dsp_chorus_get_depth(YseDspObject *obj)#
void yse_dsp_chorus_set_feedback(YseDspObject *obj, float value)#
float yse_dsp_chorus_get_feedback(YseDspObject *obj)#
void yse_dsp_chorus_set_spread(YseDspObject *obj, float value)#
float yse_dsp_chorus_get_spread(YseDspObject *obj)#
void yse_dsp_plate_reverb_set_decay(YseDspObject *obj, float value)#

─── plate reverb (#162) ──────────────────────────────────────────────── Dattorro plate reverb for a channel insert or a send return.

Distinct from the engine’s global spatial reverb; impact(0.25) is a natural insert mix, impact(1) is fully wet (send-return use).

float yse_dsp_plate_reverb_get_decay(YseDspObject *obj)#
void yse_dsp_plate_reverb_set_damping(YseDspObject *obj, float hz)#
float yse_dsp_plate_reverb_get_damping(YseDspObject *obj)#
void yse_dsp_plate_reverb_set_predelay(YseDspObject *obj, float ms)#
float yse_dsp_plate_reverb_get_predelay(YseDspObject *obj)#
void yse_dsp_eq_set_frequency(YseDspObject *obj, YseEqBand band, float hz)#

─── parametric EQ (#163) ─────────────────────────────────────────────── Four cascaded bands (low shelf, two peaks, high shelf); each parameter is addressed by a YseEqBand.

Gain is in dB (0 = flat / band bypass).

float yse_dsp_eq_get_frequency(YseDspObject *obj, YseEqBand band)#
void yse_dsp_eq_set_gain(YseDspObject *obj, YseEqBand band, float db)#
float yse_dsp_eq_get_gain(YseDspObject *obj, YseEqBand band)#
void yse_dsp_eq_set_q(YseDspObject *obj, YseEqBand band, float value)#
float yse_dsp_eq_get_q(YseDspObject *obj, YseEqBand band)#
void yse_dsp_compressor_set_detector(YseDspObject *obj, YseCompressorDetector mode)#

─── compressor (#163) ────────────────────────────────────────────────── Feed-forward, stereo-linked dynamics.

gain_reduction_db is a read-only meter of the reduction on the last processed sample (<= 0 dB).

YseCompressorDetector yse_dsp_compressor_get_detector(YseDspObject *obj)#
void yse_dsp_compressor_set_threshold(YseDspObject *obj, float db)#
float yse_dsp_compressor_get_threshold(YseDspObject *obj)#
void yse_dsp_compressor_set_ratio(YseDspObject *obj, float value)#
float yse_dsp_compressor_get_ratio(YseDspObject *obj)#
void yse_dsp_compressor_set_attack(YseDspObject *obj, float ms)#
float yse_dsp_compressor_get_attack(YseDspObject *obj)#
void yse_dsp_compressor_set_release(YseDspObject *obj, float ms)#
float yse_dsp_compressor_get_release(YseDspObject *obj)#
void yse_dsp_compressor_set_makeup(YseDspObject *obj, float db)#
float yse_dsp_compressor_get_makeup(YseDspObject *obj)#
float yse_dsp_compressor_get_gain_reduction_db(YseDspObject *obj)#
void yse_dsp_morphing_reverb_set_preset_a(YseDspObject *obj, YseReverbPreset preset)#

Set an endpoint from a named preset.

void yse_dsp_morphing_reverb_set_preset_b(YseDspObject *obj, YseReverbPreset preset)#
void yse_dsp_morphing_reverb_set_preset_a_values(YseDspObject *obj, const YseReverbPresetValues *values)#

Set an endpoint from a custom parameter set.

NULL values is a no-op.

void yse_dsp_morphing_reverb_set_preset_b_values(YseDspObject *obj, const YseReverbPresetValues *values)#
void yse_dsp_morphing_reverb_get_preset_a(YseDspObject *obj, YseReverbPresetValues *out)#

Read an endpoint’s current parameter set into *out.

NULL out is a no-op; a NULL handle zero-fills *out.

void yse_dsp_morphing_reverb_get_preset_b(YseDspObject *obj, YseReverbPresetValues *out)#
void yse_dsp_morphing_reverb_set_morph(YseDspObject *obj, float value)#

The morph control input: 0 = pure A, 1 = pure B, clamped to [0, 1].

float yse_dsp_morphing_reverb_get_morph(YseDspObject *obj)#
void yse_dsp_underwater_set_depth(YseDspObject *obj, float depth)#

─── underwater (#327 module, #909 C API) ──────────────────────────────── The engine’s underwater treatment as an ordinary chainable insert: mixes every channel toward a position-neutral, low-passed average as depth (distance below the water surface, world units) grows.

depth <= 1 is transparent, 1..5 crossfades, >= 5 is fully neutral; negative values clamp to

  1. Writes are allocation-free and wait-free, callable from any control thread. The wet/dry balance is inherent in depth, so the inherited yse_dsp_object_set_impact() is NOT applied. Independent of the engine’s own instance driven by yse_system_set_underwater_depth().

float yse_dsp_underwater_get_depth(YseDspObject *obj)#

yse_patcher.h — Max/MSP-style modular DSP/event graph.

C ABI mirror of YseEngine/patcher/{patcher,pHandle,pObjectList}.hpp.

A YsePatcher owns its YsePHandle objects — never destroy handles directly, use yse_patcher_delete_object().

Outgoing messages reach the host through yse_patcher_set_send_callback (the C mirror of patcher::SetOscHandler), besides the bus.

Object type identifiers are the same strings YSE::OBJ exposes (“~sine”, “.+”, “~lp”, etc.). See patcher/pObjectList.hpp upstream.

Convention: every void-returning function in this header is a null-safe no-op when called with a NULL handle (patcher or phandle). Status queries return 0 / false / NULL on NULL — except the handful where 0 is itself a valid answer and would be indistinguishable from the failure: yse_phandle_output_data_type answers YSE_OUT_INVALID, the two object-ID queries (yse_phandle_get_id, yse_phandle_get_connection_target) answer YSE_PATCHER_ID_NONE, and yse_phandle_get_connection_target_inlet answers YSE_PATCHER_INLET_NONE.

Defines

YSE_PATCHER_ID_NONE#

“No such object” — the answer from every function in this header that returns a patcher object ID but has nothing to report.

Object IDs are per-patcher and start at 0 (issue #730), so 0 is the ID of the first object in every patch and cannot double as a failure marker. This value matches the engine’s pObject::kNoStorageID, which is what an object belonging to no patcher reports, and it is never a real object’s ID: a patcher issues the smallest number none of its live objects holds (issue #733), so the largest ID it can hand out is its live object count — 2^32 objects would have to fit in memory at once.

yse_patcher_get_handle_from_id(p, YSE_PATCHER_ID_NONE) is NULL.

YSE_PATCHER_INLET_NONE#

“No such inlet” — the answer from yse_phandle_get_connection_target_inlet when it has no edge to report an inlet for (issue #736).

Inlet 0 is the leftmost inlet and the one most connections in a patch arrive at, so it cannot double as a failure marker any more than object ID 0 can. This value matches the engine’s pObject::kNoInletIndex and is never a real inlet: an object would have to declare 2^32 of them.

A separate name from YSE_PATCHER_ID_NONE on purpose, though the two hold the same number. An inlet index is not an object ID; comparing an inlet against the ID sentinel should read wrong at the call site even while it happens to work. Bindings should mirror both names rather than collapse them.

Typedefs

typedef struct YsePHandle YsePHandle#

Borrowed — owned by the parent YsePatcher.

Release with yse_patcher_delete_object(patcher, handle); never call a destroy on the handle directly.

typedef void (*YsePatcherSendCallback)(void *user_data, char *address, YseOutType kind, int i, float f, char *s)#

A message the patcher sends to a name no “.r” in it receives: a “.s” inside the patch, or a yse_patcher_pass_* call, with nothing listening.

address is the name it was sent to — the “.s” argument or the pass_* to, not the “patcher.<name>.<slot>” bus address. kind says which value it carries: YSE_OUT_BANG (none), YSE_OUT_INT (i), YSE_OUT_FLOAT (f) or YSE_OUT_LIST (s). The fields a kind does not use are 0 and NULL.

The receiver OWNS the strings: address and s are one malloc’d block, released with yse_patcher_free_message(address), which frees s with it — never free s on its own. They stay valid after the callback returns, so an asynchronous host (a Dart NativeCallable.listener) can read them later.

Threading: called synchronously on whichever thread emitted the message — the host’s own for a pass_* or a yse_phandle_set_* that drives a “.s”, an engine worker for a timer-driven one (a millisecond “.metro”). Never the audio callback: a “.s” fired while the patch renders stays inside it. Keep it cheap and thread-safe.

Functions

YsePatcher *yse_patcher_create(void)#
void yse_patcher_destroy(YsePatcher *p)#
YseStatus yse_patcher_init(YsePatcher *p, int main_outputs)#

Give the patcher main_outputs audio outputs.

YSE_OK on success (and on a repeat call, which the engine ignores: the first count stays); YSE_ERR_INVALID_HANDLE on a NULL patcher, YSE_ERR_INVALID_ARGUMENT on a negative count, YSE_ERR_EXCEPTION if the engine threw. Every failure leaves its reason in yse_last_error(). Control thread only.

YseStatus yse_patcher_set_name(YsePatcher *p, const char *name)#

Name the patcher (issue #896).

The name scopes every bus slot and shared store inside it: they are addressed as “patcher.<name>.<slot>”. Two patchers with the same name share those slots; uniqueness is not enforced. Renaming re-anchors every name-scoped object transparently.

NULL or “” restores the auto-generated “patcher_<N>” name. Called before yse_patcher_init, the name is stashed and applied at init; NULL / “” there drops a stashed name so init keeps the auto-name.

A name longer than 55 characters is refused: YSE_ERR_INVALID_ARGUMENT, the reason in yse_last_error(), and the current name kept. YSE_ERR_INVALID_HANDLE on a NULL patcher. Control thread only.

size_t yse_patcher_get_name(YsePatcher *p, char *buf, size_t cap)#

The current name, snprintf style — returns the full length and accepts a NULL buffer as a size query.

Before yse_patcher_init this is the stashed name, or “” when there is none: the auto-name is assigned at init.

YsePHandle *yse_patcher_create_object(YsePatcher *p, const char *type, const char *args)#
void yse_patcher_delete_object(YsePatcher *p, YsePHandle *obj)#
void yse_patcher_clear(YsePatcher *p)#
void yse_patcher_connect(YsePatcher *p, YsePHandle *from, int outlet, YsePHandle *to, int inlet)#

Draw / remove a cord.

Both handles must be objects of p: a NULL handle, or one created by another patcher, is ignored (the engine logs the refusal) and the graph is left unchanged.

void yse_patcher_disconnect(YsePatcher *p, YsePHandle *from, int outlet, YsePHandle *to, int inlet)#
int yse_patcher_is_valid_object(const char *type)#
void yse_patcher_set_container(YsePatcher *p, YsePHandle *obj, YsePHandle *container)#

A subpatcher is an object of type “patcher” that other objects are placed inside, so the parent patch addresses the group as one object (issue #545).

Its boundary is made of “.inlet” / “.outlet” objects for message pins and “~inlet” / “~outlet” objects for signal pins (issue #764). Both rates share ONE index space: inlet N of a subpatcher is the “.inlet” or “~inlet” inside it whose index is N — a “.inlet 0” beside a “~inlet 1” presents two inlets, and one index cannot be claimed at both rates. yse_patcher_connect resolves that for you — connecting to a subpatcher records an ordinary edge straight to the boundary object.

Containment is addressing, not storage. A nested object is an ordinary object in the patcher’s one flat graph: it takes part in the same single atomic graph swap on every edit, and the audio thread’s cost does not grow with nesting depth. Put obj inside the subpatcher container, or pass NULL for container to move obj back out to the top level. Refused (logged, no change) when container is not a “patcher” object in this patcher, when either handle belongs elsewhere, or when the move would put a subpatcher inside itself or one of its own descendants.

Publishes no new graph — containment changes no edge and no pin. What it changes is what yse_patcher_connect / _disconnect mean when handed the subpatcher, and what yse_patcher_delete_object takes with it: a deleted subpatcher takes its contents, transitively.

YsePHandle *yse_patcher_get_container(YsePatcher *p, YsePHandle *obj)#

The subpatcher obj is inside, or NULL when it is at the top level (and NULL on a NULL argument).

Borrowed, like every other handle here.

int yse_patcher_subpatcher_inlets(YsePatcher *p, YsePHandle *container)#

How many inlets / outlets a subpatcher presents to its parent: one past the highest index claimed by a boundary object among its contents — “.inlet” and “~inlet” (resp.

“.outlet” and “~outlet”) alike, counted in their one shared numbering — so a sparsely numbered boundary reports the range a parent can address rather than the number of boundary objects. 0 when the handle is not a subpatcher.

A “patcher” object owns no pins of its own, so yse_phandle_get_inputs answers 0 for one — these are the questions to ask instead.

int yse_patcher_subpatcher_outlets(YsePatcher *p, YsePHandle *container)#
size_t yse_patcher_dump_json(YsePatcher *p, char *buf, size_t cap)#
YseStatus yse_patcher_parse_json(YsePatcher *p, const char *content)#

Load a patch previously produced by yse_patcher_dump_json.

Loading adds to the patcher: objects already in it are kept, and the loaded ones are created next to them. Call yse_patcher_clear first to replace the patch (issue #939). YSE_OK on success; YSE_ERR_INVALID_HANDLE on a NULL patcher, YSE_ERR_INVALID_ARGUMENT on NULL content, YSE_ERR_NOT_INITIALIZED before yse_patcher_init, and YSE_ERR_EXCEPTION when the content is malformed JSON or not a patch. Every failure leaves its reason in yse_last_error() and the patcher exactly as it was: nothing of the file is loaded (issue #938). Control thread only.

unsigned int yse_patcher_objects(YsePatcher *p)#
YsePHandle *yse_patcher_get_handle_from_list(YsePatcher *p, unsigned int idx)#
YsePHandle *yse_patcher_get_handle_from_id(YsePatcher *p, unsigned int id)#
int yse_patcher_pass_bang(YsePatcher *p, const char *to)#

Deliver a value to the “.r <to>” receivers in this patcher.

1 when one exists — or when a send callback is installed (see below): a message no receiver answers is then handed to the callback instead, and still reports 1. 0 when neither takes it, and on a NULL argument.

int yse_patcher_pass_int(YsePatcher *p, int value, const char *to)#
int yse_patcher_pass_float(YsePatcher *p, float value, const char *to)#
int yse_patcher_pass_string(YsePatcher *p, const char *value, const char *to)#
YseStatus yse_patcher_set_send_callback(YsePatcher *p, YsePatcherSendCallback cb, void *user_data)#

Install the send callback; NULL for cb clears it.

user_data is opaque and forwarded to every call. Replaces any previous callback, but a message already being dispatched on another thread may still reach the previous (cb, user_data) pair — always as a pair, never one install’s cb with another’s user_data. yse_patcher_destroy is the point after which no callback runs: it returns only once every call in progress on another thread has finished. Called before yse_patcher_init, the callback is kept and takes effect at init.

While a callback is installed, pass_* answers 1 for a name nothing receives; once cleared, 0 again.

May be called from inside the callback. Do not destroy the patcher from inside its own callback, and do not install, clear or destroy while holding a lock the callback itself takes: both wait for calls in progress on other threads. YSE_ERR_INVALID_HANDLE on a NULL patcher, YSE_ERR_EXCEPTION (reason in yse_last_error()) if the install could not allocate — the previous callback is then kept.

void yse_patcher_free_message(char *address)#

Release a message delivered to a YsePatcherSendCallback: pass its address.

NULL is a no-op.

size_t yse_phandle_get_type(YsePHandle *h, char *buf, size_t cap)#
size_t yse_phandle_get_name(YsePHandle *h, char *buf, size_t cap)#
size_t yse_phandle_get_params(YsePHandle *h, char *buf, size_t cap)#
size_t yse_phandle_get_gui_value(YsePHandle *h, char *buf, size_t cap)#

What a host polls to draw an object (issue #551).

GUI state is a list of string cells: a scalar control (“.slider”, “.t”, “.i”, …) has one cell, a structured one (“.rslider”, “.multislider”, “.matrixctrl”) has several. The engine-side contract lives in patcher/pObject.h; these three functions are its ABI mirror.

Host thread only, and a poll may be destructive: “.b” reports the press it is clearing. Poll a given object through the whole-state read or through the per-cell reads once per frame, never both, or a consume-on-read object reports its event to one and not the other. The per-cell reads are also not atomic across cells — a repaint may see one cell from before an edit and the next from after it. Use the whole-state read when a coherent snapshot matters. The whole state as one string: the value for a scalar control, or every cell space separated in index order for a structured one. Empty for an object with no GUI state. snprintf style — returns the full length and accepts a NULL buffer as a size query.

unsigned int yse_phandle_get_gui_value_count(YsePHandle *h)#

How many cells this object’s GUI state has; 1 for a scalar control, 0 on NULL.

size_t yse_phandle_get_gui_value_at(YsePHandle *h, unsigned int index, char *buf, size_t cap)#

One cell, and the empty string for an index at or past the count.

Cell 0 of a scalar control is exactly yse_phandle_get_gui_value.

int yse_phandle_gui_value_is_settable(YsePHandle *h)#

Whether this object accepts its own GUI state back on inlet 0 — 1 when the round trip holds, 0 otherwise and on NULL.

1 means yse_phandle_set_list(h, 0, <what get_gui_value returned>) restores the state that was read, and yse_phandle_set_list(h, 0, “set <index> <value>”) writes one cell — except on a one-cell control whose cell is free text (“.textedit”), which drops the “set” form so it can hold the text “set 0 hello”; there the whole-state write is the cell write, and the round trip still holds. That is the write half of the protocol, and the whole of it: there is no GUI-value setter. State goes into an object as an ordinary message on the control thread, so it passes through the object’s own clamps and outlet sends and never races the audio thread.

The scalar controls (“.slider”, “.i”, “.f”, “.dial”, “.incdec”, “.t”) hold the round trip since their #846 migration. 0 for the objects that still predate the protocol — and for “.b” deliberately: its value is a consume-on-read press report, an event a restore could only replay.

size_t yse_phandle_get_gui_property(YsePHandle *h, const char *key, char *buf, size_t cap)#

Editor decoration — geometry, colour, anything the host wants persisted with the patch.

Opaque to the engine and serialised under the “gui” key by yse_patcher_dump_json. Not live control state: that is the GUI value protocol above.

void yse_phandle_set_gui_property(YsePHandle *h, const char *key, const char *value)#
void yse_phandle_set_bang(YsePHandle *h, unsigned int inlet)#
void yse_phandle_set_int(YsePHandle *h, unsigned int inlet, int value)#
void yse_phandle_set_float(YsePHandle *h, unsigned int inlet, float value)#
void yse_phandle_set_list(YsePHandle *h, unsigned int inlet, const char *value)#
void yse_phandle_set_params(YsePHandle *h, const char *args)#
int yse_phandle_get_inputs(YsePHandle *h)#
int yse_phandle_get_outputs(YsePHandle *h)#
int yse_phandle_is_dsp_input(YsePHandle *h, unsigned int inlet)#
YseOutType yse_phandle_output_data_type(YsePHandle *h, unsigned int pin)#
unsigned int yse_phandle_get_id(YsePHandle *h)#

Storage ID of this object within its patcher — the number the object is written as by yse_patcher_dump_json, and the key yse_patcher_get_handle_from_id takes.

YSE_PATCHER_ID_NONE on NULL. IDs are dense and are reused: deleting an object frees its number for the next object created, so an ID identifies an object only while that object is alive. An ID cached across a yse_patcher_delete_object may resolve to a different object, not to NULL — keep the YsePHandle* if you need a reference that outlives the number.

unsigned int yse_phandle_get_connections(YsePHandle *h, unsigned int outlet)#

Number of edges leaving outlet.

0 on NULL and 0 for an outlet past the object’s outlet count — yse_phandle_set_params can shrink that count, so an outlet number cached across a re-parse may name nothing; compare against yse_phandle_get_outputs to tell an absent outlet from an idle one.

unsigned int yse_phandle_get_connection_target(YsePHandle *h, unsigned int outlet, unsigned int connection)#

ID of the object one edge leaving outlet arrives at.

YSE_PATCHER_ID_NONE on NULL, on an outlet past the object’s outlet count, or on a connection past that outlet’s edge count.

unsigned int yse_phandle_get_connection_target_inlet(YsePHandle *h, unsigned int outlet, unsigned int connection)#

Inlet on the target object that this edge arrives at.

YSE_PATCHER_INLET_NONE on NULL, on an outlet past the object’s outlet count, or on a connection past that outlet’s edge count.

int yse_patcher_get_type_count(void)#
const char *yse_patcher_get_type_name(int index)#
const char *yse_patcher_get_type_description(const char *type_name)#
YsePCategory yse_patcher_get_type_category(const char *type_name)#
int yse_patcher_get_type_is_dsp(const char *type_name)#
int yse_patcher_get_inlet_count(const char *type_name)#
void yse_patcher_get_inlet_info(const char *type_name, int idx, const char **label, const char **doc, const char **range, unsigned int *accepts_bitmask)#
int yse_patcher_get_outlet_count(const char *type_name)#
void yse_patcher_get_outlet_info(const char *type_name, int idx, const char **label, const char **doc, const char **range, YseOutType *type)#
int yse_patcher_get_param_count(const char *type_name)#
void yse_patcher_get_param_info(const char *type_name, int idx, const char **name, const char **doc, const char **default_value, const char **range)#
char *yse_patcher_get_metadata_json(void)#

Returns a fresh malloc’d JSON snapshot of every registered object’s metadata.

One-stop shop for bindings that want to cache the metadata or regenerate their own reference at build time. The caller must release the buffer with yse_free_string() (declared in yse_common.h); returns NULL on allocation failure.

Each object entry carries “name”, “category” (the YsePCategory name without its YSE_PCAT_ prefix, e.g. “ROUTING”), “is_dsp”, “requires_midi_device”, “description”, “inlets”, “outlets” and “params”. “requires_midi_device” is true for the objects that hold a MIDI device port; those exist only in builds with YSE_ENABLE_MIDI_DEVICE (not macOS or Android), so a palette built from one platform’s metadata can mark them as non-portable.

Synth and instruments#

yse_synth.h — polyphonic synthesiser voice pool rendered behind one sound.

C ABI mirror of YseEngine/synth/synthInterface.hpp (YSE::synth) and §12 (“Public API surface”) of docs/design/synth_core.md.

A synth owns polyphony, voice allocation, stealing and keyboard/pedal state; you drive it with note, controller and pedal events. Build the voice pool from a built-in voice type with yse_synth_add_voices_*, attach it behind a positioned sound with yse_synth_attach_to_sound(), then play notes. Cloning happens off the audio thread on the engine’s setup pool, so a synth becomes playable a short moment after add-voices returns (poll yse_synth_get_num_voices() for the cloned count) — exactly like a file-backed sound is not playable until its buffer finishes loading.

Scope: the BUILT-IN voice types are exposed via yse_synth_add_voices_*: the reference sine + ADSR voice (issue #152), and the three instruments (issue #178) — the SFZ sampler (add_voices_sampler, fed a YseSfzInstrument loaded through yse_instrument.h), the virtual-analog + wavetable voice (add_voices_va, with per-parameter setters below), and the DX7-class 6-op FM voice (add_voices_fm, patched from a YseDx7Bank or the headline setters below). Defining a CUSTOM dspVoice subclass from C is deliberately DEFERRED — it needs the same audio-thread dspSourceObject callback plumbing that keeps dspSourceObject unwrapped (see yse_dsp.h and docs/design/synth_core.md §1 non-goals).

Parameter reach (parity, #178): the VA voice’s live patch (YSE::SYNTH::vaParams) is fully covered by the yse_synth_va_set_* setters below — every field is a glitch-free atomic, so each setter is safe to call while voices play. The FM voice’s patch (YSE::SYNTH::fmPatch) is plain DX7 voice data; its full 155- parameter set is authored by importing a DX7 bank and selecting a patch (yse_synth_fm_set_patch), and the headline globals + per-operator level/ frequency are also reachable directly via the yse_synth_fm_set_* setters. FM patch edits take effect on the NEXT note-on (the FM core bakes operator state at key-down, like the hardware between notes), so the FM setters are not glitch-free mid-note — unlike the VA ones. The sampler voice has no live tone parameters: its sound is the immutable SFZ instrument, authored at load time through yse_instrument.h (yse_sfz_load / yse_sfz_load_config).

Per-note 3D positioning (issue #171, §14 of docs/design/per_note_positioning.md) is exposed by the same policy: only the BUILT-IN position handlers (static / random-spread / orbit) are selectable, via yse_synth_set_position_handler with the YseSynthPositionHandler enum. Defining a CUSTOM position handler (subclassing YSE::SYNTH::positionHandler from C) is DEFERRED for the same reason a custom dspVoice is — its noteOn / update / onRelease hooks run on the AUDIO THREAD, which needs the callback plumbing that keeps dspSourceObject unwrapped (see yse_dsp.h). Per-note position readback here is a single best-effort snapshot (yse_synth_get_voice_position), not a continuous readback stream.

Convention: every void-returning function in this header is a null-safe no-op when called with a NULL handle. Status queries return 0 / false on NULL. Operations that can fail (add-voices, attach) return YseStatus and populate yse_last_error() on failure. Channels follow the engine’s synth API: 0 = omni / all channels, 1..16 = a specific MIDI channel. Velocity, controller and aftertouch values are normalised to [0, 1]; the pitch wheel to [-1, 1].

Typedefs

typedef void (*YseSynthNoteCallback)(int note_on, float *note_number, float *velocity)#

Audio-thread note-rewrite hook, mirroring YSE::synth::onNoteEvent (docs/design/synth_core.md §7).

Invoked by the engine on the AUDIO THREAD for every note-on / note-off, before keyboard bookkeeping and voice allocation. It may rewrite *note_number and *velocity in place — the classic use is transposition, retuning, velocity curves or note filtering.

note_on is 1 for a note-on, 0 for a note-off. Same real-time rules as a voice’s render: no allocation, no locks, no blocking I/O. There is no user_data slot — the engine hook is a bare captureless function pointer, so state must be reached through globals (matches the C++ API, which accepts only a free function / captureless lambda).

typedef struct YseSynthPositionParams YseSynthPositionParams#

─── per-note 3D positioning (issue #171) ────────────────────────────────

Configuration for yse_synth_set_position_handler. One flat, ffigen-friendly struct covering every built-in handler; only the fields belonging to the selected kind are read. Pass NULL to take the engine’s defaults for that kind. Mirrors the chainable setters on YSE::SYNTH::staticHandler / randomSpreadHandler / orbitHandler. All positions are in the same coordinate frame as a sound position.

Functions

YseSynth *yse_synth_create(void)#

Create and register a synth.

Ready to receive add-voices and note events immediately (runs the C++ constructor and YSE::synth::create()). Returns NULL on allocation failure with yse_last_error() set.

void yse_synth_destroy(YseSynth *h)#
int yse_synth_is_valid(YseSynth *h)#

Whether the synth has a live implementation (registered with the engine).

void yse_synth_set_name(YseSynth *h, const char *name)#

Assign a bus-addressable name to the synth (mirrors YSE::synth::name, issue #388).

Once named “foo”, the engine subscribes it to the global named bus addresses synth.foo.note / .off / .cc / .bend / .aftertouch / .alloff, so note and controller events published by name reach the synth engine-side with no host round-trip. Payload shapes are locked by docs/design/live_coding_dsl.md (“Mapping to synth events”); delivery reuses the same RT-safe message inbox as the note/controller functions above. Anonymous synths (the default) are not addressable.

NULL or “” clears the name and removes the subscriptions; renaming re-subscribes under the new name. Names are unique per synth: a duplicate is rejected and logged engine-side (first registration wins — there is no error return, matching the C++ API). Only effective while the engine is between init and close.

YseStatus yse_synth_add_voices_sine(YseSynth *h, int num_voices, int channel, int lowest_note, int highest_note, float attack, float decay, float sustain, float release)#

Add a group of num_voices built-in sine voices (sine oscillator shaped by an ADSR envelope) responding to note numbers in [lowest_note, highest_note] on channel (0 = omni).

May be called several times to build layered or split keyboards. attack / decay / release are in seconds; sustain is a level in [0, 1]. Must be called before the synth is played; adding voices after the pool is built is rejected (a non-goal, see docs/design/synth_core.md §1). Returns YseStatus; on failure yse_last_error() is set and no group is added.

YseStatus yse_synth_add_voices_sampler(YseSynth *h, YseSfzInstrument *instrument, int num_voices, int channel, int lowest_note, int highest_note)#

Add a group of SFZ sampler voices rendering instrument (loaded via yse_sfz_load / yse_sfz_load_config).

The instrument’s region table and PCM are shared with the voice group, which retains its own reference — so the YseSfzInstrument handle may be destroyed right after this returns. YSE_ERR_INVALID_ARGUMENT if instrument is NULL or already destroyed.

YseStatus yse_synth_add_voices_va(YseSynth *h, int num_voices, int channel, int lowest_note, int highest_note)#

Add a group of virtual-analog + wavetable voices with a fresh default patch.

This establishes the synth’s VA patch; the yse_synth_va_set_* setters below steer it. Call once per synth (a second call replaces which patch the setters target — layering multiple VA groups is out of scope for the C API).

YseStatus yse_synth_add_voices_fm(YseSynth *h, int num_voices, int channel, int lowest_note, int highest_note)#

Add a group of DX7-class 6-operator FM voices with the built-in sine test patch.

This establishes the synth’s FM patch; select a DX7 voice into it with yse_synth_fm_set_patch, or dial the headline params with yse_synth_fm_set_*. Call once per synth (see add_voices_va’s note).

void yse_synth_va_set_osc_wave(YseSynth *h, int osc, YseVaWaveform wave)#

─── VA patch parameters (issue #178) ───────────────────────────────────── Steer the synth’s VA patch (established by yse_synth_add_voices_va).

Every value is a glitch-free atomic read on the audio thread, so these are safe to call while voices play. All are null-safe no-ops on a NULL handle or a synth with no VA group. osc is the oscillator index 0..2 (out-of-range ignored). Times are in seconds; sustains and normalised depths in [0, 1] unless noted.

void yse_synth_va_set_osc_detune(YseSynth *h, int osc, float semitones)#
void yse_synth_va_set_osc_level(YseSynth *h, int osc, float level)#
void yse_synth_va_set_osc_pulse_width(YseSynth *h, int osc, float width)#
void yse_synth_va_set_wavetable_position(YseSynth *h, float position)#
void yse_synth_va_set_cutoff(YseSynth *h, float hz)#
void yse_synth_va_set_resonance(YseSynth *h, float resonance)#
void yse_synth_va_set_key_tracking(YseSynth *h, float amount)#
void yse_synth_va_set_filter_env_amount(YseSynth *h, float octaves)#
void yse_synth_va_set_filter_vel_amount(YseSynth *h, float octaves)#
void yse_synth_va_set_amp_attack(YseSynth *h, float seconds)#
void yse_synth_va_set_amp_decay(YseSynth *h, float seconds)#
void yse_synth_va_set_amp_sustain(YseSynth *h, float level)#
void yse_synth_va_set_amp_release(YseSynth *h, float seconds)#
void yse_synth_va_set_amp_vel_amount(YseSynth *h, float amount)#
void yse_synth_va_set_filter_attack(YseSynth *h, float seconds)#
void yse_synth_va_set_filter_decay(YseSynth *h, float seconds)#
void yse_synth_va_set_filter_sustain(YseSynth *h, float level)#
void yse_synth_va_set_filter_release(YseSynth *h, float seconds)#
void yse_synth_va_set_lfo_type(YseSynth *h, YseLfoType type)#
void yse_synth_va_set_lfo_rate(YseSynth *h, float hz)#
void yse_synth_va_set_lfo_to_pitch(YseSynth *h, float semitones)#
void yse_synth_va_set_lfo_to_cutoff(YseSynth *h, float octaves)#
void yse_synth_va_set_lfo_to_wavetable(YseSynth *h, float amount)#
void yse_synth_va_set_gain(YseSynth *h, float gain)#
void yse_synth_va_load_wavetable(YseSynth *h, int slot, const float *cycle, size_t length)#

Install a single-cycle waveform into the VA wavetable morph bank at slot (used by YSE_VA_WAVETABLE mode).

cycle points to length normalised samples (one period). SETUP-THREAD only — this reshapes table storage; call before the synth is played, not while voices render. Null-safe no-op on a NULL handle / cycle, an empty length, or a synth with no VA group.

YseStatus yse_synth_fm_set_patch(YseSynth *h, YseDx7Bank *bank, int index)#

Copy patch index from a DX7 bank (imported via yse_dx7_import_sysex) into the synth’s FM patch — the way to reach the full 155-parameter DX7 voice from C.

The patch is copied, so bank may be destroyed afterwards. YSE_ERR_INVALID_ARGUMENT for a NULL / destroyed bank or an out-of-range index; YSE_ERR_INVALID_HANDLE for a NULL synth or one with no FM group.

void yse_synth_fm_set_algorithm(YseSynth *h, int algorithm)#

Headline global params (DX7 ranges; clamped defensively engine-side).

0..31

void yse_synth_fm_set_feedback(YseSynth *h, int feedback)#

0..7

void yse_synth_fm_set_transpose(YseSynth *h, int transpose)#

0..48, 24 = none

void yse_synth_fm_set_lfo_speed(YseSynth *h, int speed)#

0..99

void yse_synth_fm_set_lfo_delay(YseSynth *h, int delay)#

0..99

void yse_synth_fm_set_lfo_waveform(YseSynth *h, int waveform)#

0..5

void yse_synth_fm_set_lfo_pitch_mod_depth(YseSynth *h, int depth)#

0..99

void yse_synth_fm_set_lfo_amp_mod_depth(YseSynth *h, int depth)#

0..99

void yse_synth_fm_set_pitch_mod_sens(YseSynth *h, int sensitivity)#

0..7

void yse_synth_fm_set_op_output_level(YseSynth *h, int op, int level)#

Headline per-operator params.

op is the operator index 0..5 (OP1..OP6); out-of-range is ignored. 0..99

void yse_synth_fm_set_op_freq_coarse(YseSynth *h, int op, int coarse)#

0..31

void yse_synth_fm_set_op_freq_fine(YseSynth *h, int op, int fine)#

0..99

void yse_synth_fm_set_op_detune(YseSynth *h, int op, int detune)#

0..14, 7 = centre

void yse_synth_fm_set_op_osc_mode(YseSynth *h, int op, int mode)#

0 ratio, 1 fixed

void yse_synth_fm_set_op_enabled(YseSynth *h, int op, int enabled)#

0/1

int yse_synth_get_num_voices(YseSynth *h)#

Total number of allocated (cloned) voices across every group.

Zero until the setup pool finishes cloning; poll it to know the synth is playable.

void yse_synth_note_on(YseSynth *h, int channel, int note_number, float velocity)#

Start / release a note.

velocity is normalised to [0, 1].

void yse_synth_note_off(YseSynth *h, int channel, int note_number, float velocity)#
void yse_synth_note_on_note(YseSynth *h, const YseNote *note)#

Start / release a note described by a YseNote (see yse_music.h), mirroring synth::noteOn/noteOff(const MUSIC::note&) (issue #909): the note’s channel is the channel, its pitch rounded to the nearest integer is the note number, and its volume is the velocity.

The note is read, not retained. A NULL synth or note is a no-op.

void yse_synth_note_off_note(YseSynth *h, const YseNote *note)#
void yse_synth_all_notes_off(YseSynth *h, int channel)#

Release every held note on channel (0 = all channels).

A bulk note-off: voices enter their normal release, they are not cut.

void yse_synth_pitch_wheel(YseSynth *h, int channel, float value)#

Bend every voice on channel.

value is normalised to [-1, 1] (0 = centre).

void yse_synth_controller(YseSynth *h, int channel, int number, float value)#

Send a control-change.

value is normalised to [0, 1]. CC 64 / 66 / 67 act as the sustain / sostenuto / soft pedals; other CC numbers are stored as the channel’s last controller value.

void yse_synth_aftertouch(YseSynth *h, int channel, int note_number, float value)#

Apply aftertouch pressure, normalised to [0, 1].

note_number == -1 is channel-wide; otherwise only the voice(s) sounding that note receive it.

void yse_synth_sustain(YseSynth *h, int channel, int down)#

Pedals (down is a boolean: non-zero = down).

void yse_synth_sostenuto(YseSynth *h, int channel, int down)#
void yse_synth_soft_pedal(YseSynth *h, int channel, int down)#
void yse_synth_set_note_callback(YseSynth *h, YseSynthNoteCallback cb)#

Install (or clear, with NULL) the audio-thread note-rewrite hook.

The engine stores the hook atomically; passing NULL disables the hook. The callback is always invoked through its own YseSynthNoteCallback signature (note_on is a full int, 0 or 1) via an internal trampoline. At most 256 DISTINCT callback functions can be installed per process (synths sharing one callback share a trampoline); past that the call leaves the previous hook installed and sets yse_last_error(). See the YseSynthNoteCallback contract above.

YseStatus yse_synth_attach_to_sound(YseSynth *h, YseSound *sound, YseChannel *channel, float volume)#

Render this synth behind sound, which supplies the single 3D position, channel routing and master play/stop intent (mirrors the C++ YSE::sound::create(synth&, channel*, volume)).

Build the synth’s voices with yse_synth_add_voices_* before calling this. channel may be NULL for the master channel. volume is a linear gain.

Lifetime: the synth must outlive the sound — destroy the sound before the synth. Returns YseStatus; on failure (e.g. the sound could not be created) yse_last_error() is set.

YseStatus yse_synth_set_position_handler(YseSynth *h, YseSynthPositionHandler kind, const YseSynthPositionParams *params)#

Attach one of the built-in per-note position handlers, giving every voice its own 3D position and movement (mirrors YSE::synth::positionHandler with a shipped handler prototype).

kind selects the handler; params configures it (NULL = engine defaults for that kind). The handle keeps the prototype alive; the engine clones it once per voice slot on the setup pool.

Must be called BEFORE the synth is attached/played — like add-voices, the engine rejects a handler swap after the voice pool is built (it logs a warning and keeps the existing handler; the call still returns YSE_OK). Returns YSE_ERR_INVALID_ARGUMENT for an unknown kind; yse_last_error() is set on failure.

Custom C-side handlers (subclassing YSE::SYNTH::positionHandler from C) are DEFERRED — the hooks run on the AUDIO THREAD, the same callback-plumbing gap that keeps custom dspVoice / dspSourceObject unwrapped (see yse_dsp.h and the scope note at the top of this header). Only the built-ins are reachable.

void yse_synth_handler_param(YseSynth *h, int index, float value)#

Update a shared handler parameter at runtime (message-based, RT-safe).

All of the synth’s live handlers read the block next audio block, so this steers the swarm / spread centre from the control thread. index is a YseSynthHandlerParam (0..2 = centre X / Y / Z); out-of-range indices are ignored engine-side. A bounded, allocation-free message — safe to call every control tick.

void yse_synth_note_position(YseSynth *h, int channel, int note_number, float x, float y, float z)#

Imperatively place the voice(s) sounding note_number on channel at (x, y, z) — app-driven trajectories (mirrors YSE::synth::notePosition).

A bounded, allocation-free message. When a handler is attached it re-steers the voice next block, so this is primarily for the no-handler case.

void yse_synth_get_voice_position(YseSynth *h, int channel, int note_number, float *x, float *y, float *z)#

Best-effort snapshot of the current position of a voice sounding (channel, note_number); writes the origin (0, 0, 0) if none is sounding (mirrors YSE::synth::getVoicePosition, intended for tests / metering).

Any of the out pointers may be NULL. This is a single snapshot, not a per-note readback stream. On a NULL synth handle it writes the origin.

struct YseSynthPositionParams

─── per-note 3D positioning (issue #171) ────────────────────────────────

Configuration for yse_synth_set_position_handler. One flat, ffigen-friendly struct covering every built-in handler; only the fields belonging to the selected kind are read. Pass NULL to take the engine’s defaults for that kind. Mirrors the chainable setters on YSE::SYNTH::staticHandler / randomSpreadHandler / orbitHandler. All positions are in the same coordinate frame as a sound position.

Public Members

float static_x#

YSE_POSITION_HANDLER_STATIC — the single fixed position.

float static_y#
float static_z#
float spread_radius#

YSE_POSITION_HANDLER_RANDOM_SPREAD.

radius of the scatter sphere around the centre

unsigned int spread_seed#

base RNG seed (a given seed reproduces the scatter)

float orbit_radius#

YSE_POSITION_HANDLER_ORBIT.

base orbit radius

float orbit_velocity_radius#

extra radius added at full velocity

float orbit_aftertouch_widen#

fraction of extra radius at full aftertouch

float orbit_rate#

orbit angular speed, radians per second

float orbit_height#

vertical offset of the orbit plane

float orbit_release_slow#

rate multiplier once the note is released

yse_instrument.h — loadable instrument assets for the synth voice pool.

C ABI mirror of the SFZ sampler instrument (YseEngine/synth/samplerVoice.hpp, YSE::SYNTH::samplerInstrument / samplerConfig, issue #174) and the DX7 SysEx bank importer (YseEngine/dsp/fm/dx7Sysex.hpp, YSE::SYNTH::dx7Bank / dx7SysEx, issue #177).

These are the SHAREABLE, engine-lifetime-independent assets a synth renders: a loaded SFZ instrument (region table + resident PCM) and a parsed DX7 bank (a list of FM patches). Build one here, then hand it to a synth voice group with yse_synth_add_voices_sampler() / yse_synth_fm_set_patch() (see yse_synth.h). Loading and parsing happen on the CALLING (setup / control) thread — never the audio thread; both read files and allocate.

Ownership: an instrument / bank handle is OWNED by the caller — release it with yse_sfz_destroy() / yse_dx7_destroy(). The handles are reference-counted assets independent of the engine: a voice group that renders one retains its own share, so it is safe to destroy the handle right after add-voices, and it is safe to hold or destroy a handle across yse_system_close(). Every handle is tracked in a live-handle registry, so a double free or a use of an already-destroyed handle is a LOGGED NO-OP, never a crash (the engine’s warning log records the misuse and the call is ignored).

Convention: every void-returning function is a null-safe no-op on a NULL handle; loaders return NULL on failure with yse_last_error() set; status queries return 0 / empty on NULL or invalid handles.

Typedefs

typedef struct YseSfzInstrument YseSfzInstrument#

Owned — release with yse_sfz_destroy.

A reference-counted SFZ instrument (region table + resident PCM). Share it across any number of synth voice groups via yse_synth_add_voices_sampler; each group retains its own share, so destroying this handle afterwards does not free the PCM until the last group is gone. Safe to destroy across yse_system_close().

typedef struct YseDx7Bank YseDx7Bank#

Owned — release with yse_dx7_destroy.

A parsed DX7 SysEx bank (a list of FM patches, 1 or 32). Select a patch into a synth’s FM voice group with yse_synth_fm_set_patch; the patch is copied into the synth, so the bank may be destroyed afterwards. Safe to destroy across yse_system_close().

typedef struct YseSamplerConfig YseSamplerConfig#

One-region convenience creator — the samplerConfig facade (spec §11) as a flat, ffigen-friendly param struct.

Builds a single-region instrument around one sample file without an .sfz text file, decoding the sample on the calling thread. name may be NULL (identification only). file is the absolute sample path. root is the key that plays the sample untransposed; low / high bound the playable key range. attack / release are the amplitude envelope times in seconds; max_length caps a non-looping one-shot in seconds. Mirrors YSE::SYNTH::samplerConfig.

Functions

YseSfzInstrument *yse_sfz_load(const char *path)#

Load and preload an .sfz file into a shareable instrument.

Parses the file and decodes every unique sample into RAM on the calling thread (off the audio thread). Returns NULL on failure (unreadable / empty file, no playable region) with yse_last_error() set.

YseSfzInstrument *yse_sfz_load_config(const YseSamplerConfig *cfg)#

Build a one-region instrument from a YseSamplerConfig.

Returns NULL on failure (NULL cfg, missing / unreadable sample) with yse_last_error() set.

int yse_sfz_is_valid(YseSfzInstrument *h)#

Whether the instrument is playable (valid region table + at least one resident sample).

0 on a NULL or already-destroyed handle.

void yse_sfz_destroy(YseSfzInstrument *h)#

Release an instrument handle.

A double free or a NULL handle is a logged no-op, not a crash.

YseDx7Bank *yse_dx7_import_sysex(const char *path)#

Load and parse a DX7 .syx file into a bank.

A 32-voice packed bulk dump yields 32 patches; a single-voice dump yields 1. Reads the file on the calling thread. Returns NULL on failure (file not found / unreadable, bad header, wrong length, checksum mismatch) with yse_last_error() set.

YseDx7Bank *yse_dx7_import_sysex_memory(const unsigned char *data, size_t length)#

Parse a DX7 SysEx image already in memory (dx7SysEx::parse, issue #909) — for hosts that ship banks as bundled assets rather than files (Android APKs, resource packs).

Accepts the same formats as yse_dx7_import_sysex(); data is read during the call only, not retained. Returns NULL on failure (NULL data, zero length, bad header, wrong length, checksum mismatch) with yse_last_error() set.

int yse_dx7_get_patch_count(YseDx7Bank *h)#

Number of patches in the bank.

0 on a NULL or already-destroyed handle.

size_t yse_dx7_get_patch_name(YseDx7Bank *h, int index, char *buf, size_t cap)#

Write the (space-trimmed) name of patch index into buf as a NUL-terminated string, snprintf-style; returns the length that would have been written (excluding the NUL), or 0 for an out-of-range index or a NULL / destroyed handle.

buf may be NULL to query the length.

int yse_dx7_find_patch(YseDx7Bank *h, const char *name)#

Index of the first patch whose (space-trimmed) name equals name exactly (dx7Bank::indexOf, issue #909), for yse_synth_fm_set_patch().

Returns -1 when no patch matches, and for a NULL name or a NULL / destroyed handle.

void yse_dx7_destroy(YseDx7Bank *h)#

Release a bank handle.

A double free or a NULL handle is a logged no-op, not a crash.

struct YseSamplerConfig

One-region convenience creator — the samplerConfig facade (spec §11) as a flat, ffigen-friendly param struct.

Builds a single-region instrument around one sample file without an .sfz text file, decoding the sample on the calling thread. name may be NULL (identification only). file is the absolute sample path. root is the key that plays the sample untransposed; low / high bound the playable key range. attack / release are the amplitude envelope times in seconds; max_length caps a non-looping one-shot in seconds. Mirrors YSE::SYNTH::samplerConfig.

Public Members

const char *name#

Instrument label (may be NULL).

const char *file#

Absolute path to the sample file (sample=).

int root#

Root note (key that plays untransposed).

int low#

Lowest playable key.

int high#

Highest playable key.

float attack#

Amplitude-envelope attack, seconds.

float release#

Amplitude-envelope release, seconds.

float max_length#

One-shot length cap, seconds (non-looping regions).

MIDI and music#

yse_midi.h — MIDI file playback + MIDI device output + MIDI device input

  • midiNote helper. C ABI mirror of YseEngine/midi/{midifile,midiNote,device}.hpp.

MIDI device input and output are Windows/Linux-only upstream (RtMidi-backed). Querying device counts/names lives in yse_system.h (yse_system_num_midi_{in,out}_devices, yse_system_midi_{in,out}_device_name).

── MIDI CHANNEL NUMBERING: TWO BASES IN THIS API ── The wire-level functions here count channels from ZERO: yse_midi_out_* takes 0..15 (the YSE::MIDI::M_CHANNEL indexing; out-of-range values are clamped), and the parsed input callback reports the raw 0..15 nibble. The musical layer counts from ONE: yse_synth_* (yse_synth.h), clip events (yse_clip.h) and the channel_filter of yse_midi_in_connect_synth take 1..16; on the synth calls and the filter, 0 means omni / every channel. So MIDI channel 1 is 0 on a yse_midi_out_* call and 1 on a synth or clip call — and a 0 passed to a synth is not channel 1 but all of them. Convert with +1 / -1 at the boundary. (A clip bound to a MIDI output does that conversion itself: its events stay 1..16.)

Pitches are 0..127 raw MIDI note numbers.

Input callbacks fire on RtMidi’s internal input thread. They MUST return quickly and MUST NOT block on I/O. The raw-bytes callback receives a malloc-allocated buffer that ownership transfers to the receiver — release it with yse_midi_in_free_message when finished. The parsed callback uses scalar arguments and transfers no ownership. The pre-decode of a parsed message takes the status nibble (bytes[0] & 0xF0) and channel nibble (bytes[0] & 0x0F); 1- and 2-byte messages report missing data bytes as 0.

Typedefs

typedef struct YseMidiFile YseMidiFile#

Owned — release with yse_midi_file_destroy.

typedef struct YseMidiIn YseMidiIn#

Owned — release with yse_midi_in_destroy.

typedef struct YseMidiNote YseMidiNote#

Owned — release with yse_midi_note_destroy.

typedef void (*YseMidiInRawCallback)(double timestamp_sec, unsigned char *bytes, size_t len, void *user_data)#

Raw-bytes callback.

bytes is malloc’d by the engine; the receiver owns it and must release with yse_midi_in_free_message. Length is in bytes.

typedef void (*YseMidiInParsedCallback)(double timestamp_sec, unsigned char status, unsigned char channel, unsigned char data1, unsigned char data2, void *user_data)#

Parsed callback.

status/channel are pre-split from the first byte; data1 and data2 are zero for messages shorter than 3 bytes. No ownership transfer.

Functions

YseMidiFile *yse_midi_file_create(void)#
void yse_midi_file_destroy(YseMidiFile *f)#
YseStatus yse_midi_file_load(YseMidiFile *f, const char *filename)#
void yse_midi_file_play(YseMidiFile *f)#
void yse_midi_file_pause(YseMidiFile *f)#
void yse_midi_file_stop(YseMidiFile *f)#
void yse_midi_file_connect_synth(YseMidiFile *f, YseSynth *synth)#

Route this file’s playback into a synth (issue #372, mirrors YSE::MIDI::file::connect).

While the file plays, every note / controller / pitch-bend event it contains is delivered to synth block-accurately on the audio thread; may be called for several synths (up to a small fixed cap) to drive them together. Calling it again for an already-connected synth is a no-op. synth is the opaque handle from yse_synth_create; a NULL file or a NULL / never-created synth is a no-op. connect only records the pointer in the file’s lock-free synth table, so no loaded file or active playback is required here. The synth must outlive the connection — disconnect it (or destroy this file) before destroying the synth. This is a control-thread call; do not call it from the audio thread.

void yse_midi_file_disconnect_synth(YseMidiFile *f, YseSynth *synth)#

Stop routing this file’s playback into synth (issue #372).

Safe to call for a synth that was never connected.

YseMidiOut *yse_midi_out_create(void)#
void yse_midi_out_destroy(YseMidiOut *m)#
void yse_midi_out_open(YseMidiOut *m, unsigned int port)#
void yse_midi_out_note_on(YseMidiOut *m, int channel, int pitch, int velocity)#
void yse_midi_out_note_off(YseMidiOut *m, int channel, int pitch, int velocity)#
void yse_midi_out_poly_pressure(YseMidiOut *m, int channel, int pitch, int value)#
void yse_midi_out_channel_pressure(YseMidiOut *m, int channel, int value)#
void yse_midi_out_program_change(YseMidiOut *m, int channel, int value)#
void yse_midi_out_control_change(YseMidiOut *m, int channel, int controller, int value)#
void yse_midi_out_all_notes_off_channel(YseMidiOut *m, int channel)#
void yse_midi_out_all_notes_off(YseMidiOut *m)#
void yse_midi_out_reset_channel(YseMidiOut *m, int channel)#
void yse_midi_out_reset(YseMidiOut *m)#
void yse_midi_out_local_control(YseMidiOut *m, int on)#
void yse_midi_out_omni(YseMidiOut *m, int on)#
void yse_midi_out_poly(YseMidiOut *m, int on)#
void yse_midi_out_raw3(YseMidiOut *m, unsigned char a, unsigned char b, unsigned char c)#
void yse_midi_out_raw(YseMidiOut *m, const unsigned char *data, size_t length)#

Send length bytes from data as one message, exactly as given — no padding and no truncation (issue #903, mirrors YSE::midiOut::Raw(const unsigned char*, size_t)).

Use it for two-byte messages (program change, channel pressure) and for SysEx. The bytes are copied out before the call returns. A NULL handle, NULL data or a zero length is a no-op.

YseMidiIn *yse_midi_in_create(void)#
void yse_midi_in_destroy(YseMidiIn *m)#
void yse_midi_in_open(YseMidiIn *m, unsigned int port)#
void yse_midi_in_close(YseMidiIn *m)#
int yse_midi_in_is_open(YseMidiIn *m)#
void yse_midi_in_set_raw_callback(YseMidiIn *m, YseMidiInRawCallback cb, void *user_data)#

Register a raw callback.

Pass NULL to detach.

void yse_midi_in_set_parsed_callback(YseMidiIn *m, YseMidiInParsedCallback cb, void *user_data)#

Register a parsed callback.

Pass NULL to detach.

void yse_midi_in_free_message(unsigned char *bytes)#

Release a byte buffer previously delivered to a YseMidiInRawCallback.

void yse_midi_in_connect_synth(YseMidiIn *m, YseSynth *synth, int channel_filter)#

Route incoming device MIDI into a synth (issue #155).

Every channel-voice message received on the open port is mapped to the synth’s normalized note API and pushed lock-free onto its inbox, on RtMidi’s input thread. channel_filter is a 1..16 MIDI channel to accept, or 0 for every channel. May be called for several synths (up to a small fixed cap); calling it again for an already-connected synth just updates its channel filter. The synth must outlive the connection — disconnect it (or close/destroy this port) before destroying the synth. This is a control-thread call; do not call it from the input callbacks. On builds without MIDI device support it no-ops.

void yse_midi_in_disconnect_synth(YseMidiIn *m, YseSynth *synth)#

Stop routing incoming device MIDI into synth (issue #155).

Safe to call for a synth that was never connected.

YseMidiNote *yse_midi_note_create(unsigned char note, unsigned int velocity)#
void yse_midi_note_destroy(YseMidiNote *n)#
void yse_midi_note_set_note(YseMidiNote *n, unsigned char note)#
unsigned char yse_midi_note_get_note(YseMidiNote *n)#
void yse_midi_note_set_velocity(YseMidiNote *n, unsigned char velocity)#
unsigned char yse_midi_note_get_velocity(YseMidiNote *n)#

yse_music.h — music-theory primitives (note, positioned note, scale, motif) + the generative player sequencer.

C ABI mirror of YseEngine/music/ + YseEngine/player/playerInterface.hpp.

All four primitive types and the player are wrapped as opaque handles. Notes and pNotes are value-like in C++ but exposed as heap allocations here so the Dart side can NativeFinalizer-back them uniformly.

Note: where the C++ API exposes operator overloads (pitch arithmetic on notes), only the explicit setPitch / getPitch is wrapped — the arithmetic operators are easy to replicate at the Dart layer.

Typedefs

typedef struct YseNote YseNote#

Owned — release with yse_note_destroy.

typedef struct YsePNote YsePNote#

Owned — release with yse_pnote_destroy.

typedef struct YseScale YseScale#

Owned — release with yse_scale_destroy.

typedef struct YseMotif YseMotif#

Owned — release with yse_motif_destroy.

typedef struct YsePlayer YsePlayer#

Owned — release with yse_player_destroy.

Functions

YseNote *yse_note_create(float pitch, float volume, float length, int channel)#
void yse_note_destroy(YseNote *n)#
void yse_note_set(YseNote *n, float pitch, float volume, float length, int channel)#
void yse_note_set_pitch(YseNote *n, float pitch)#
void yse_note_set_volume(YseNote *n, float volume)#
void yse_note_set_length(YseNote *n, float length)#
void yse_note_set_channel(YseNote *n, int channel)#
float yse_note_get_pitch(YseNote *n)#
float yse_note_get_volume(YseNote *n)#
float yse_note_get_length(YseNote *n)#
int yse_note_get_channel(YseNote *n)#
YsePNote *yse_pnote_create(float position, float pitch, float volume, float length, int channel)#
void yse_pnote_destroy(YsePNote *n)#
void yse_pnote_set_position(YsePNote *n, float position)#
float yse_pnote_get_position(YsePNote *n)#
void yse_pnote_set_pitch(YsePNote *n, float pitch)#

Inherited from note:

void yse_pnote_set_volume(YsePNote *n, float volume)#
void yse_pnote_set_length(YsePNote *n, float length)#
float yse_pnote_get_pitch(YsePNote *n)#
float yse_pnote_get_volume(YsePNote *n)#
float yse_pnote_get_length(YsePNote *n)#
void yse_pnote_set_channel(YsePNote *n, int channel)#
int yse_pnote_get_channel(YsePNote *n)#
YseScale *yse_scale_create(void)#
void yse_scale_destroy(YseScale *s)#
void yse_scale_add(YseScale *s, float pitch, float octave_step)#
void yse_scale_remove(YseScale *s, float pitch, float octave_step)#
int yse_scale_has(YseScale *s, float pitch)#
float yse_scale_nearest(YseScale *s, float pitch)#
unsigned int yse_scale_size(YseScale *s)#
void yse_scale_clear(YseScale *s)#
YseMotif *yse_motif_create(void)#
void yse_motif_destroy(YseMotif *m)#
void yse_motif_add(YseMotif *m, YsePNote *note)#
void yse_motif_clear(YseMotif *m)#
void yse_motif_set_length(YseMotif *m, float length)#
void yse_motif_set_length_auto(YseMotif *m)#
void yse_motif_transpose(YseMotif *m, float pitch)#
void yse_motif_set_first_pitch(YseMotif *m, YseScale *valid_pitches)#
float yse_motif_get_length(YseMotif *m)#
int yse_motif_empty(YseMotif *m)#
unsigned int yse_motif_size(YseMotif *m)#
YsePlayer *yse_player_create(YseSynth *synth)#

Create a generative player bound to synth and register it with the engine.

synth must be a live handle from yse_synth_create and must outlive the player — every note the player generates is delivered to it. Returns NULL (with yse_last_error() set) when synth is NULL or on allocation failure. This is the only create path: a player has no useful state until it is bound to a synth, so unlike most create functions it takes its target up front (issue #268).

void yse_player_destroy(YsePlayer *p)#
void yse_player_play(YsePlayer *p)#
void yse_player_stop(YsePlayer *p)#
int yse_player_is_playing(YsePlayer *p)#
void yse_player_set_minimum_pitch(YsePlayer *p, float target, float fade_time)#
void yse_player_set_maximum_pitch(YsePlayer *p, float target, float fade_time)#
void yse_player_set_minimum_velocity(YsePlayer *p, float target, float fade_time)#
void yse_player_set_maximum_velocity(YsePlayer *p, float target, float fade_time)#
void yse_player_set_minimum_gap(YsePlayer *p, float target, float fade_time)#
void yse_player_set_maximum_gap(YsePlayer *p, float target, float fade_time)#
void yse_player_set_minimum_length(YsePlayer *p, float target, float fade_time)#
void yse_player_set_maximum_length(YsePlayer *p, float target, float fade_time)#
void yse_player_set_voices(YsePlayer *p, unsigned int target, float fade_time)#
void yse_player_set_scale(YsePlayer *p, YseScale *scale, float fade_time)#
void yse_player_add_motif(YsePlayer *p, YseMotif *motif, unsigned int weight)#
void yse_player_remove_motif(YsePlayer *p, YseMotif *motif)#
void yse_player_adjust_motif_weight(YsePlayer *p, YseMotif *motif, unsigned int weight)#
void yse_player_play_partial_motifs(YsePlayer *p, float target, float fade_time)#
void yse_player_play_motifs(YsePlayer *p, float target, float fade_time)#
void yse_player_fit_motifs_to_scale(YsePlayer *p, float target, float fade_time)#

Clip transport#

Beat-timed note clips dispatched from the audio thread against a domain clock, targeting internal synths or an external MIDI-out port. The clocks themselves are created and steered with the yse_system_*_clock functions in yse_system.h above. Clocks and clips walks through both, with a C example.

yse_clip.h — clip transport (issue #250).

C ABI mirror of YseEngine/clip/clip.hpp.

A clip transport loops a flat, beat-timed note-event list against a domain clock (see yse_system_create_clock in yse_system.h), dispatched from the audio thread — so the FFI/UI side never dispatches a note. The event list is replaceable while playing; the swap is lock-free with respect to the audio thread and sounding notes still receive their note-off across the swap.

Output targets one or more YseSynth instances (RT-safe internal event queues) and — on builds with MIDI device support — one or more external MIDI output ports (issue #350): the audio thread stamps fired messages with an absolute send time and a dedicated sender thread performs the RtMidi sends, so the audio callback never touches the device. Channels are 1..16; pitches are 0..127; velocity and pitch bend are normalized (see YseClipEvent).

Typedefs

typedef struct YseClip YseClip#

Owned — release with yse_clip_destroy.

typedef struct YseSynth YseSynth#

Opaque synth handle (defined in yse_synth.h).

Owned — release with yse_synth_destroy.

Forward declaration — the synth a player drives.

Opaque synth handle (defined in yse_synth.h) — target of yse_midi_file_connect_synth / yse_midi_in_connect_synth.

Owned by the caller and created via yse_synth_create (see yse_synth.h); it must outlive the player. The player feeds every note it generates into this synth’s lock-free inbox.

The handle also owns the built-in voice prototypes created by yse_synth_add_voices_*, freeing them on destroy; keep the synth alive until after any sound rendering it is destroyed (see yse_synth_attach_to_sound).

typedef struct YseMidiOut YseMidiOut#

Opaque MIDI-out handle (defined in yse_midi.h).

Owned — release with yse_midi_out_destroy.

typedef struct YseClipEvent YseClipEvent#

One timed note event, positioned in beats on the bound domain clock.

Layout-compatible with YSE::clipEvent.

Functions

YseClip *yse_clip_create(void)#
void yse_clip_destroy(YseClip *c)#
YseStatus yse_clip_bind(YseClip *c, const char *clock_name)#

Bind the clip to a live domain clock by name.

YSE_OK on success; YSE_ERR_INVALID_HANDLE on a NULL clip, YSE_ERR_INVALID_ARGUMENT on a NULL name or when no live clock owns the name — which also unbinds the clip from any clock it was bound to. Every failure leaves its reason in yse_last_error(). Binding shares the clock’s lifetime, so yse_system_destroy_clock() on a bound clock is safe in any order: the clock stops advancing and the clip simply stops firing.

void yse_clip_set_events(YseClip *c, const YseClipEvent *events, size_t count)#

Replace the event list (copied).

Takes effect at the next audio block boundary. events may be NULL only when count is 0 (clears the list).

void yse_clip_set_loop_length(YseClip *c, double beats)#

Loop length in beats.

Values <= 0 disable looping (events fire once).

void yse_clip_connect_synth(YseClip *c, YseSynth *synth)#

Route this clip’s playback into a synth (may be called for several synths).

The synth must outlive the connection.

void yse_clip_disconnect_synth(YseClip *c, YseSynth *synth)#
void yse_clip_connect_midi_out(YseClip *c, YseMidiOut *m)#

Route this clip’s playback to an external MIDI output port (issue #350).

m must already have opened a port (yse_midi_out_open); connecting an unopened handle is ignored. The underlying device port is engine-owned and stays open, so m itself may be destroyed after connecting. May be called for several ports. The audio thread stamps fired messages with an absolute send time and hands them to a dedicated sender thread over a bounded lock-free queue — timing stays block-accurate, and the audio callback never performs the send. On builds without MIDI device support these set last_error and no-op.

void yse_clip_disconnect_midi_out(YseClip *c, YseMidiOut *m)#
void yse_clip_play(YseClip *c)#
void yse_clip_stop(YseClip *c)#
int yse_clip_is_playing(YseClip *c)#
struct YseClipEvent

One timed note event, positioned in beats on the bound domain clock.

Layout-compatible with YSE::clipEvent.

Public Members

double start_beat#

beat within the loop at which the note starts (>= 0)

double duration_beats#

note length in beats

int channel#

MIDI channel, 1..16.

int pitch#

MIDI note number, 0..127.

float velocity#

normalized to [0, 1]

float pitch_bend#

optional per-note bend, [-1, 1]; 0 = none

Live coding: scripting and bus tap#

The embedded-Python live-coding surface (YSE_ENABLE_PYTHON builds; the symbols exist in every build) and the host bus tap, which subscribes the host to a bus-address prefix and delivers (address, value) frames on the thread that drives yse_system_update().

yse_python.h — C ABI scripting surface (issue #125, epic #119).

Lets a host (Phi via dart-yse, Python ctypes, …) submit scripts to the embedded CPython interpreter and receive errors asynchronously. The three symbols ship in every build with identical signatures; only their behaviour differs between a YSE_ENABLE_PYTHON=ON and =OFF library, so a binding can be written once and link against either.

Self-contained C header: depends only on yse_common.h.

Typedefs

typedef void (*YseScriptErrorCallback)(const char *traceback_utf8, void *userdata)#

Callback that receives formatted tracebacks from uncaught Python exceptions and syntax errors.

traceback_utf8 is owned by the engine and valid only for the duration of the call — copy it if you need to retain it.

Functions

int yse_python_enabled(void)#

Compile-time feature query.

Returns 1 if the library was built with YSE_ENABLE_PYTHON=ON, else 0. Safe to call regardless of build configuration or engine state.

void yse_python_run_script(const char *src)#

Submit a UTF-8 script for asynchronous evaluation on the script thread.

Returns immediately. The engine takes a copy; the caller may free src on return. A NULL src is a no-op.

On a YSE_ENABLE_PYTHON=OFF build, the registered error callback (if any) is invoked synchronously, before this returns, with the literal string “YSE compiled without YSE_ENABLE_PYTHON”.

void yse_python_set_script_error_callback(YseScriptErrorCallback cb, void *userdata)#

Register the error callback.

Pass NULL to clear. Thread-safe via atomic swap (the project’s callback-bridge convention): installing or replacing the callback while the engine is dispatching a traceback is safe, and the previous callback never receives a later traceback.

On an ON build the callback fires on the host thread that drives yse_system_update(), once per failed script. Works identically on an OFF build (it just stores the pointer), so a host may register unconditionally.

yse_bus.h — host access to the global named bus: prefix taps (issue #389), plus publish and exact-address subscribe (issue #904).

Lets a host (Phi via dart-yse, Python ctypes, …) subscribe to a bus address prefix* and receive an (address, value) frame for every publish whose address starts with that prefix — e.g. a “phi.ctl.” tap sees every live-coding control verb a script publishes. Multiple taps may be live at once, each with its own callback + user_data.

This is host-only surface: the script-facing subscribe (yse.on()) stays exact-match with no wildcards, per the DSL spec (docs/design/ live_coding_dsl.md §”Address grammar”). Prefix matching happens inside the engine’s control-thread dispatch, so a tap adds zero audio-thread cost.

Delivery: the callback fires on the thread that drives yse_system_update() (the control thread). Publishes made on the control thread itself are delivered synchronously inside the publish; publishes from any other thread (audio callback, script thread, timer thread) are queued and delivered during the next yse_system_update() tick.

Convention (see yse_c_internal.hpp): void functions taking a handle are null-safe no-ops on a NULL handle.

Self-contained C header: depends only on yse_common.h.

Typedefs

typedef struct YseBusTap YseBusTap#

Owned — release with yse_bus_tap_destroy.

typedef enum YseBusValueKind YseBusValueKind

Payload kind of a bus value.

The bus carries five kinds: bang (a valueless trigger, published e.g. by a patcher gSend bang outlet), int, float, string, and float list. Bus int/float are 32-bit.

typedef void (*YseBusTapCallback)(const char *address, YseBusValueKind kind, int i, float f, const char *str, const float *list, size_t list_len, void *user_data)#

Receives one (address, value) frame per matching publish.

Exactly one of the payload parameters is meaningful, selected by kind:

YSE_BUS_BANG — no payload (i, f, str, list are all zero/NULL) YSE_BUS_INT — i YSE_BUS_FLOAT — f YSE_BUS_STRING — str (NUL-terminated UTF-8) YSE_BUS_LIST — list + list_len (list may be NULL when list_len is 0)

address, str and list are owned by the engine and valid ONLY for the duration of the call — copy anything you need to retain before returning. There is no free function (same contract as YseScriptErrorCallback).

typedef struct YseBusSub YseBusSub#

Owned — release with yse_bus_unsubscribe.

typedef void (*YseBusSubCallback)(const char *address, YseBusValueKind kind, int i, float f, const char *str, const float *list, size_t list_len, void *user_data)#

Same frame contract as YseBusTapCallback: address is the subscribed address, exactly one payload parameter is meaningful per kind, and every pointer is engine-owned and valid only for the duration of the call.

Enums

enum YseBusValueKind#

Payload kind of a bus value.

The bus carries five kinds: bang (a valueless trigger, published e.g. by a patcher gSend bang outlet), int, float, string, and float list. Bus int/float are 32-bit.

Values:

enumerator YSE_BUS_BANG#
enumerator YSE_BUS_INT#
enumerator YSE_BUS_FLOAT#
enumerator YSE_BUS_STRING#
enumerator YSE_BUS_LIST#

Functions

YseBusTap *yse_bus_tap_create(const char *prefix, YseBusTapCallback cb, void *user_data)#

Subscribe cb to every bus publish whose address starts with prefix (plain byte-wise prefix match; an empty string matches every address).

Returns NULL — with the reason in yse_last_error() — if prefix or cb is NULL, or if the engine is not initialised: create taps after yse_system_init / yse_system_init_offline.

Lifecycle: yse_system_close() invalidates every live tap — the engine-side subscription dies with the bus, and no callback fires after close returns. The YseBusTap handle itself stays safe to destroy (before or after a re-init), but it does not reattach: re-create taps after the next init.

Threading: call create and destroy on the control thread (the one driving yse_system_update()). That guarantees no callback fires after destroy returns.

void yse_bus_tap_destroy(YseBusTap *tap)#

Unsubscribe and release the tap.

Null-safe no-op.

YseStatus yse_bus_publish_bang(const char *address)#

── Publish (issue #904) ─────────────────────────────────────────────────

Publish a value to one exact bus address — the same path a script’s yse.send() takes, so it reaches everything a script can: a named synth’s “synth.<name>.note”, a patcher’s .r on “patcher.<name>.<slot>”, a named channel’s “channel.<name>.volume”, exact subscriptions and prefix taps.

Thread attribution: the publish is tagged as a control-rate (non-audio) publish, like a script’s. On the control thread (the one driving yse_system_update()) it dispatches synchronously — every subscriber and tap has run when the call returns. From any other host thread it is parked in the bus’s control inbox (a short mutex-guarded append, no dispatch) and delivered during the next yse_system_update(). Do not call these from inside a real-time audio callback, and do not publish from another thread concurrently with yse_system_close().

Returns YSE_ERR_NOT_INITIALIZED when the engine is down, YSE_ERR_INVALID_ARGUMENT for a NULL or empty address (or a NULL string / a NULL list with a non-zero count), YSE_ERR_EXCEPTION on an internal failure — each with the reason in yse_last_error(). A publish no one listens to is not an error.

YseStatus yse_bus_publish_int(const char *address, int value)#
YseStatus yse_bus_publish_float(const char *address, float value)#
YseStatus yse_bus_publish_string(const char *address, const char *value)#

value is NUL-terminated UTF-8, copied before the call returns.

YseStatus yse_bus_publish_list(const char *address, const float *values, size_t count)#

values is copied before the call returns; it may be NULL when count is 0 (an empty list).

YseBusSub *yse_bus_subscribe(const char *address, YseBusSubCallback cb, void *user_data)#

Subscribe cb to every publish on exactly address.

Returns NULL — with the reason in yse_last_error() — if address is NULL or empty, cb is NULL, or the engine is not initialised.

Delivery, lifecycle and threading follow the tap rules above: callbacks fire on the control thread (inline for a control-thread publish, else during yse_system_update()); yse_system_close() invalidates every live subscription (the handle stays safe to release, but does not reattach after a re-init); call subscribe and unsubscribe on the control thread, which guarantees no callback fires after unsubscribe returns.

void yse_bus_unsubscribe(YseBusSub *sub)#

Unsubscribe and release the subscription.

Null-safe no-op.

Logging and buffer I/O#

yse_log.h — engine logging.

C ABI mirror of YseEngine/log.hpp.

Two ways to consume log output:

  1. Default file sink (set with yse_log_set_logfile / inspect with yse_log_get_logfile).

  2. C callback installed via yse_log_set_callback. The callback is invoked from whichever engine thread emitted the log entry (never the audio thread), one call at a time, with the engine’s log lock held. It must therefore not call back into the library: a yse_* call that logs, or yse_log_send_message / yse_log_set_callback / yse_log_set_logfile, deadlocks. Copy or queue the message, free it and return; do not block. Pass NULL to restore the default file sink.

Logging is only active between yse_system_init() and yse_system_close().

Typedefs

typedef struct YseLog YseLog#

Borrowed singleton — owned by the engine, never destroy.

Obtain via yse_log_get().

typedef void (*YseLogCallback)(char *msg, void *user_data)#

The receiver OWNS msg and must release it with yse_log_free_message when finished.

The pointer is allocated with malloc by the bridge so any C client can free() it directly if preferred.

Functions

YseLog *yse_log_get(void)#
void yse_log_send_message(YseLog *log, const char *msg)#
void yse_log_set_level(YseLog *log, YseErrorLevel level)#
YseErrorLevel yse_log_get_level(YseLog *log)#
void yse_log_set_logfile(YseLog *log, const char *path)#
size_t yse_log_get_logfile(YseLog *log, char *buf, size_t cap)#
void yse_log_set_callback(YseLog *log, YseLogCallback cb, void *user_data)#

Replaces the default file sink.

Pass NULL for cb to restore the default. user_data is opaque — forwarded to every callback invocation.

void yse_log_free_message(char *msg)#

Release a message string previously delivered to a YseLogCallback.

yse_buffer_io.h — feed sound files from in-memory byte buffers.

C ABI mirror of YseEngine/BufferIO.hpp.

Register bytes under string IDs, then load sounds by passing those IDs where a file path would normally go. Used for asset packs and bundled-resource scenarios where the OS file system isn’t available (Android APKs, encrypted asset bundles, etc.).

Typedefs

typedef struct YseBufferIO YseBufferIO#

Owned — release with yse_buffer_io_destroy.

Functions

YseBufferIO *yse_buffer_io_create(int store_copy)#

store_copy=1 copies the supplied bytes; store_copy=0 keeps a pointer to the caller’s buffer (which must outlive the registration).

void yse_buffer_io_destroy(YseBufferIO *io)#
void yse_buffer_io_set_active(YseBufferIO *io, int on)#
int yse_buffer_io_get_active(YseBufferIO *io)#
int yse_buffer_io_name_exists(YseBufferIO *io, const char *id)#
int yse_buffer_io_add(YseBufferIO *io, const char *id, char *buffer, int length)#
int yse_buffer_io_remove_by_name(YseBufferIO *io, const char *id)#
int yse_buffer_io_exists(YseBufferIO *io, const char *buffer)#

Look up / unregister a buffer by the ADDRESS the host passed to yse_buffer_io_add() (BufferIO::BufferExists / RemoveBuffer, issue #909).

Only meaningful with store_copy=0: a store_copy=1 layer registers its own copy, so the host’s address never matches and both return 0. Return 1 when a registration matched (and, for remove, was dropped), 0 otherwise or on a NULL argument.

int yse_buffer_io_remove(YseBufferIO *io, const char *buffer)#

Common types and enums#

yse_common.h — shared types, status codes, error reporting.

Self-contained C header: no transitive includes outside <stddef.h>. Consumed by Dart ffigen and any other C ABI client.

Defines

YSE_C_API#
YSE_C_CALLBACK#

Typedefs

typedef enum YseStatus YseStatus
typedef struct yse_pos_t yse_pos_t#

Enums

enum YseStatus#

Values:

enumerator YSE_OK#
enumerator YSE_ERR_GENERIC#
enumerator YSE_ERR_NOT_INITIALIZED#
enumerator YSE_ERR_INVALID_HANDLE#
enumerator YSE_ERR_INVALID_ARGUMENT#
enumerator YSE_ERR_FILE_NOT_FOUND#
enumerator YSE_ERR_AUDIO_DEVICE#
enumerator YSE_ERR_MIDI#
enumerator YSE_ERR_EXCEPTION#

Functions

const char *yse_version(void)#
const char *yse_last_error(void)#

Returns the last error message recorded on the calling thread (the slot is thread-local).

The returned pointer is valid until the next yse_* call from the same thread; copy the string if you need to hold onto it. Empty string when no error has been recorded.

void yse_clear_last_error(void)#
void yse_free_string(char *s)#

Release a string the library allocated and handed to the caller — any function whose comment says its result is released with yse_free_string (e.g.

yse_patcher_get_metadata_json). NULL is a no-op. Never pass it a pointer to engine-owned storage or a buffer the caller allocated.

struct yse_pos_t

Public Members

float x#
float y#
float z#

yse_enums.h — C mirrors of YSE::enums.

Values MUST stay in lockstep with YseEngine/headers/enums.hpp. The file is hand-mirrored; yse_enums_check.cpp static_asserts every value against the engine, so a drifted value fails the build.

Typedefs

typedef enum YseChannelType YseChannelType

Mirrors YSE::CHANNEL_TYPE in headers/enums.hpp.

typedef enum YseReconnectState YseReconnectState

Mirrors YSE::RECONNECT_STATE in headers/enums.hpp: what the yse_system_auto_reconnect() watchdog last did (issue #1027).

typedef enum YseErrorLevel YseErrorLevel

Mirrors YSE::ERROR_LEVEL in headers/enums.hpp.

typedef enum YseOutType YseOutType

Mirrors YSE::OUT_TYPE in headers/enums.hpp (patcher outlet data type).

typedef enum YseLfoType YseLfoType

Mirrors YSE::DSP::LFO_TYPE in dsp/lfo.hpp.

typedef enum YseVaWaveform YseVaWaveform

Mirrors YSE::SYNTH::VA_WAVEFORM in synth/vaVoice.hpp — the oscillator waveform selector for the virtual-analog voice (yse_synth_va_set_osc_wave).

typedef enum YseDspSweepShape YseDspSweepShape

Mirrors YSE::DSP::MODULES::sweepFilter::SHAPE.

typedef enum YseDspDelayTap YseDspDelayTap

Mirrors YSE::DSP::MODULES::basicDelay::DELAY_NR.

typedef enum YseChorusMode YseChorusMode

Mirrors YSE::DSP::MODULES::chorusMode (chorus.hpp) — the chorus/flanger topology switch.

typedef enum YseEqBand YseEqBand

Mirrors YSE::DSP::MODULES::eqBand (parametricEQ.hpp) — the four fixed bands of the parametric EQ.

YSE_EQ_BAND_COUNT is the sentinel count.

typedef enum YseCompressorDetector YseCompressorDetector

Mirrors YSE::DSP::MODULES::compressorDetector (compressor.hpp) — the level-detector mode.

typedef enum YsePCategory YsePCategory

Mirrors YSE::PATCHER::pCategory in patcher/pEnums.h.

The patcher registry exposes this through the metadata API so binding-side documentation generators can group objects without inspecting the engine source. Values are only ever appended, so a number a host stored keeps its meaning. GENERIC is the fallback for an object that fits no other category.

typedef enum YseInletAccepts YseInletAccepts

Bitmask of message types an inlet accepts; mirrors YSE::PATCHER::InletType.

OR the flags together; check with &.

typedef enum YseSynthPositionHandler YseSynthPositionHandler

Selects which built-in per-note position handler yse_synth_set_position_handler attaches.

Mirrors the three shipped handler classes in synth/positionHandlers.hpp (staticHandler / randomSpreadHandler / orbitHandler). This is a C-API-owned dispatch selector: the engine has no single enum of handler kinds (each is a distinct class), so its drift guard is structural, not a value mirror — the exhaustive dispatch in yse_synth.cpp plus the YSE_POSITION_HANDLER_COUNT sentinel and the is_base_of asserts in yse_enums_check.cpp. Keep YSE_POSITION_HANDLER_COUNT last.

typedef enum YseSynthHandlerParam YseSynthHandlerParam

Shared handler-parameter indices the built-in handlers read for their steerable centre; mirrors YSE::SYNTH::HandlerParamIndex in synth/positionHandlers.hpp.

Pass one to yse_synth_handler_param to move the swarm / spread centre at runtime. Indices 0..2 are the centre X / Y / Z; higher indices (up to the engine’s block size) are free for custom use.

typedef enum YseReverbPreset YseReverbPreset

Mirrors YSE::REVERB_PRESET in headers/enums.hpp.

Enums

enum YseChannelType#

Mirrors YSE::CHANNEL_TYPE in headers/enums.hpp.

Values:

enumerator YSE_CT_AUTO#
enumerator YSE_CT_MONO#
enumerator YSE_CT_STEREO#
enumerator YSE_CT_QUAD#
enumerator YSE_CT_51#
enumerator YSE_CT_51SIDE#
enumerator YSE_CT_61#
enumerator YSE_CT_71#
enumerator YSE_CT_CUSTOM#
enum YseReconnectState#

Mirrors YSE::RECONNECT_STATE in headers/enums.hpp: what the yse_system_auto_reconnect() watchdog last did (issue #1027).

Values:

enumerator YSE_RS_NONE#

No attempt since init, a device open, or the last auto_reconnect call.

enumerator YSE_RS_RECONNECTING#

The last attempt opened nothing; the watchdog tries again.

enumerator YSE_RS_RESTORED#

The last attempt reopened the session’s own device.

enumerator YSE_RS_FELL_BACK_TO_DEFAULT#

The default output device was opened instead.

enumerator YSE_RS_FAILED#

The attempt limit was reached; the watchdog stopped trying.

enum YseErrorLevel#

Mirrors YSE::ERROR_LEVEL in headers/enums.hpp.

Values:

enumerator YSE_EL_NONE#
enumerator YSE_EL_ERROR#
enumerator YSE_EL_WARNING#
enumerator YSE_EL_DEBUG#
enum YseOutType#

Mirrors YSE::OUT_TYPE in headers/enums.hpp (patcher outlet data type).

Values:

enumerator YSE_OUT_INVALID#
enumerator YSE_OUT_BANG#
enumerator YSE_OUT_FLOAT#
enumerator YSE_OUT_INT#
enumerator YSE_OUT_BUFFER#
enumerator YSE_OUT_LIST#
enumerator YSE_OUT_ANY#
enum YseLfoType#

Mirrors YSE::DSP::LFO_TYPE in dsp/lfo.hpp.

Values:

enumerator YSE_LFO_NONE#
enumerator YSE_LFO_SAW#
enumerator YSE_LFO_SAW_REVERSED#
enumerator YSE_LFO_TRIANGLE#
enumerator YSE_LFO_SINE#
enumerator YSE_LFO_SQUARE#
enumerator YSE_LFO_RANDOM#
enum YseVaWaveform#

Mirrors YSE::SYNTH::VA_WAVEFORM in synth/vaVoice.hpp — the oscillator waveform selector for the virtual-analog voice (yse_synth_va_set_osc_wave).

Values:

enumerator YSE_VA_SAW#

Band-limited sawtooth.

enumerator YSE_VA_PULSE#

Band-limited pulse with variable width (PWM).

enumerator YSE_VA_TRIANGLE#

Band-limited triangle.

enumerator YSE_VA_SINE#

Sine.

enumerator YSE_VA_NOISE#

White noise.

enumerator YSE_VA_WAVETABLE#

Morph across the wavetable bank.

enum YseDspSweepShape#

Mirrors YSE::DSP::MODULES::sweepFilter::SHAPE.

Values:

enumerator YSE_SWEEP_TRIANGLE#
enumerator YSE_SWEEP_SAW#
enumerator YSE_SWEEP_SQUARE#
enum YseDspDelayTap#

Mirrors YSE::DSP::MODULES::basicDelay::DELAY_NR.

Values:

enumerator YSE_DELAY_TAP_FIRST#
enumerator YSE_DELAY_TAP_SECOND#
enumerator YSE_DELAY_TAP_THIRD#
enum YseChorusMode#

Mirrors YSE::DSP::MODULES::chorusMode (chorus.hpp) — the chorus/flanger topology switch.

Values:

enumerator YSE_CHORUS_MODE_CHORUS#

Longer base delay, wide slow sweep.

enumerator YSE_CHORUS_MODE_FLANGER#

Short base delay, feedback comb.

enum YseEqBand#

Mirrors YSE::DSP::MODULES::eqBand (parametricEQ.hpp) — the four fixed bands of the parametric EQ.

YSE_EQ_BAND_COUNT is the sentinel count.

Values:

enumerator YSE_EQ_LOW_SHELF#
enumerator YSE_EQ_PEAK_1#
enumerator YSE_EQ_PEAK_2#
enumerator YSE_EQ_HIGH_SHELF#
enumerator YSE_EQ_BAND_COUNT#
enum YseCompressorDetector#

Mirrors YSE::DSP::MODULES::compressorDetector (compressor.hpp) — the level-detector mode.

Values:

enumerator YSE_COMPRESSOR_DETECT_PEAK#

Instantaneous linked peak.

enumerator YSE_COMPRESSOR_DETECT_RMS#

Short mean-square window.

enum YsePCategory#

Mirrors YSE::PATCHER::pCategory in patcher/pEnums.h.

The patcher registry exposes this through the metadata API so binding-side documentation generators can group objects without inspecting the engine source. Values are only ever appended, so a number a host stored keeps its meaning. GENERIC is the fallback for an object that fits no other category.

Values:

enumerator YSE_PCAT_UNSET#
enumerator YSE_PCAT_OSC#

Signal generators (~sine, ~noise, ~line, …).

enumerator YSE_PCAT_FILTER#

Filters.

enumerator YSE_PCAT_MATH#

Arithmetic, conversion, analysis.

enumerator YSE_PCAT_GENERIC#

Uncategorisable fallback.

enumerator YSE_PCAT_GUI#

User-facing controls and message boxes.

enumerator YSE_PCAT_TIME#

Timing utilities (.metro, .delay, .line, …).

enumerator YSE_PCAT_MIDI#

MIDI formatting, parsing and device I/O.

enumerator YSE_PCAT_ROUTING#

Steering messages (.gate, .route, .sel, …).

enumerator YSE_PCAT_CONTROL#

Control flow and ordering (.trigger, .uzi, …).

enumerator YSE_PCAT_LIST#

Building and taking apart lists (.pack, .zl, …).

enumerator YSE_PCAT_STRING#

Symbols and text (.sprintf, .regexp, …).

enumerator YSE_PCAT_COLLECTION#

Stores (.coll, .table, .bag, …).

enumerator YSE_PCAT_DICT#

The .dict family.

enumerator YSE_PCAT_ARRAY#

The .array family.

enumerator YSE_PCAT_RANDOM#

Random and probabilistic sources.

enumerator YSE_PCAT_IO#

Audio and host I/O (~adc, ~dac, .s, .r, .print, …).

enumerator YSE_PCAT_ENCAPSULATION#

Subpatchers and their inlets/outlets.

enumerator YSE_PCAT_SEQUENCE#

Timed recorders / players (.seq, .mtr, .qlist).

enum YseInletAccepts#

Bitmask of message types an inlet accepts; mirrors YSE::PATCHER::InletType.

OR the flags together; check with &.

Values:

enumerator YSE_IN_ACCEPTS_BUFFER#
enumerator YSE_IN_ACCEPTS_FLOAT#
enumerator YSE_IN_ACCEPTS_INT#
enumerator YSE_IN_ACCEPTS_BANG#
enumerator YSE_IN_ACCEPTS_LIST#
enum YseSynthPositionHandler#

Selects which built-in per-note position handler yse_synth_set_position_handler attaches.

Mirrors the three shipped handler classes in synth/positionHandlers.hpp (staticHandler / randomSpreadHandler / orbitHandler). This is a C-API-owned dispatch selector: the engine has no single enum of handler kinds (each is a distinct class), so its drift guard is structural, not a value mirror — the exhaustive dispatch in yse_synth.cpp plus the YSE_POSITION_HANDLER_COUNT sentinel and the is_base_of asserts in yse_enums_check.cpp. Keep YSE_POSITION_HANDLER_COUNT last.

Values:

enumerator YSE_POSITION_HANDLER_STATIC#

staticHandler — one fixed position.

enumerator YSE_POSITION_HANDLER_RANDOM_SPREAD#

randomSpreadHandler — seeded scatter.

enumerator YSE_POSITION_HANDLER_ORBIT#

orbitHandler — the swarm handler.

enumerator YSE_POSITION_HANDLER_COUNT#

sentinel — number of kinds; keep last.

enum YseSynthHandlerParam#

Shared handler-parameter indices the built-in handlers read for their steerable centre; mirrors YSE::SYNTH::HandlerParamIndex in synth/positionHandlers.hpp.

Pass one to yse_synth_handler_param to move the swarm / spread centre at runtime. Indices 0..2 are the centre X / Y / Z; higher indices (up to the engine’s block size) are free for custom use.

Values:

enumerator YSE_HANDLER_PARAM_CENTER_X#
enumerator YSE_HANDLER_PARAM_CENTER_Y#
enumerator YSE_HANDLER_PARAM_CENTER_Z#
enum YseReverbPreset#

Mirrors YSE::REVERB_PRESET in headers/enums.hpp.

Values:

enumerator YSE_REVERB_OFF#
enumerator YSE_REVERB_GENERIC#
enumerator YSE_REVERB_PADDED#
enumerator YSE_REVERB_ROOM#
enumerator YSE_REVERB_BATHROOM#
enumerator YSE_REVERB_STONEROOM#
enumerator YSE_REVERB_LARGEROOM#
enumerator YSE_REVERB_HALL#
enumerator YSE_REVERB_CAVE#
enumerator YSE_REVERB_SEWERPIPE#
enumerator YSE_REVERB_UNDERWATER#