Bus#
The engine’s global named bus is its message plane: a value published to an
address reaches everything listening there, whoever published it. The
Bus façade gives the host all three sides of it:
Bus.publish()— send aBusValueto one exact address.Bus.subscribe()— a broadcastStreamof every value published to one exact address.Bus.tap()— a broadcastStreamof every value published under an address prefix (byte-wise;''matches everything).
A host publish takes the same path as a live-coding script’s yse.send, so
host, scripts and patcher .s / .r objects all share one bus.
final synth = Synth()..addSineVoices(8)..busName = 'lead';
Bus.publish('synth.lead.note', const BusList([1, 60, 0.8]));
final sub = Bus.subscribe('phi.ctl.play').listen((frame) {
print('${frame.address}: ${frame.value}');
});
Address namespaces#
The engine listens on these namespaces. <name> is the bus name given to
the object; everything is matched exact-string and case-sensitively.
Address |
Listener |
Payload |
|---|---|---|
|
a |
|
|
same |
|
|
same |
|
|
same |
|
|
same |
|
|
same |
|
|
a |
any kind |
|
a |
|
|
same |
|
|
same |
|
|
a |
|
A sound drops values that arrive before its load has succeeded. Names are
unique per kind: naming a second synth, sound or channel with a name already
in use is rejected and logged by the engine, with no error to the host.
busName = null (or '') clears the name and its subscriptions.
Channel.name and Channel.busName are independent: name is the
log name passed to Channel.create and never puts the channel on the bus.
Any other address is free for host and script use; only subscribers and taps hear it. A publish nobody listens to is not an error.
Threading and delivery#
Call every Bus member from the isolate that called
System.init() / System.initOffline(). The engine fires
subscription and tap callbacks on that control thread — inline for a
control-thread publish, otherwise during System.update() — never on
the audio thread. So a host Bus.publish() dispatches synchronously:
when it returns, every engine-side listener has run and the matching frames
are queued on their streams (Dart listeners receive them in a microtask, no
update() needed). Publishes made on the script or audio thread arrive
during the next update().
The callback’s address, string and list payloads are engine-owned and only
valid during the call, so the bridge is a NativeCallable.isolateLocal that
copies them out before returning.
Never publish from an audio callback, or concurrently with
System.close().
Lifecycle#
All listeners of one address (or prefix) share a single native subscription,
created on first use and released when the last listener cancels.
System.close() releases every subscription and tap and closes their
streams; subscribe again after the next init.
Bus.publish() throws YseException with
YSE_ERR_NOT_INITIALIZED when the engine is down and
YSE_ERR_INVALID_ARGUMENT for an empty address. Bus.subscribe()
and Bus.tap() throw StateError when called before init.
LiveCoding.bus is a deprecated forwarder to Bus.tap().
Bus#
abstract class Bus
Host access to the engine’s global named bus: publish a value to an address, subscribe to one exact address, or tap every address under a prefix.
The bus is the engine’s message plane. A value published to an address reaches everything listening there, whoever published it — a host [publish], a live-coding script’s yse.send(…), or a patcher .s (gSend) object. The address namespaces the engine itself listens on:
Address |
Listener |
|---|---|
synth.<name>.<event> |
a synth named with Synth.busName |
patcher.<name>.<slot> |
a .r object in a named patcher |
sound.<name>.<prop> |
a named sound |
channel.<name>.<prop> |
a named channel |
Anything else is free for the host and scripts to use (phi.ctl.play, …): only [subscribe]rs and [tap]s hear it. All addresses match exact-string and case-sensitively, except a [tap], which is a byte-wise prefix match.
## Threading
Call every member from the isolate that called System.init() / System.initOffline() — the control thread, which drives System.update(). A [publish] from there dispatches synchronously: every engine-side listener, [subscribe]r and [tap] callback has run by the time it returns, and the matching frames are already queued on their streams (Dart listeners receive them in a microtask, without needing an update()). Publishes the engine makes from other threads (the script thread, the audio thread) are delivered during the next update().
Never publish from an audio callback, or concurrently with System.close().
## Lifecycle
[subscribe] and [tap] streams are bound to the engine session they were created in. System.close() releases every native subscription and closes every bus stream (listeners get onDone); subscribe again after the next init.
Static methods#
static void publish(String address, BusValue value)
Publish [value] to the exact [address].
The publish takes the same path as a script’s yse.send(…), so it reaches everything a script can — see the address table on [Bus]. On the control isolate it dispatches synchronously. A publish nobody listens to is not an error.
Throws [YseException] with status YSE_ERR_NOT_INITIALIZED when the engine is not running, or YSE_ERR_INVALID_ARGUMENT for an empty [address]. Throws [ArgumentError] for a [BusInt] outside the signed 32-bit range.
static Stream<BusFrame> subscribe(String address)
Broadcast stream of every value published to exactly [address].
Unlike [tap], this is an exact match: a subscription to patcher.lead.cutoff does not see patcher.lead.cutoff2. Each frame’s address is [address] itself.
All listeners of one [address] share a single native subscription, created on the first call and released when the last listener cancels (a later call creates a fresh one). See [Bus] for threading and lifecycle.
The engine fires the native callback on the control thread — inline for a control-thread publish, else during System.update() — never on the audio thread. Its address, string and list payloads are engine-owned and valid only for the duration of the call, so the bridge is a NativeCallable.isolateLocal that copies everything out before returning.
Throws [ArgumentError] for an empty [address], and [StateError] if the engine rejects the subscription (e.g. before System.init).
static Stream<BusFrame> tap(String prefix)
Broadcast stream of every value published to any address that starts with [prefix] (a plain byte-wise prefix match; the empty string matches every address). Each frame carries the full address it was published on.
A tap adds no audio-thread cost: the match happens in the engine’s control-thread dispatch. Sharing, threading, payload copying and lifecycle are exactly as for [subscribe].
Throws [StateError] if the engine rejects the tap (e.g. before System.init).
BusValue#
class BusValue
A value carried on the engine’s global named bus — what [Bus.publish] sends and what [Bus.subscribe] / [Bus.tap] deliver.
The concrete subtype encodes which of the engine’s five bus payload kinds it is — [BusBang], [BusInt], [BusFloat], [BusString], or [BusList]. Because the type is sealed, an exhaustive switch over a [BusValue] needs no default case; pattern-match each subtype to read its payload (value for the scalar kinds, values for [BusList]).
Constructors#
BusValue()
typedef BusFrame#
typedef BusFrame = ({String address, BusValue value});
One (address, value) frame delivered by [Bus.subscribe] or [Bus.tap]: the full bus [address] the value was published on, paired with its typed [value].
A named record — read the fields directly (frame.address, frame.value) or destructure them (final (:address, :value) = frame;).
BusBang#
class BusBang extends BusValue
A valueless trigger (YSE_BUS_BANG) — e.g. a patcher gSend bang outlet.
Constructors#
BusBang()
Const constructor — all bangs are equal and carry no payload.
Properties#
int get hashCode
Methods#
bool ==(Object other)
String toString()
BusInt#
class BusInt extends BusValue
A 32-bit integer bus value (YSE_BUS_INT).
Constructors#
BusInt(this.value)
Wrap an integer bus payload.
Properties#
final int value
The integer payload. Published values must fit a signed 32-bit int.
int get hashCode
Methods#
bool ==(Object other)
String toString()
BusFloat#
class BusFloat extends BusValue
A 32-bit float bus value (YSE_BUS_FLOAT).
Constructors#
BusFloat(this.value)
Wrap a float bus payload.
Properties#
final double value
The float payload, widened to a Dart [double] (narrowed to 32 bits on publish).
int get hashCode
Methods#
bool ==(Object other)
String toString()
BusString#
class BusString extends BusValue
A string bus value (YSE_BUS_STRING, NUL-terminated UTF-8 engine-side).
Constructors#
BusString(this.value)
Wrap a string bus payload.
Properties#
final String value
The string payload.
int get hashCode
Methods#
bool ==(Object other)
String toString()
BusList#
class BusList extends BusValue
A list of 32-bit floats (YSE_BUS_LIST) — e.g. the [channel, note, velocity] payload of synth.<name>.note.
Constructors#
BusList(this.values)
Wrap a float-list bus payload.
Properties#
final List<double> values
The float payload. May be empty.
int get hashCode
Methods#
bool ==(Object other)
String toString()