Sound#

Sound playback. Construct one Sound per voice in the scene via one of its loaders:

  • Sound.fromFile() — an audio file, in memory or streamed.

  • Sound.fromBuffer() — a mono in-memory DspBuffer.

  • Sound.fromMultiBuffer() — a stereo or surround in-memory DspMultiBuffer, one buffer per channel. With a non-zero Sound.spread the source channels fan out around the sound’s position; at the default spread of 0 they all pan to the same point.

  • Sound.fromPatcher() — the ~dac output of a Patcher.

  • Sound.fromSynth() — a Synth voice pool.

Every source must outlive the sound. The sound holds a Dart reference to its buffer, patcher or synth (and to the effect chain assigned to Sound.dsp, each effect holding the one linked after it), so garbage collection never frees it under a live sound. When a sound and its source become unreachable together, the source is still released only after the sound’s native destroy has run. Call the source’s dispose() only after disposing the sound.

Upstream: YSE::sound.

Occlusion#

A sound with Sound.occlusion enabled is ducked by geometry between it and the listener. The application supplies the amount through one process-wide callback, System.occlusion, which receives the sound and listener positions (already scaled by the distance factor) and returns 0 (clear line of sight) to 1 (fully blocked):

System.instance.occlusion = (src, lis) => world.raycast(src, lis);
sound.occlusion = true;

The engine calls it once per occlusion-enabled sound during every System.update(), synchronously on the calling isolate and never on the audio thread, so the callback may query a physics engine or allocate. Keep update() on the isolate that installed it. Set null to remove it; sounds then keep the last value they received. System.close() removes it as well. An exception thrown by the callback counts as 0 for that update and is reported to the zone the callback was installed in.

Sound#

class Sound implements Finalizable

A playable instance of an audio source.

Construct one sound per voice in the scene. The source can be a file on disk (wav / ogg / flac and other formats depending on the platform); buffer-backed and DSP-source variants ship in later milestones.

Constructors#

factory Sound.fromFile(String filename, {Channel? channel, bool loop = false, double volume = 1.0, bool streaming = false})

Construct a sound backed by an audio file.

[filename] is an absolute path or a path relative to the working directory. [channel] routes the sound through that mixer node; pass null to use the master mix. [streaming] streams the audio from disk instead of loading it into memory — use for large one-off assets, avoid for sounds played repeatedly.

Throws [YseException] if the file cannot be loaded.

factory Sound.fromPatcher(Patcher patcher, {Channel? channel, double volume = 1.0})

Construct a sound driven by a [Patcher] graph.

Lifetime contract: [patcher] must outlive this sound — the engine renders its ~dac output on every audio callback. The sound holds a Dart reference to it, so garbage collection never frees the patcher under a live sound; don’t .dispose() the patcher before disposing the sound (the engine keeps rendering the graph it already took over, but every control call — [Patcher.passBang], [Patcher.messages], edits — is gone with the handle).

Throws [YseException] if the sound cannot be created, or if [patcher] is already driving another sound.

factory Sound.fromBuffer(DspBuffer buffer, {Channel? channel, bool loop = false, double volume = 1.0})

Construct a sound backed by an in-memory [DspBuffer].

Lifetime contract: [buffer] must outlive this sound — the audio thread reads from it on every callback. The sound holds a Dart reference to it, so garbage collection never frees the buffer under a live sound; don’t .dispose() the buffer before disposing the sound.

factory Sound.fromMultiBuffer(DspMultiBuffer buffer, {Channel? channel, bool loop = false, double volume = 1.0})

Construct a sound backed by an in-memory [DspMultiBuffer] — a stereo or surround source with one buffer per channel.

The sound plays the length of the shortest channel. Set [spread] to fan the source channels out around the sound’s position (at 1, a stereo source straight ahead plays channel 0 left and channel 1 right); at the default spread of 0 every channel pans to the same point. [channel] routes it through that mixer node; pass null for the master mix.

Lifetime contract: [buffer] must outlive this sound — the audio thread reads from it on every callback. The sound holds a Dart reference to it, so garbage collection never frees it under a live sound; don’t .dispose() it before disposing the sound.

Throws [YseException] if the sound cannot be created or loaded (for example from a disposed [buffer]).

