Core

Contents

Core#

System#

YSE::System() starts and ends the engine session, opens audio devices and sets the sample rate. Sessions and devices explains the session lifecycle and device handling, and Threading model says which calls belong on the control thread.

namespace YSE

Public API of libYSE — sound playback, mixing, and 3D positional audio.

Entry points: YSE::System (lifecycle and audio device), YSE::Listener (3D origin), YSE::sound (a playable source), YSE::channel (mixing tree), YSE::reverb (positioned reverb zone), YSE::patcher (modular DSP graph), YSE::player (note sequencer). Sub-namespaces group domain-specific types: YSE::DSP for signal processing, YSE::MIDI for MIDI I/O, YSE::MUSIC for note / chord / motif primitives.

Note

Apart from their constructors, the oscillator and vcf classes must only be invoked from inside a DSP callback / process body.

Typedefs

typedef float (*occlusionFunc)(const Pos &source, const Pos &listener)#

Signature of a user-supplied sound-occlusion callback.

Given the source and listener positions, return the occlusion amount in the range [0.0, 1.0]: 0 means no obstruction (full volume), 1 means fully occluded (silent). The engine applies it as a gain duck (finalGain *= 1 - occlusion). Typical implementations raycast through game-world geometry. See system::occlusionCallback for the threading contract — this runs on the control thread, not the audio callback.

Functions

system &System()#

Access the singleton system object.

< This macro is added to all public class declarations.

Variables

const std::string VERSION = "3.1.0"#
class system#

Engine lifecycle, audio device control, and global effect settings.

system is the top-level entry point for libYSE. Construct nothing directly — access the singleton through the System() free function. The typical lifecycle is:

  1. System().init() once at startup.

  2. System().update() every frame.

  3. System().close() once at shutdown.

Between init and close, the engine manages an audio device, runs the DSP graph, and dispatches messages to playing sounds.

See also

YSE::System

See also

YSE::listener

Active device readouts

Live state of the currently open audio device.

Each returns 0 when no device is open (pre-init, after close(), or under initOffline()). Host applications use these to render device-info UI without having to re-enumerate YSE::device descriptors, and to survive device reconnects cleanly.

double getActiveSampleRate()#

Currently negotiated sample rate in Hz.

int getActiveBufferSize()#

Currently negotiated audio buffer size in frames.

This is the device’s frames-per-callback (PortAudio’s framesPerBuffer / Oboe’s framesPerBurst) — NOT the engine’s internal STANDARD_BUFFERSIZE, which may differ.

int getActiveOutputLatency()#

Currently negotiated output latency in samples.

Convert to milliseconds with (latency / sampleRate) * 1000.

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. Because all clocks derive from the single sample clock, polytemporal relationships stay exact and deterministic. Tempo is a playable, rampable control.

Threading. createClock / destroyClock / setTempo are control-thread operations; beatPosition / currentTempo may be read from the UI thread at frame rate (e.g. for a playhead). None of them run on or block the audio callback.

bool createClock(const std::string &name, float initialTempo = 120.f)#

Create a domain clock.

Parameters:
  • name – Unique domain name. Empty names are rejected.

  • initialTempo – Starting tempo in BPM (default 120).

Returns:

true on success; false if the name is empty or a live clock already owns it (first registration wins).

void destroyClock(const std::string &name)#

Destroy a domain clock.

No-op for an unknown name.

The clock stops being visible to queries immediately; its beat/tempo advancement stops on the next audio callback.

bool clockExists(const std::string &name)#

Whether a live domain clock with name exists.

system &setTempo(const std::string &name, float bpm, float rampSeconds = 0.f)#

Ramp a domain clock’s tempo toward bpm over rampSeconds.

Instant when rampSeconds is 0; otherwise linear. Tempo is a played control signal — call it as often as you like. It is not clamped: 0 pauses the clock and a negative tempo runs it backwards. No-op for an unknown name.

double beatPosition(const std::string &name)#

Current beat position (running integral of tempo) of a clock, or 0 for an unknown name.

float currentTempo(const std::string &name)#

Current tempo in BPM of a clock, or 0 for an unknown name.

Public Functions

system()#
bool init()#

Initialise the engine and open the default audio device.

Note

