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 |
Borrowed |
The engine owns it: the singletons ( |
Owned by a parent |
A |
Reference-counted asset |
|
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_destroyreleases everyYseDspBuffer, whichever of the four constructors made it. The handle remembers its constructor, so destroy frees the right object (#662).yse_dsp_object_destroyreleases 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,YseDspMultiBufferorYsePatcherloaded into a sound must outlive the sound. Destroy the sound first.A synth attached to a sound, or connected to a
YseMidiInor MIDI file, must outlive that connection.An effect set with
yse_sound_set_dsporyse_channel_set_dspis 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
voidfunction (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 exampleYSE_PATCHER_ID_NONE.A function that returns
YseStatusreturnsYSE_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_createhandle toyse_dsp_wavetable_create_saw) is undefined behaviour. The call returnsYSE_OKand 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 |
|---|---|
|
Success. |
|
The handle argument was |
|
A |
|
The call needs a running engine, for example a bus publish before
|
|
A sound file could not be loaded. |
|
|
|
The engine threw a C++ exception. The library catches it at the ABI boundary and reports it as this status. |
|
Any other failure. |
The last error. yse_last_error() returns a message about the most
recent failure:
Every error status except
YSE_ERR_INVALID_HANDLEcomes with a message, and so does a constructor or loader that fails and returnsNULL. ANULLhandle may returnYSE_ERR_INVALID_HANDLEwithout one. Test the status first, and read the message second.A
voidfunction 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(). Readingyse_last_error()after a call that returnedYSE_OKcan 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 mostcap - 1bytes and a NUL, and return the full length. Passbuf = NULL, cap = 0to ask for the size first. A return value ofcapor more means the text was cut short. On aNULLhandle they write an empty string and return 0.Owned by the library.
yse_version(),yse_last_error()and the patcher metadata getters returnconst char*that you never free.Owned by you.
yse_patcher_get_metadata_json()returns achar*that you release withyse_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)returnsNULLand sets the last error wheniis not belowyse_system_num_devices(). An offline session lists no devices untilyse_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
NULLdescriptor.The MIDI device name getters return an empty string for an
idwith 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_updateandyse_system_closefrom 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 |
|---|---|---|
|
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 |
|
The thread that calls |
May lock, allocate and query a physics engine. The positions are copies, valid for the call only. |
|
The control thread: inline for a publish made on it, otherwise during
|
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. |
|
The thread that calls |
The traceback is valid for the call only. |
|
The thread that sent the message: yours for a |
You own |
|
RtMidi’s input thread for that port. |
Return quickly. You own the raw callback’s |
|
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 |
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 |
|---|---|
|
|
|
|
|
Returned |
|
Renamed |
|
Renamed |
|
Moved from |
|
Returns |
Functions added:
Area |
Functions |
|---|---|
Session |
|
Named bus |
|
Sounds and buffers |
|
Effects and reverb |
|
Patcher |
|
Synth, MIDI and music |
|
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
srcandlistener, 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.
srcandlistenerpoint 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
cbremoves 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 NULLsysis a no-op.
Functions
-
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).
-
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).
-
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.
-
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.
-
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.
-
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().
-
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.
-
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_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.
outputsis 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 fromoutputs; YSE_CT_CUSTOM allocatesoutputsspeakers whose positions are set afterwards.
-
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
idat or past the matching count, or anyidat all on a host whose MIDI backend failed to initialise (issue #585). A non-empty name therefore always means a real port.
-
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.
nameis a NUL-terminated UTF-8 string. YSE_OK on success; YSE_ERR_INVALID_HANDLE on a NULLsys, YSE_ERR_INVALID_ARGUMENT whennameis 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
nameexists, else 0.
-
void yse_system_set_tempo(YseSystem *sys, const char *name, float bpm, float ramp_seconds)#
Ramp the named clock’s tempo toward
bpmoverramp_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_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
Functions
-
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
loopandvolumearguments 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
mbfeeds the sound’s channel i, so a two-channel buffer plays as a stereo source.Build
mbwith yse_dsp_multi_buffer_create() (see yse_dsp.h). The engine readsmbfor 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.
-
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_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)) ofchtoreturn_busatlevel.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
namepassed 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:
Owned: yse_reverb_create() → YseReverb*, paired with yse_reverb_destroy(). Use yse_reverb_set_position() to drop the zone in the scene.
Borrowed: yse_system_get_global_reverb() → YseReverb*. Never destroy. The global reverb is the fallback wherever no positioned zone reaches; rolled-off positioned zones mix against it.
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_set_reflection(YseReverb *rev, int reflection, int time, float gain)#
Early reflections (4 slots, index 0..3).
-
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.
outmay aliasaorb. 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]
-
float roomsize#
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
-
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_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.
bufandothermay be the same handle. A NULLbuforotheris 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
countsamples 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
countchannel 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
channelsis NULL,countis 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
nextafterheadin a processing chain (dspObject::link): with head->X already linked, the result is head->next->X.Pass NULL as
nextto 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
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.
addressis the name it was sent to — the “.s” argument or the pass_*to, not the “patcher.<name>.<slot>” bus address.kindsays 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:
addressandsare one malloc’d block, released with yse_patcher_free_message(address), which freesswith it — never freeson 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_outputsaudio 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 forcontainerto 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
outletarrives 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.
-
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_voicesbuilt-in sine voices (sine oscillator shaped by an ADSR envelope) responding to note numbers in [lowest_note, highest_note] onchannel(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
instrumentis 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.
oscis 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_lfo_type(YseSynth *h, YseLfoType type)#
-
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).cyclepoints tolengthnormalised 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
indexfrom 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
bankmay 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_op_output_level(YseSynth *h, int op, int level)#
Headline per-operator params.
opis the operator index 0..5 (OP1..OP6); out-of-range is ignored. 0..99
-
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_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_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_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.
channelmay 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).
kindselects the handler;paramsconfigures 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.
indexis 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_numberonchannelat (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
-
float static_x#
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.
namemay be NULL (identification only).fileis the absolute sample path.rootis the key that plays the sample untransposed;low/highbound the playable key range.attack/releaseare the amplitude envelope times in seconds;max_lengthcaps 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();
datais 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
indexintobufas 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.bufmay 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
nameexactly (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.
namemay be NULL (identification only).fileis the absolute sample path.rootis the key that plays the sample untransposed;low/highbound the playable key range.attack/releaseare the amplitude envelope times in seconds;max_lengthcaps 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).
-
const char *name#
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 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.
bytesis 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
synthblock-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.synthis 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
lengthbytes fromdataas 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.
-
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_filteris 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
Functions
-
YsePlayer *yse_player_create(YseSynth *synth)#
Create a generative player bound to
synthand register it with the engine.synthmust 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) whensynthis 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).
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 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
-
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.
eventsmay be NULL only whencountis 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_connect_midi_out(YseClip *c, YseMidiOut *m)#
Route this clip’s playback to an external MIDI output port (issue #350).
mmust 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, somitself 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)#
-
struct YseClipEvent
One timed note event, positioned in beats on the bound domain clock.
Layout-compatible with YSE::clipEvent.
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_utf8is 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
srcon return. A NULLsrcis 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 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 —
iYSE_BUS_FLOAT —fYSE_BUS_STRING —str(NUL-terminated UTF-8) YSE_BUS_LIST —list+list_len(list may be NULL when list_len is 0)address,strandlistare 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 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:
addressis the subscribed address, exactly one payload parameter is meaningful perkind, 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#
-
enumerator YSE_BUS_BANG#
Functions
-
YseBusTap *yse_bus_tap_create(const char *prefix, YseBusTapCallback cb, void *user_data)#
Subscribe
cbto every bus publish whose address starts withprefix(plain byte-wise prefix match; an empty string matches every address).Returns NULL — with the reason in yse_last_error() — if
prefixorcbis 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.
-
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_string(const char *address, const char *value)#
valueis NUL-terminated UTF-8, copied before the call returns.
-
YseStatus yse_bus_publish_list(const char *address, const float *values, size_t count)#
valuesis copied before the call returns; it may be NULL whencountis 0 (an empty list).
-
YseBusSub *yse_bus_subscribe(const char *address, YseBusSubCallback cb, void *user_data)#
Subscribe
cbto every publish on exactlyaddress.Returns NULL — with the reason in yse_last_error() — if
addressis NULL or empty,cbis 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.
Logging and buffer I/O#
yse_log.h — engine logging.
C ABI mirror of YseEngine/log.hpp.
Two ways to consume log output:
Default file sink (set with yse_log_set_logfile / inspect with yse_log_get_logfile).
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
-
void yse_log_set_level(YseLog *log, YseErrorLevel level)#
-
YseErrorLevel yse_log_get_level(YseLog *log)#
-
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.
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#
-
enumerator YSE_OK#
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
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
-
typedef enum YseDspDelayTap YseDspDelayTap
-
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#
-
enumerator YSE_CT_AUTO#
-
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.
-
enumerator YSE_RS_NONE#
-
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#
-
enumerator YSE_EL_NONE#
-
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#
-
enumerator YSE_OUT_INVALID#
-
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#
-
enumerator YSE_LFO_NONE#
-
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.
-
enumerator YSE_VA_SAW#
-
enum YseDspSweepShape#
Mirrors YSE::DSP::MODULES::sweepFilter::SHAPE.
Values:
-
enumerator YSE_SWEEP_TRIANGLE#
-
enumerator YSE_SWEEP_SAW#
-
enumerator YSE_SWEEP_SQUARE#
-
enumerator YSE_SWEEP_TRIANGLE#
-
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#
-
enumerator YSE_DELAY_TAP_FIRST#
-
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_CHORUS#
-
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#
-
enumerator YSE_EQ_LOW_SHELF#
-
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_PEAK#
-
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).
-
enumerator YSE_PCAT_UNSET#
-
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#
-
enumerator YSE_IN_ACCEPTS_BUFFER#
-
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.
-
enumerator YSE_POSITION_HANDLER_STATIC#
-
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#
-
enumerator YSE_HANDLER_PARAM_CENTER_X#
-
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#
-
enumerator YSE_REVERB_OFF#