simvx.core.audio_protocol¶
Backend-agnostic audio interface.
The audio system talks to three thin structural :class:Protocol types:
- class:
AudioPlaybackBackend: start / stop / pause / live-update sounds.
- class:
AudioStreamingBackend: chunk-fed PCM streaming (for procedural synthesis, AudioWorklet, etc.).
- class:
AudioBusBackend: bus-layout reconciliation and capability advertisement.
A fourth, one-member facet, :class:TreeBoundAudioBackend, is implemented only
by a backend that has no device clock of its own and paces itself by the engine.
A concrete backend implements whichever facets it can. The union
- class:
AudioBackendkeeps the legacy “everything in one Protocol” surface for callers that genuinely need all three (e.g.MiniaudioBackend,WebAudioBackend); narrower callers should depend on the smallest Protocol they actually use and reach the backend via- attr:
SceneTree.audio_playback/ :attr:audio_streaming/- attr:
audio_busesso the type checker enforces the facet boundary.
Capabilities are a :class:enum.StrEnum so typos are caught at import.
Existing string callers keep working: Capability.PLAY_BASIC compares
equal to "play.basic" and round-trips through frozenset literally.
Channels are opaque integer handles allocated by the backend. The backend owns the mapping from handle to native voice; the Python layer treats them as bookmarks.
Module Contents¶
Classes¶
Capabilities a backend can advertise via :meth: |
|
Backend facet for starting and controlling individual sounds. |
|
Backend facet for chunk-fed PCM streaming. |
|
Backend facet that lets a chunk-fed producer size its writes. |
|
Backend facet that lets a chunk-fed producer say it has finished. |
|
Backend facet for bus-layout reconciliation and capability discovery. |
|
Backend facet for a backend whose clock is a :class: |
|
Union Protocol: a backend that implements all three facets. |
Data¶
Capabilities every backend that participates in playback + streaming advertises. |
|
Mode kwarg for :meth: |
API¶
- simvx.core.audio_protocol.__all__¶
[‘AudioBackend’, ‘AudioBusBackend’, ‘AudioPlaybackBackend’, ‘AudioStreamDraining’, ‘AudioStreamPacin…
- class simvx.core.audio_protocol.Capability[source]¶
Bases:
enum.StrEnumCapabilities a backend can advertise via :meth:
AudioBusBackend.list_capabilities.Members are strings so
Capability.PLAY_BASIC in capsworks against either afrozenset[Capability]or a legacyfrozenset[str]. New capability flags must be declared here: string literals in backend code bypass the typo check and are banned.Initialization
Initialize self. See help(type(self)) for accurate signature.
- PLAY_BASIC¶
‘play.basic’
- PLAY_2D¶
‘play.2d’
- PLAY_3D¶
‘play.3d’
- SPATIAL_HRTF¶
‘spatial.hrtf’
- SPATIAL_DOPPLER¶
‘spatial.doppler’
- STREAMING¶
‘streaming’
- STREAMING_WAV¶
‘streaming.wav’
- STREAMING_OGG¶
‘streaming.ogg’
- STREAMING_MP3¶
‘streaming.mp3’
- STREAMING_FLAC¶
‘streaming.flac’
- EFFECT_GAIN¶
‘effect.gain’
- EFFECT_FILTER_BIQUAD¶
‘effect.filter_biquad’
- EFFECT_PARAMETRIC_EQ¶
‘effect.parametric_eq’
- EFFECT_DELAY¶
‘effect.delay’
- EFFECT_REVERB¶
‘effect.reverb’
- EFFECT_COMPRESSOR¶
‘effect.compressor’
- EFFECT_SOFTCLIP¶
‘effect.softclip’
- __new__(*values)¶
- __add__()¶
- __contains__()¶
- __delattr__()¶
- __dir__()¶
- __eq__()¶
- __format__()¶
- __ge__()¶
- __getattribute__()¶
- __getitem__()¶
- __getnewargs__()¶
- __getstate__()¶
- __gt__()¶
- __hash__()¶
- __iter__()¶
- __le__()¶
- __len__()¶
- __lt__()¶
- __mod__()¶
- __mul__()¶
- __ne__()¶
- __reduce__()¶
- __reduce_ex__()¶
- __repr__()¶
- __rmod__()¶
- __rmul__()¶
- __setattr__()¶
- __sizeof__()¶
- __str__()¶
- __subclasshook__()¶
- capitalize()¶
- casefold()¶
- center()¶
- count()¶
- encode()¶
- endswith()¶
- expandtabs()¶
- find()¶
- format()¶
- format_map()¶
- index()¶
- isalnum()¶
- isalpha()¶
- isascii()¶
- isdecimal()¶
- isdigit()¶
- isidentifier()¶
- islower()¶
- isnumeric()¶
- isprintable()¶
- isspace()¶
- istitle()¶
- isupper()¶
- join()¶
- ljust()¶
- lower()¶
- lstrip()¶
- partition()¶
- removeprefix()¶
- removesuffix()¶
- replace()¶
- rfind()¶
- rindex()¶
- rjust()¶
- rpartition()¶
- rsplit()¶
- rstrip()¶
- split()¶
- splitlines()¶
- startswith()¶
- strip()¶
- swapcase()¶
- title()¶
- translate()¶
- upper()¶
- zfill()¶
- __deepcopy__(memo)¶
- __copy__()¶
- name()¶
- value()¶
- simvx.core.audio_protocol.CAPABILITIES_CORE: frozenset[simvx.core.audio_protocol.Capability]¶
‘frozenset(…)’
Capabilities every backend that participates in playback + streaming advertises.
The Null backend deliberately omits the streaming members because it does not implement :class:
AudioStreamingBackend; seeNullAudioBackendfor its narrowed capability set.
- simvx.core.audio_protocol.PlayMode¶
None
Mode kwarg for :meth:
AudioPlaybackBackend.play_audio.The Node-side dispatcher (
_AudioPlaybackMixin._play_common) selects the mode from the player class; backends branch on it internally so the spatial / non-spatial code paths share one entry point.
- class simvx.core.audio_protocol.AudioPlaybackBackend[source]¶
Bases:
typing.ProtocolBackend facet for starting and controlling individual sounds.
The unified :meth:
play_audiocollapses the historicalplay_audio/play_audio_2d/play_audio_3dtrio into one entry point parameterised bymode. Backends branch onmodeinternally; spatial kwargs (position,max_distance) are ignored whenmode == "non_positional".Channel lifetime. A channel id is owned by whoever started it and is given back with :meth:
stop_audio, which must be safe to call on a channel that has already ended or already been released. A backend may not assume every channel is released: a caller can forget one, so a backend that holds per-channel resources reclaims the channels that have played out by itself rather than keeping them until shutdown.- Meth:
is_channel_activemust answerFalsefor both a finished channel and an unknown id, so the two are indistinguishable to the caller and reclaiming early is always safe.
- play_audio(stream: simvx.core.audio.AudioClip, *, mode: simvx.core.audio_protocol.PlayMode = 'non_positional', position: Any = None, volume_db: float = 0.0, pitch: float = 1.0, loop: bool = False, bus: str = 'Master', max_distance: float = 100.0, from_position: float = 0.0, pan: float = 0.0, gain_db: float = 0.0) int | None[source]¶
Play
stream. Returns the channel id orNoneon failure.modeselects the spatial path:"non_positional": background music / UI sounds.positionmax_distanceare ignored;busdefaults to"Master".
"2d": Node-side spatialization computespan+volume_dbfrom the listener; the backend just applies them before the first audio buffer is rendered.busdefaults to"SFX"."3d": same as 2D, plus :meth:update_audio_3dper frame for Doppler. The Node pre-computes initial pitch and forwards it.
from_positionseeks the playback cursor (in seconds) before the first sample is rendered.pan([-1, 1],0= centre) is applied beforesound.start()so spatial players’ first audio buffer is correctly panned.
- __slots__¶
()
- classmethod __init_subclass__(*args, **kwargs)¶
- classmethod __class_getitem__(item)¶
- class simvx.core.audio_protocol.AudioStreamingBackend[source]¶
Bases:
typing.ProtocolBackend facet for chunk-fed PCM streaming.
Procedural synthesis (
AudioSynth.attach_to), AudioWorklet feeds, and compressed-container streaming via native decoders all flow through these two methods. Backends that can’t stream (the Null backend, any no-device test stub) deliberately do NOT implement this Protocol: callers mustisinstance(backend, AudioStreamingBackend)and raise- Class:
AudioCapabilityError(or warn-once and skip) when the facet is absent.
- open_stream(*, volume_db: float = 0.0, bus: str = 'Master', buffer_seconds: float = 0.5, loop: bool = False, stream: simvx.core.audio.AudioClip | None = None) int[source]¶
Open a streaming channel for chunk-fed PCM playback.
If
streamis provided and itscontaineris a compressed format ("ogg"/"mp3"/"flac"), backends with a native streaming decoder open the file directly and ignore subsequent- Meth:
feed_audio_chunkcalls. For"wav"or synthetic"pcm"streams, the caller is expected to feed raw int16 stereo bytes via :meth:feed_audio_chunk. Backends that cannot decode the requested container raise :class:AudioCapabilityError.
looprestarts the channel at the beginning when it plays out. It applies only when the backend owns the decoder: a chunk-fed channel loops by having its producer rewind and feed the file again, which is the only place that knows where the start is.
- __slots__¶
()
- classmethod __init_subclass__(*args, **kwargs)¶
- classmethod __class_getitem__(item)¶
- class simvx.core.audio_protocol.AudioStreamPacing[source]¶
Bases:
typing.ProtocolBackend facet that lets a chunk-fed producer size its writes.
A producer with no idea how much room is left writes as fast as its caller ticks, which reads a file to its end in a fraction of the time the audio takes to play. These two members let it write exactly what fits, so the feed runs at the speed of the sound.
Kept out of :class:
AudioStreamingBackenddeliberately. That Protocol is runtime-checked to decide whether streaming is available at all, so widening it would take streaming away from every backend that has not implemented pacing yet. Producers isinstance-check this facet on its own and fall back to pacing themselves against the frame clock.- stream_format() tuple[int, int][source]¶
(sample_rate, channels)of the PCM this backend’s rings accept.
- frames_available(channel_id: int) int[source]¶
Frames
channel_idcan accept right now;0when it is full.Also
0for a channel whose audio the backend decodes itself: such a channel has no ring, so there is nothing for a producer to write into it.
- __slots__¶
()
- classmethod __init_subclass__(*args, **kwargs)¶
- classmethod __class_getitem__(item)¶
- class simvx.core.audio_protocol.AudioStreamDraining[source]¶
Bases:
typing.ProtocolBackend facet that lets a chunk-fed producer say it has finished.
- Class:
AudioStreamingBackendgives a producer no way to end a stream. The only signal available is :meth:AudioPlaybackBackend.stop_audio, which tears the channel down at once and throws away everything the producer has written but the device has not yet played: up to a whole buffer of the end of the sound. This facet is how the producer says “that was the last chunk” instead, and lets the backend keep the channel alive until what is already queued has been heard.
Kept out of :class:
AudioStreamingBackendfor the same reason as- Class:
AudioStreamPacing: that Protocol is runtime-checked to decide whether streaming is available at all, so a backend that has not implemented draining yet would lose streaming entirely. Producers isinstance-check this facet on its own and fall back to the immediate stop, which is what they did before it existed.
- end_of_stream(channel_id: int) None[source]¶
Declare that no further :meth:
feed_audio_chunkcall is coming.The channel keeps playing what it already holds.
- Meth:
AudioPlaybackBackend.is_channel_activeanswersFalsefor it once that has played out, which is the producer’s cue to stop it. A no-op for a channel the backend decodes itself, for an unknown id, and on a second call.
- __slots__¶
()
- classmethod __init_subclass__(*args, **kwargs)¶
- classmethod __class_getitem__(item)¶
- class simvx.core.audio_protocol.AudioBusBackend[source]¶
Bases:
typing.ProtocolBackend facet for bus-layout reconciliation and capability discovery.
- sync_bus_layout(layout: simvx.core.audio_bus.AudioBusLayout) None[source]¶
Reconcile the backend’s native bus / effect graph against
layout.Cheap and idempotent: backends keep their own
dict[bus_name, native_handle]and add/remove/update only what changed since the last call. Called automatically by the audio server when :class:AudioBusLayoutor any bus’seffectslist changes; manual call is safe.
- list_capabilities() frozenset[simvx.core.audio_protocol.Capability][source]¶
Capability flags this backend supports.
Effect modules and Nodes call this before attempting to materialise a native feature; absent capabilities raise
- Class:
AudioCapabilityError(strict) or warn-once (lenient).
- __slots__¶
()
- classmethod __init_subclass__(*args, **kwargs)¶
- classmethod __class_getitem__(item)¶
- class simvx.core.audio_protocol.TreeBoundAudioBackend[source]¶
Bases:
typing.ProtocolBackend facet for a backend whose clock is a :class:
SceneTree, not a device.A backend driven by real audio hardware knows how much time has passed because the device tells it. A backend with no device – the silent
- Class:
~simvx.core.audio_backend.NullAudioBackendis the only shipped one – has to be told, and several trees can be alive at once, so it must be told which tree’s ticks are its own.- Meth:
SceneTree.install_audio_backendisinstance-checks this facet and calls :meth:bind_treeon the backends that declare it. A backend that does not implement it is simply never bound; nothing else about installation changes.
- bind_tree(tree: simvx.core.scene_tree.SceneTree) None[source]¶
Adopt tree as the clock this backend measures its playback against.
Called once per install, with the installing tree. A re-install onto a different tree calls it again with the new one. The backend is expected to hold the tree weakly: a finished scene’s node graph must not be kept alive by the audio backend that outlived it.
- __slots__¶
()
- classmethod __init_subclass__(*args, **kwargs)¶
- classmethod __class_getitem__(item)¶
- class simvx.core.audio_protocol.AudioBackend[source]¶
Bases:
simvx.core.audio_protocol.AudioPlaybackBackend,simvx.core.audio_protocol.AudioStreamingBackend,simvx.core.audio_protocol.AudioBusBackend,typing.ProtocolUnion Protocol: a backend that implements all three facets.
MiniaudioBackend(and its legacy fallback) andWebAudioBackendsatisfy this.NullAudioBackenddoes NOT (it doesn’t implement- Class:
AudioStreamingBackend): callers that need streaming should isinstance-check :class:AudioStreamingBackenddirectly via- Attr:
SceneTree.audio_streaming.
- play_audio(stream: simvx.core.audio.AudioClip, *, mode: simvx.core.audio_protocol.PlayMode = 'non_positional', position: Any = None, volume_db: float = 0.0, pitch: float = 1.0, loop: bool = False, bus: str = 'Master', max_distance: float = 100.0, from_position: float = 0.0, pan: float = 0.0, gain_db: float = 0.0) int | None¶
- stop_audio(channel_id: int) None¶
- pause_audio(channel_id: int) None¶
- resume_audio(channel_id: int) None¶
- update_audio_2d(channel_id: int, volume_db: float, pan: float) None¶
- update_audio_3d(channel_id: int, volume_db: float, pan: float, pitch: float) None¶
- set_pitch(channel_id: int, pitch: float) None¶
- set_listener_position(x: float, y: float, z: float) None¶
- set_listener_velocity(x: float, y: float, z: float) None¶
- set_listener_direction(x: float, y: float, z: float) None¶
- set_listener_world_up(x: float, y: float, z: float) None¶
- get_playback_position(channel_id: int) float¶
- is_channel_active(channel_id: int) bool¶
- shutdown() None¶
- __slots__¶
()
- classmethod __init_subclass__(*args, **kwargs)¶
- classmethod __class_getitem__(item)¶
- open_stream(*, volume_db: float = 0.0, bus: str = 'Master', buffer_seconds: float = 0.5, loop: bool = False, stream: simvx.core.audio.AudioClip | None = None) int¶
- feed_audio_chunk(channel_id: int, chunk: bytes) None¶
- sync_bus_layout(layout: simvx.core.audio_bus.AudioBusLayout) None¶
- list_capabilities() frozenset[simvx.core.audio_protocol.Capability]¶