simvx.core.audio_backend._null¶
The silent (no-op) audio backend.
NullAudioBackend is selected when no audio device is available (sandboxed
CI, headless containers). Calls return valid channel IDs and behave
consistently, but nothing is rendered to a device.
Module Contents¶
Classes¶
Silent backend implementing :class: |
API¶
- class simvx.core.audio_backend._null.NullAudioBackend(sample_rate: int = _DEFAULT_SAMPLE_RATE, nchannels: int = _DEFAULT_CHANNELS)[source]¶
Silent backend implementing :class:
AudioPlaybackBackend+ :class:AudioBusBackend.Selected automatically by :func:
make_backendwhen neither the native extension nor the legacyminiaudiopath can start (e.g. no audio device on the host, ALSA/Pulse not running, theminiaudioPython package not installed, or a sandboxed environment with no compiler).The engine, scene tree, and :class:
AudioPlayernodes all operate normally: calls return valid channel IDs and- Meth:
is_channel_activebehaves consistently, but nothing is rendered to a device.
A non-looping channel reports itself finished once the clip’s duration has elapsed, so
queue_free_on_endplayers reap on a machine with no audio just as they do on one with a sound card. The elapsed time is read from the tree this backend was installed on, not from a wall clock: a headless run that ticks faster than real time ends its sounds after the same number of ticks either way, a paused tree holds its sounds where they are, and a time-scaled one stretches them with everything else. Only that tree’s ticks count, so another tree built in the same process (an editor’s edited scene, which nothing ticks) neither ends these sounds nor stalls them. That is the one divergence from a real device, whose playback cursor is driven by the audio hardware and knows nothing of the tree; it buys a deterministic silent backend for CI and headless capture, which is what this backend exists for. Tests substitute_clockto drive it by hand.Clip length comes from the PCM a synthetic clip carries or, for a file-backed one, from the container’s own metadata: WAV, OGG, MP3 and FLAC alike, since reading a length needs a decoder but no device. A clip whose length cannot be read keeps its channel until an explicit
stop_audio: that covers a clip with no file behind it, an unrecognised or corrupt container, and the case whereminiaudioitself is missing rather than merely deviceless.A change of pitch mid-playback moves the end with it, because the resampler it stands for changes how long the rest of the clip takes.
NullBackend does NOT implement :class:
AudioStreamingBackend. Code paths that need streaming (procedural synth via- Class:
AudioSynth.attach_to, AudioWorklet feeds, etc.) must checkisinstance(backend, AudioStreamingBackend)and raise / warn-once when it’s absent. Advertised capabilities are narrowed to{Capability.PLAY_BASIC}so effect modules don’t try to instantiate native nodes on the null path.
Initialization
- bind_tree(tree: simvx.core.scene_tree.SceneTree) None[source]¶
Adopt tree’s clock; called by
SceneTree.install_audio_backend.This backend’s half of
- Class:
~simvx.core.audio_protocol.TreeBoundAudioBackend, which is what installing isinstance-checks. Channel ends are then measured against the ticks of the tree whose sounds they are, which is the only tree that can stop or reap them.
- play_audio(stream: simvx.core.audio.AudioClip, *, mode: str = '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]¶
- list_capabilities() frozenset[simvx.core.audio_protocol.Capability][source]¶
- sync_bus_layout(layout: simvx.core.audio_bus.AudioBusLayout) None[source]¶