factory Sound.fromSynth(Synth synth, {Channel? channel, double volume = 1.0})

Construct a positioned sound that renders a [Synth]’s voice pool.

Mirrors the engine’s YSE::sound::create(synth&, channel*, volume): the sound supplies the single 3D position, [channel] routing and master play/stop intent for every voice. Build the synth’s voices (with its add*Voices methods) before constructing this sound.

Lifetime contract: [synth] must outlive this sound — the audio thread renders its voices on every callback. The sound holds a Dart reference to it, so garbage collection never frees the synth under a live sound; don’t .dispose() the synth before disposing the sound. Pass null for [channel] to route through the master mix.

Throws [YseException] if the sound cannot be created.

Properties#

bool get isValid

Whether this sound has a live native implementation.

bool get isReady

Whether the asynchronous file load has finished.

Calling [play] on a not-yet-ready sound is safe — the start is queued.

bool get isStreaming

Whether the sound is streamed from disk (vs loaded into memory).

bool get isPlaying

Whether the sound is currently playing.

bool get isPaused

Whether the sound is currently paused.

bool get isStopped

Whether the sound is currently stopped.

Pos get position
set position(Pos value)

Position of this sound in the virtual scene.

double get volume
set volume(double value)

Volume in the range [0.0, 1.0].

double get speed
set speed(double value)

Playback speed. 2.0 is one octave up, 0.5 is one octave down. Negative plays backwards (not supported for streaming sounds).

double get size
set size(double value)

Audible radius — beyond this distance the sound fades out.

double get spread
set spread(double value)

Channel spread for multichannel sounds (no-op for mono).

bool get looping
set looping(bool value)

Whether the sound loops continuously.

bool get relative
set relative(bool value)

Whether the sound is positioned relative to the listener.

bool get doppler
set doppler(bool value)

Whether doppler shift is enabled for this sound.

bool get pan2D
set pan2D(bool value)

Shorthand for relative + listener-origin position + no doppler.

bool get occlusion
set occlusion(bool value)

Whether occlusion is active for this sound.

An occlusion-enabled sound is ducked by the value the [System.occlusion] callback returns for it on every [System.update] (gain *= 1 - occlusion). With no callback installed the sound keeps the last value it received (0 initially).

double get time
set time(double samples)

Playhead position in samples.

int get length

Length of the source in samples.

set dsp(DspObject? dsp)

Attach a DSP effect chain to this sound.

Pass null to clear. The engine holds a borrowed reference to [dsp] for as long as the sound is live; [dsp] must outlive this sound and the engine’s slow-pool delete tick that follows its destruction. The sound holds a Dart reference to the assigned head, and each effect holds the one [DspObject.link]ed after it, so garbage collection never frees any part of the chain under a live sound (#80, #84).

String? get busName
set busName(String? name)

The name this sound answers to on the global bus, or null when it is anonymous (the default).

Once named foo, the engine subscribes the sound to:

  • sound.foo.volume — a BusFloat (or BusInt), as [volume];

  • sound.foo.speed — a BusFloat (or BusInt), as [speed];

  • sound.foo.position — a BusList of three floats [x, y, z], as [position].

so a Bus.publish (or a script’s yse.send, or a patcher .s) drives it with no host round-trip. Values that arrive before the sound’s load has succeeded are dropped.

Setting null or ‘’ clears the name and its subscriptions; renaming re-subscribes. Names are unique per sound — a duplicate is rejected and logged by the engine, with no error here (this getter still reports the requested name). Only effective while the engine is between init and close. There is no native getter: this returns the last value set through this wrapper, matching Synth.busName.

Methods#

void play()

Start playback.

void pause()

Pause playback. [play] resumes from the current position.

void stop()

Stop playback and rewind to the start of the source.

void toggle()

Cycle playing → paused → playing, or stopped → playing.

void restart()

Restart from the beginning regardless of current position.

void fadeTo(double target, {required Duration fade})

Fade to [target] over [fade] milliseconds.

void fadeAndStop(Duration time)

Fade out over [time], then stop.

void moveTo(Channel target)

Move this sound to a different channel.

void dispose()

Destroy the underlying native sound and detach the finalizer.

Idempotent. After [dispose] the sound is unusable.