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-memoryDspBuffer.Sound.fromMultiBuffer()— a stereo or surround in-memoryDspMultiBuffer, one buffer per channel. With a non-zeroSound.spreadthe 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~dacoutput of aPatcher.Sound.fromSynth()— aSynthvoice 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.