Threading. This call runs on and defines the engine’s control thread. Call update() and close() from the same thread. The control-thread identity is captured here and never re-derived, so driving init() and update() from different threads silently loses the named-bus inline fast path — see the update() threading note (issue #290).

Returns:

true when a stream on the default output device is running, false if no device could be opened: the audio backend failed to start, there is no default output device, or the device refused to open or start the stream. The reason is logged. A failed call tears down everything it set up, so the engine is left closed; call init() again, or initOffline() to run without a device (issue #973).

bool initOffline()#

Initialise the engine without opening an audio device.

For benchmarks, automated tests, and headless tooling that need a fully-configured engine but no PortAudio stream — same channel tree, same DSP graph, no audio thread. Once initialised this way, drive the engine via System().renderOffline(blocks) rather than the audio callback.

Returns:

true on success.

void renderOffline(int blocks)#

Render N audio blocks synchronously on the calling thread.

Runs the same callback body the audio thread executes in production — manager update, channel-tree DSP, channel mixing, reverb — for blocks × STANDARD_BUFFERSIZE samples worth of output, which is discarded. Use only after initOffline(); driving this concurrently with a live audio thread would race the manager-update path.

resume() cannot put such a thread there behind your back (issue #719), but openDevice() deliberately can — after a successful openDevice() on an offline session, stop driving this.

void update()#

Pump engine state.

Call once per frame from the main thread. Drives message delivery, sound state transitions, virtualisation decisions, and listener velocity calculations.

Note

Threading. update() must be called from the same thread that called init() — the engine’s single control thread. That identity is frozen when the engine initialises (not re-derived per tick) and decides who may dispatch control-rate (T_GUI) named-bus publishes inline; a publish from any other thread is instead deferred to the next update() tick to keep the per-object message queues single-producer (issue #193). Driving init() and update() from different threads is unsupported: it stays functionally correct — every publish simply takes the deferred path and is dispatched on whichever thread runs update() — but permanently forfeits the inline fast path, because the control thread is captured at init() rather than followed from update() (issue #290).

void close()#

Shut down the engine and release the audio device.

void pause()#

Pause audio output.

The engine keeps running but the device is silent.

pause() closes the audio stream, so the audio tick stops draining the lock-free control-to-audio inboxes (per-object message queues and the managers’ toLoadInboxes). Interface setters and object creation called while paused keep enqueuing onto those queues; the messages are held and delivered in order on resume(), not dropped. This is intentional — the queues carry ordered discrete commands (play/stop, position, load-handoff pointers), so silently dropping them would corrupt state, unlike the latest-value-wins parameter queues (NamedBus, patcher) which do cap.

The consequence is unbounded memory growth if an app pushes a very large number of setter/creation calls while paused: lfQueue allocates doubling-size blocks on demand and never frees them, so the peak retained capacity persists for the queue’s lifetime even after the backlog drains. All allocation happens producer-side on the control thread, so this never violates audio-thread real-time discipline. Applications that drive a heavy control-rate workload should keep the engine running (leave the device open) rather than pausing, or simply avoid issuing large batches of setters while paused. See issue #289.

void resume()#

Resume audio output after pause().

Restarts the device this session already had; it is not a request for one. On a session started with initOffline() — and on a closed engine — there is no device to resume, so the call is a no-op with a debug log line rather than an open of whatever the platform default happens to be (issue #719). Use openDevice() to give an offline session a device deliberately.

After openDevice(), the device restarted is that device, looked up again by name and host API, with the channel count and buffer size it was opened with. When it cannot be reopened nothing is opened and a warning is logged; resume() never moves the session to another device (issue #1027). A session that never called openDevice() runs on the default device and reopens the current default.

int missedCallbacks()#

Consecutive update() ticks during which the audio device delivered no callback.

Reset to 0 by the first update() that sees a callback, so 0 means “audio is flowing right now” and any non-zero value means it is not.

Non-zero does not by itself mean something is wrong: a stream that has just been started (init(), resume(), openDevice()) is not delivering callbacks yet either — the backend’s start call returns before the device runs, typically for tens of milliseconds. Reading this in a loop until it returns 0 is the way to wait for a device to come up; a value that keeps climbing is a starved audio thread or a disconnected device. autoReconnect distinguishes the two itself and does not act on this counter.

reverb &getGlobalReverb()#

Access the global reverb.

Disabled by default. When enabled, it acts as the fallback reverb at any position not covered by a positioned reverb zone. Partially rolled-off reverb zones are mixed against the global reverb.

const std::vector<device> &getDevices()#

All audio output devices visible to the engine.

Note

Only available when libYSE is linked as a static library. When linked dynamically, use getNumDevices / getDevice instead to avoid leaking the standard-library std::vector across the ABI boundary.

unsigned int getNumDevices()#

Number of audio output devices available.

const device &getDevice(unsigned int nr)#

Audio device at index nr.

Throws:

std::out_of_range – if nr >= getNumDevices(). Note that an empty device list (offline or headless session) makes index 0 out of range.

bool refreshDevices()#

Enumerate the audio devices without opening a stream.

init() enumerates the devices as a side effect of opening the default one, and initOffline() enumerates none, so this is how a host lists them, or resolves a stored device, before opening anything (issue #1026). Valid with no session at all, on an initOffline() session, and on a running device session.

On desktop it starts the audio backend (PortAudio) if it is not running yet, without opening a stream. After that, openDevice() also works on an initOffline() session in a process that never called init(). With no stream open, the backend is restarted so the list includes devices plugged in or removed since it started. A stream that is open is never touched; in that case the list is re-read from the backend’s enumeration as it stands, which on PortAudio does not see hot-plugged devices until the device is closed.

The refresh replaces the list: device references and deviceSetup objects built from the old list are invalid afterwards, and a device’s index may now name a different device. Resolve by name and host (device::getName() / getTypeName()), then build the setup.

Control thread only, not concurrently with other system calls. On bare headless Linux runners, starting PortAudio can hang (the reason initOffline() does not start it), so headless tools should not call this.

Returns:

true when the backend was reached and the list is current (it may still be empty). false when the backend could not start: the list is then empty and the reason is logged.

bool openDevice(const deviceSetup &object, CHANNEL_TYPE conf = CT_AUTO)#

Open an audio device.

Allowed on a session started with initOffline(), and a successful call promotes it to a session with a device: pause() / resume() work on it from then on, and renderOffline() no longer does — a live audio callback thread is now driving the same manager updates (issue #719). Unlike resume(), this call names the device it wants, so it is taken at its word.

On desktop the promotion needs the audio backend to be running: initOffline() does not start PortAudio, and openDevice() does not start it on demand. Call refreshDevices() first, which starts it and enumerates the devices to pick from (issue #1026). Without it, a process that has only ever run offline sessions has an empty device list and this returns false with a log line (issue #972).

The stream opens at the session’s sample rate, which is locked for the whole session and need not be the device’s default rate (an offline session runs at the requested rate, or 48 kHz). A device that refuses that rate is reported with a log line and nothing opens; choose the rate with requestSampleRate() before init() / initOffline().

Parameters:
  • object – Device + host + sample-rate configuration.

  • conf – Speaker layout. CT_AUTO picks stereo when possible.

Returns:

true when the backend opened a stream for object. false when it refused the setup — no output device, an output ID no host API resolves, a stream open error (a refused session rate included), or a backend that was never started — in which case the reason is logged, the running stream and the mixer layout are left alone, and the session is not promoted (issue #900).

system &setChannelConfiguration(CHANNEL_TYPE conf, int outputs)#

Set the speaker layout without opening an audio device.

openDevice() derives the layout from the device it actually opened, which leaves a session with no device — initOffline(), headless CI, renderOffline() benchmarks — stuck on the stereo layout initOffline() / init() install. This is the entry point for those: it configures the mixer directly and claims nothing about a device (issue #668).

Call it after init() / initOffline() — initialisation installs the default stereo layout itself, so an earlier call is overwritten. The new layout is picked up by the next audio callback (or the next renderOffline() block), which resizes the master to match.

On a session that does have a device open, this configures the mixer for a channel count the running stream may not have — that is the caller’s business here, and the reason openDevice() keeps its own device-driven path rather than routing through this.

Parameters:
  • conf – Speaker layout. CT_AUTO derives one from outputs; CT_CUSTOM allocates outputs speakers and leaves their positions to be set afterwards.

  • outputs – Number of output channels. Must be at least 1 — a zero-output layout silences the engine on the next callback, so it is refused with a log line.

void closeCurrentDevice()#

Close the currently open audio device.

const std::string &getDefaultDevice()#

Name of the platform default audio device.

const std::string &getDefaultHost()#

Name of the platform default audio host (e.g.

WASAPI, ALSA).

const std::string &getActiveDevice()#

Name of the device whose output stream is open right now, empty when none is.

Together with getActiveHost() this tells a host which device it is actually playing on — after autoReconnect fell back to the default device, say (issue #1027). Compare against device::getName() / getTypeName(). Control thread only.

const std::string &getActiveHost()#

Host API (e.g.

WASAPI, ALSA) of the device whose output stream is open right now, empty when none is. Control thread only.

system &occlusionCallback(float (*func)(const YSE::Pos&, const YSE::Pos&))#

Install a sound-occlusion callback.

The engine calls the function for every occlusion-enabled sound to compute how much it should be attenuated by world geometry. The returned factor is applied as a gain duck (finalGain *= 1 - occlusion). Typical implementations issue a raycast through the game physics engine. Pass nullptr to disable.

See also

occlusionFunc

Note

Threading. The callback runs on the thread that calls System().update() (the application/control thread), once per occlusion-enabled sound per update tick — never on the audio callback thread. The result is delivered to the audio thread over the lock-free sound message queue. This means a raycast that takes locks or allocates cannot stall the audio callback (issue #209), but it also means the callback must not block update() for long.

occlusionFunc occlusionCallback()#

Current occlusion callback, or nullptr if none installed.

system &underWaterFX(const channel &target)#

Route a channel through the built-in under-water effect.

Since issue #327 the underwater treatment is an ordinary insert module (DSP::MODULES::underWater); this call places the engine’s default instance at the head of target's insert chain through the normal channel::setDSP message path. It therefore occupies the channel’s insert slot: a later setDSP on the same channel replaces it, and vice versa. Only one channel carries the stock effect at a time — calling this again with a different channel moves it. To combine the effect with other inserts, or to drive it from your own control logic, instantiate your own DSP::MODULES::underWater instead.

system &setUnderWaterDepth(float value)#

Set the listener’s depth below the water surface.

The default spatial driver of the underwater module: evaluate it at control rate on your update thread and the value is delivered to the audio thread as an ordinary wait-free parameter write.

Parameters:

value – Depth below the surface in world distance units. Zero or less disables the effect entirely; the low-passed, position-neutral treatment fades in above 1 and is fully position-neutral from 5 down. Any positive depth also enables the built-in underwater reverb zone at the listener’s position.

system &maxSounds(int value)#

Set the maximum number of concurrently audible sounds.

When this limit is exceeded, the engine virtualises the least significant sounds (typically furthest from the listener) instead of rendering them, freeing CPU for the audible ones.

int maxSounds()#

Current maxSounds limit.

system &AudioTest(bool on)#

Enable or disable the built-in audio test signal.

Outputs a steady tone through the audio device for verifying the output chain.

system &autoReconnect(bool on, int delay)#

Configure automatic device reconnection.

The watchdog runs on update(): when the device has delivered no audio callback for delay milliseconds it closes and re-opens the output stream on the device the session had, the same way resume() does. Same as the four-argument overload with fallbackToDefault false and no attempt limit.

Parameters:
  • on – When true, the engine attempts to re-open the audio device after a disconnection (e.g. headphones unplugged).

  • delay – Milliseconds of silence before a reconnection attempt, and the interval between further attempts while the device stays unavailable. Negative values are clamped to 0.

system &autoReconnect(bool on, int delay, bool fallbackToDefault, int maxAttempts = 0)#

Configure automatic device reconnection, with a fallback policy and an attempt limit.

The watchdog runs on update(): when the device has delivered no audio callback for delay milliseconds it closes the output stream and opens it again on the device this session had (issue #1027). That is the device of the last successful openDevice(), looked up again by name and host API (indices shift when devices are re-enumerated), with the output channels and buffer size it was opened with; a session that never called openDevice() runs on the default output device, and reopens whatever the default device is at that moment.

When the session’s device cannot be reopened, nothing is opened and the watchdog tries again delay milliseconds later — unless fallbackToDefault is set, in which case the default output device is opened instead, and the speaker layout is narrowed if the default device has fewer outputs than the layout. The session does not move back by itself when its own device returns.

On PortAudio the lookup uses the backend’s device enumeration as it stands. A device that was unplugged and plugged back in may only be found after refreshDevices(), which a host can call while the watchdog has no stream open (reconnectState() reads RS_RECONNECTING).

reconnectState(), reconnectCount(), getActiveDevice() and getActiveHost() tell the host what happened.

A stream that has just been started is not stalled — the backend’s start call returns before the device delivers its first callback — so a stream that has yet to deliver is given a start-up grace period of half a second regardless of delay. Without it, a small delay would tear down a perfectly healthy device that was still coming up, and the replacement stream would be torn down for the same reason (issue #681).

On a session with no device — initOffline(), headless CI — the watchdog reaches the device through resume() like any other caller, and resume() refuses there, so enabling it costs a pair of no-ops per interval and can never graft a device onto such a session (issue #719).

Calling this resets reconnectState() to RS_NONE and re-arms a watchdog that gave up.

Note

Up to and including v2.4.0 the delay was compared against a count of update() calls rather than milliseconds, so the same number meant a different wait on every host depending on how often it called update(). It is now the documented millisecond value: existing callers passing a tick count get a shorter wall clock wait than before on a host polling slower than 1 kHz.

Parameters:
  • on – When true, the engine attempts to re-open the audio device after a disconnection (e.g. headphones unplugged).

  • delay – Milliseconds of silence before a reconnection attempt, and the interval between further attempts while the device stays unavailable. Negative values are clamped to 0.

  • fallbackToDefault – Open the default output device when the session’s own device cannot be reopened. Off by default: an audio application usually wants to know rather than be moved to the built-in output.

  • maxAttempts – Consecutive failed attempts after which the watchdog stops trying and reconnectState() reads RS_FAILED, so a machine with no audio hardware does not retry forever. 0 or less means no limit.

RECONNECT_STATE reconnectState()#

What the reconnection watchdog last did (issue #1027).

RS_NONE until the watchdog acts; it goes back to RS_NONE on init(), a successful openDevice() and every autoReconnect() call. Safe to read from any thread.

unsigned int reconnectCount()#

Number of reconnection attempts the watchdog has made this session, successful or not.

Lets a host notice a reconnect it did not see happen. Reset by init(). Safe to read from any thread.

float cpuLoad()#

Audio callback wall-clock load as a fraction of the buffer period.

Measured by YSE: timestamps taken at the entry/exit of each backend callback (PortAudio’s paCallback or Oboe’s onAudioReady) and divided by the buffer’s audio time. EMA-smoothed with a ~1 s time constant. Returns 0 when no device is open.

This is a dropout-risk indicator: 1.0 means the callback is taking as long as the buffer it produces, i.e. the next buffer will arrive late.

Distinct from the cost of update() on the main thread. Timed ourselves (rather than reading Pa_GetStreamCpuLoad) so the Oboe / Android backend can report a comparable number — that API doesn’t exist there.

system &requestSampleRate(unsigned int rate)#

Request the audio sample rate for the next engine session.

The sample rate is an application setting, fixed per session: call this before init() (or initOffline()) and the engine asks the audio backend to open the device at rate Hz — threaded through to Pa_OpenStream on desktop and the Oboe stream builder on Android. The device may refuse or negotiate a different rate; the negotiated result stays authoritative and is reported by getSampleRate(). A refused request is a log line plus the negotiated fallback, never an error. Offline / headless sessions have no device to negotiate with and run exactly at the requested rate.

Pass 0 to clear the request; the backend then opens at the device default (the engine’s pre-negotiation initial value is 48000).

Changing the rate of a running session means close() followed by init() — calling this mid-session only stores the request for the next session. The request survives close(), so a host can set it once for repeated init/close cycles. (issue #646)

unsigned int requestSampleRate()#

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

system &renderThreads(int count)#

Set how many worker threads help the audio thread render.

The mix is rendered as a task graph that the audio callback thread works through together with count render workers (issue #861).

  • -1 (default, and any negative value): auto — the number of physical cores minus one (the audio thread renders too, and the host keeps a core), capped at 8. Only cores the process may run on count, and on a hybrid CPU the workers are placed on performance cores first (issue #862).

  • 0: serial — the audio thread renders everything, no workers are started. Use on constrained hardware.

  • n > 0: exactly n workers (at most 64).

Whatever the count, nobody is woken for a block estimated to cost less than waking a worker: in real-time rendering the audio thread renders such a block alone, so small scenes do not pay for idle workers. The setting decides only which thread renders each part of the mix, never how it is summed.

Call before init() / initOffline(). On an offline session (or with no session) the change applies at once; during a device session it is stored and applied by the next init(), since the live callback may be rendering. The setting survives close().

int renderThreads()#

The render thread setting last requested: -1 for auto.

int activeRenderThreads()#

The number of render workers currently running (auto resolved; a request deferred to the next session is not reflected yet).

double getSampleRate()#

Engine session sample rate in Hz.

The rate the engine locked to when init() / initOffline() ran. Stays constant across the entire session, including pause / resume cycles where getActiveSampleRate() transiently drops to 0. Returns 0 before the lock is established (pre-init).

Use this when scheduling sample-count-driven work that must outlive a pause; use getActiveSampleRate() for live device-state UI.

void sleep(unsigned int ms)#

Sleep the calling thread for ms milliseconds.

Convenience for console applications that don’t otherwise yield between calls to update().

inline std::string Version() const#

libYSE version string.

Private Members

std::atomic<occlusionFunc> occlusionPtr#
int currentlyMissedCallbacks = 0#
bool doAutoReconnect = false#
int reconnectDelay = 0#
bool reconnectFallback = false#
int reconnectMaxAttempts = 0#
int reconnectFailures = 0#
std::atomic<int> reconnectStateValue = {0}#
std::atomic<unsigned int> reconnectAttempts = {0}#
unsigned int watchdogStreamStarts = 0#
unsigned long long watchdogSinceMs = 0#
bool watchdogAwaitingFirstCallback = false#

Listener#

namespace YSE

Public API of libYSE — sound playback, mixing, and 3D positional audio.

Entry points: YSE::System (lifecycle and audio device), YSE::Listener (3D origin), YSE::sound (a playable source), YSE::channel (mixing tree), YSE::reverb (positioned reverb zone), YSE::patcher (modular DSP graph), YSE::player (note sequencer). Sub-namespaces group domain-specific types: YSE::DSP for signal processing, YSE::MIDI for MIDI I/O, YSE::MUSIC for note / chord / motif primitives.

Note

Apart from their constructors, the oscillator and vcf classes must only be invoked from inside a DSP callback / process body.

Functions

listener &Listener()#

Access the singleton listener object.

< This macro is added to all public class declarations.

class listener#

Singleton representing the listener’s position and orientation in the virtual scene.

The Listener defines the reference point used by the engine to pan sounds across the available speakers, attenuate them by distance, and compute doppler shifts. Update its position every frame (typically alongside System().update()) so velocity and doppler stay coherent.

Access through the free function Listener().

See also

YSE::Listener

See also

YSE::Pos

Public Functions

Pos pos()#

Current listener position in world coordinates.

Pos vel()#

Current listener velocity in units per second.

Derived from successive calls to pos(const Pos&) — it cannot be set directly. Used internally for doppler calculations.

Pos forward()#

Forward-facing unit vector of the listener.

Pos upward()#

Upward unit vector of the listener.

listener &pos(const Pos &pos)#

Set the listener position.

Call once per frame to keep velocity-based effects (doppler, motion panning) accurate. Setting the position less frequently is fine for static scenes but will degrade the velocity estimate.

listener &orient(const Pos &forward, const Pos &up = Pos(0, 1, 0))#

Set the listener orientation.

Parameters:
  • forward – The direction the listener faces.

  • up – The upward axis. Defaults to (0, 1, 0), i.e. rotation confined to a horizontal plane.

Logging#

By default the engine writes its log to a file in the working directory. YSE::Log().setHandler() sends the lines to your own logHandler instead. Engine threads call the handler one line at a time, and Threading model lists the rules a handler must follow.

namespace YSE

Public API of libYSE — sound playback, mixing, and 3D positional audio.

Entry points: YSE::System (lifecycle and audio device), YSE::Listener (3D origin), YSE::sound (a playable source), YSE::channel (mixing tree), YSE::reverb (positioned reverb zone), YSE::patcher (modular DSP graph), YSE::player (note sequencer). Sub-namespaces group domain-specific types: YSE::DSP for signal processing, YSE::MIDI for MIDI I/O, YSE::MUSIC for note / chord / motif primitives.

Note

Apart from their constructors, the oscillator and vcf classes must only be invoked from inside a DSP callback / process body.

Functions

log &Log()#

Access the singleton logging object.

< This macro is added to all public class declarations.

class log#

Singleton logging facility.

By default the engine writes messages to a text file in the working directory. Use setLogfile to redirect that file, or setHandler to bypass the file entirely and feed log lines into your own sink.

See also

YSE::Log

Note

Logging is only active between System().init() and System().close().

Public Functions

log &sendMessage(const char *msg)#

Send an application-level message to the YSE log.

The message is tagged app message and emitted at error log level so it survives filters set above EL_DEBUG.

log &setLevel(ERROR_LEVEL value)#

Set the active log level.

Defaults to EL_DEBUG in debug builds and EL_ERROR in release builds. Messages above the chosen level are dropped.

ERROR_LEVEL getLevel()#

Current log level.

log &setHandler(logHandler *handler)#

Install a custom log handler.

Replaces the default file-based sink. Pass nullptr to restore the default behaviour.

log &setLogfile(const char *path)#

Change the path of the default log file.

Defaults to YSElog.txt in the process working directory. Has no effect after a custom handler has been installed via setHandler.

const char *getLogfile()#

Current log file path.

class logHandler#

Base class for custom log sinks.

Subclass and override AddMessage to route log output somewhere other than the default log file — for example an in-game console or a third-party telemetry system. Register the instance with log::setHandler.

Several engine threads log, but the engine delivers one line at a time: AddMessage is never entered from two threads at once, so a handler may keep ordinary unsynchronised state (issue #820). The message may arrive on any of those threads, and the engine holds its sink lock for the duration of the call — so a handler must not log back into YSE::Log(), and should hand anything slow to a thread of its own rather than block here.

Public Functions

inline virtual void AddMessage(const std::string&)#

Called by the engine for every log message.

Default implementation discards it.

Called with the engine’s log sink serialised: never concurrently with itself, and never re-entrantly from inside this call.

inline virtual ~logHandler()#

I/O#

namespace YSE

Public API of libYSE — sound playback, mixing, and 3D positional audio.

Entry points: YSE::System (lifecycle and audio device), YSE::Listener (3D origin), YSE::sound (a playable source), YSE::channel (mixing tree), YSE::reverb (positioned reverb zone), YSE::patcher (modular DSP graph), YSE::player (note sequencer). Sub-namespaces group domain-specific types: YSE::DSP for signal processing, YSE::MIDI for MIDI I/O, YSE::MUSIC for note / chord / motif primitives.

Note

Apart from their constructors, the oscillator and vcf classes must only be invoked from inside a DSP callback / process body.

Functions

io &IO()#

Access the singleton custom-IO object.

< This macro is added to all public class declarations.

class io#

Custom file-system callbacks for asset loading.

Game engines and packed-asset workflows often need libYSE to read sound files through their own VFS instead of the host operating system. The io singleton lets you install C-style callbacks for each step of the file lifecycle (open / read / seek / close / exists / length / getPosition). Call setActive(true) to switch the engine over.

Access through the free function IO() — do not instantiate.

See also

YSE::IO

See also

YSE::BufferIO For an alternative that feeds sounds from in-memory buffers.

Public Functions

io()#
io &open(bool (*funcPtr)(const char *filename, long long *filesize, void **fileHandle))#

Install the open callback.

The callback opens the named file, writes its size into filesize, stores an implementation-defined handle in fileHandle, and returns true on success.

io &close(void (*funcPtr)(void *fileHandle))#

Install the close callback.

io &read(long long (*funcPtr)(void *destBuffer, long long maxBytesToRead, void *fileHandle))#

Install the read callback.

Reads up to maxBytesToRead bytes into destBuffer. Returns the number of bytes actually read, or 0 at end-of-file.

io &getPosition(long long (*funcPtr)(void *fileHandle))#

Install the get-position callback.

Returns the current read offset in bytes.

io &fileExists(bool (*funcPtr)(const char *filename))#

Install the file-exists callback.

io &length(long long (*funcPtr)(void *fileHandle))#

Install the length callback.

Returns the total size of the open file in bytes.

io &seek(long long (*funcPtr)(long long offset, int whence, void *fileHandle))#

Install the seek callback.

whence matches the FILEPOINT enum (FP_START, FP_CURRENT, FP_END). Returns the new absolute position.

io &setActive(bool value)#

Enable or disable the custom IO layer.

When inactive, libYSE falls back to its default platform file I/O.

Bool getActive()#

Whether the custom IO callbacks are currently active.

Private Members

aBool active#
namespace YSE

Public API of libYSE — sound playback, mixing, and 3D positional audio.

Entry points: YSE::System (lifecycle and audio device), YSE::Listener (3D origin), YSE::sound (a playable source), YSE::channel (mixing tree), YSE::reverb (positioned reverb zone), YSE::patcher (modular DSP graph), YSE::player (note sequencer). Sub-namespaces group domain-specific types: YSE::DSP for signal processing, YSE::MIDI for MIDI I/O, YSE::MUSIC for note / chord / motif primitives.

Note

Apart from their constructors, the oscillator and vcf classes must only be invoked from inside a DSP callback / process body.

class BufferIO#

Feed sound files into the engine from in-memory byte buffers.

BufferIO is the simpler alternative to the callback-based YSE::io interface: register byte buffers under string IDs, then load sounds by passing those IDs as if they were file names. Typical use is bundling audio assets inside a game-engine resource pack or inside an Android APK where the regular file system is not accessible.

Construct one instance, call SetActive(true), add your buffers, then create sounds normally.

See also

YSE::io For full callback-based VFS integration.

Public Functions

BufferIO(bool storeCopy = false)#

Construct a BufferIO layer.

Parameters:

storeCopy – When true, AddBuffer copies the supplied bytes so the caller can free its buffer immediately. When false (default), the caller owns the memory and must keep it alive for as long as the buffer is registered.

void SetActive(bool value)#

Enable or disable this BufferIO layer.

bool GetActive()#

Whether this BufferIO layer is currently active.

bool BufferNameExists(const char *ID)#

Whether a buffer is registered under the given ID.

bool BufferExists(char *buffer)#

Whether the given byte buffer is currently registered.

bool AddBuffer(const char *ID, char *buffer, int length)#

Register a byte buffer under an ID.

Sounds can then be created by passing ID where a file name would normally go. length is the size of the buffer in bytes.

Returns:

true on success, false if the ID is already in use.

bool RemoveBufferByName(const char *ID)#

Unregister a buffer by its ID.

bool RemoveBuffer(char *buffer)#

Unregister a buffer by its address.

~BufferIO()#

Private Members

bool active#
bool storeCopy#

Reverb#

namespace YSE

Public API of libYSE — sound playback, mixing, and 3D positional audio.

Entry points: YSE::System (lifecycle and audio device), YSE::Listener (3D origin), YSE::sound (a playable source), YSE::channel (mixing tree), YSE::reverb (positioned reverb zone), YSE::patcher (modular DSP graph), YSE::player (note sequencer). Sub-namespaces group domain-specific types: YSE::DSP for signal processing, YSE::MIDI for MIDI I/O, YSE::MUSIC for note / chord / motif primitives.

Note

Apart from their constructors, the oscillator and vcf classes must only be invoked from inside a DSP callback / process body.

namespace REVERB#

Enums

enum MESSAGE#

Values:

enumerator POSITION#
enumerator SIZE#
enumerator ROLLOFF#
enumerator ACTIVE#
enumerator ROOMSIZE#
enumerator DAMP#
enumerator DRY_WET#
enumerator MODULATION#
enumerator REFLECTION#
namespace YSE

Public API of libYSE — sound playback, mixing, and 3D positional audio.

Entry points: YSE::System (lifecycle and audio device), YSE::Listener (3D origin), YSE::sound (a playable source), YSE::channel (mixing tree), YSE::reverb (positioned reverb zone), YSE::patcher (modular DSP graph), YSE::player (note sequencer). Sub-namespaces group domain-specific types: YSE::DSP for signal processing, YSE::MIDI for MIDI I/O, YSE::MUSIC for note / chord / motif primitives.

Note

Apart from their constructors, the oscillator and vcf classes must only be invoked from inside a DSP callback / process body.

class reverb#

A positioned reverb zone.

Each reverb object holds a set of parameters and a position in the scene. At the end of every DSP frame the engine looks at every reverb whose rolloff radius overlaps the listener and blends their parameters by proximity into the single shared reverb processor. The effect: you can drop multiple reverb zones around the world (cave, hall, bathroom) and the listener smoothly transitions between them as they move.

A “global” reverb is also available through System().getGlobalReverb(); it is mixed in as the fallback wherever no positioned reverb reaches.

See also

YSE::System

Public Functions

reverb(bool global = false)#

Construct a reverb zone.

Parameters:

global – Reserved for the engine — leave as false in user code. true is used internally by System().getGlobalReverb().

~reverb()#
reverb &operator=(const reverb&) = delete#

Copy-assignment is deleted.

A reverb owns a raw pimpl that the engine tracks by interface identity. Copy-assigning would alias two interfaces onto a single implementation and turn that impl’s single-producer/single-consumer message queue into a dual-producer queue on the audio thread (issue #192). Copying a reverb is never valid — forbid it.

void create()#

Initialise the reverb.

Must be called after System().init() and before any other method on this object.

bool isValid()#

Whether this reverb has a live implementation.

reverb &setPosition(const Pos &value)#

Set the position of the reverb zone in the scene.

Pos getPosition()#

Current zone position.

reverb &setSize(float value)#

Radius within which the reverb is at full strength.

Inside this radius the zone is applied fully; beyond it, the strength fades over the rolloff distance (see setRollOff).

float getSize()#

Current full-strength radius.

reverb &setRollOff(float value)#

Distance over which the reverb fades out.

Measured from the edge of the full-strength radius. Outside size + rollOff from the center, this zone contributes nothing.

float getRollOff()#

Current rolloff distance.

reverb &setActive(bool value)#

Enable or disable this reverb zone.

bool getActive()#

Whether this reverb zone is currently active.

reverb &setRoomSize(float value)#

Set the simulated room size.

Larger values give longer tails.

float getRoomSize()#

Current room size.

reverb &setDamping(float value)#

Set the high-frequency damping.

Higher damping makes the reverb tail darken faster, simulating soft materials.

float getDamping()#

Current damping value.

reverb &setDryWetBalance(float dry, float wet)#

Set the dry/wet balance.

Note

dry + wet should usually be 1.0. Sums above 1.0 can clip.

Parameters:
  • dry – How much of the source signal passes through unprocessed.

  • wet – How much of the reverberated signal is mixed in.

float getWet()#

Current wet level.

float getDry()#

Current dry level.

reverb &setModulation(float frequency, float width)#

Modulate the reverb tail.

Adds a slow LFO to the reverb output to break up metallic resonances.

Parameters:
  • frequency – Modulation rate in Hz.

  • width – Modulation depth.

float getModulationFrequency()#

Current modulation frequency.

float getModulationWidth()#

Current modulation width.

reverb &setReflection(int reflection, int time, float gain)#

Configure one of the four early reflections.

Layered on top of the diffuse reverb tail to give the perception of nearby reflective surfaces.

Parameters:
  • reflection – Reflection index in [0, 3].

  • time – Delay time of this reflection.

  • gain – Gain of this reflection.

int getReflectionTime(int reflection)#

Delay time of the given reflection.

reflection is in [0, 3].

float getReflectionGain(int reflection)#

Gain of the given reflection.

reflection is in [0, 3].

reverb &setPreset(REVERB_PRESET value)#

Apply a named preset (cave, hall, bathroom, …).

Private Members

REVERB::implementationObject *pimpl#
Bool connectedToManager#
Bool active#
Flt roomsize#
Flt damp#
Flt wet#
Flt dry#
Flt modFrequency#
Flt modWidth#
Int earlyPtr[4]#
Flt earlyGain[4]#
Pos position#
Flt size#
Flt rolloff#
REVERB_PRESET preset#
Bool global#

The parameter sets behind each REVERB_PRESET (see Types, enums and constants), shared with the morphing reverb in Mixing effects:

namespace YSE

Public API of libYSE — sound playback, mixing, and 3D positional audio.

Entry points: YSE::System (lifecycle and audio device), YSE::Listener (3D origin), YSE::sound (a playable source), YSE::channel (mixing tree), YSE::reverb (positioned reverb zone), YSE::patcher (modular DSP graph), YSE::player (note sequencer). Sub-namespaces group domain-specific types: YSE::DSP for signal processing, YSE::MIDI for MIDI I/O, YSE::MUSIC for note / chord / motif primitives.

Note

Apart from their constructors, the oscillator and vcf classes must only be invoked from inside a DSP callback / process body.

namespace REVERB

Functions

const presetValues &getPresetValues(REVERB_PRESET preset)#

Parameter set of a named preset.

The returned values are identical to what YSE::reverb::setPreset has always applied — that function now reads from this table. < This macro is added to all public class declarations.

presetValues morph(const presetValues &a, const presetValues &b, Flt t)#

The preset-interpolation core: linear blend of two parameter sets.

Every field is interpolated as a + (b - a) * t with t clamped to [0, 1], so t == 0 returns a and t == 1 returns b. Allocation-free and safe to call on the audio thread. < This macro is added to all public class declarations.