SimVX Audio System¶
Complete audio playback system with background music, UI sounds, and 3D spatial audio.
Features¶
Background Music & UI Sounds - Non-positional audio for music and interface feedback
2D Positional Audio - Stereo panning based on screen position
3D Spatial Audio - Distance attenuation, directional panning, and Doppler effect
Audio Buses - Volume control per category (master, music, sfx, ui)
Flexible Sources - Filesystem paths, package-shipped assets, synthetic tones
Serialization - Save/load audio players in scene files
Note
Audio playback requires an audio backend (e.g. SDL3). The Vulkan-only install does not include audio output — audio nodes will still serialize and process, but produce no sound without a backend that implements AudioBackend.
Quick Start¶
1. Background Music¶
from simvx.core import AudioStreamPlayer
music = AudioStreamPlayer(
stream="music/theme.ogg", # filesystem path
volume_db=-5.0,
loop=True,
autoplay=True,
bus="music"
)
root.add_child(music)
2. 2D Sound Effects¶
from simvx.core import AudioStreamPlayer2D, Resource, Vec2
explosion = AudioStreamPlayer2D(
stream=Resource("game.assets", "explosion.wav"), # package-shipped asset
position=Vec2(200, 300),
max_distance=400.0,
bus="sfx"
)
root.add_child(explosion)
explosion.play()
3. 3D Spatial Audio¶
from simvx.core import AudioStreamPlayer3D, Vec3
engine = AudioStreamPlayer3D(
stream="sfx/engine.ogg",
position=Vec3(10, 0, 5),
max_distance=100.0,
loop=True,
doppler_scale=0.5,
bus="sfx"
)
root.add_child(engine)
engine.play()
4. Synthetic Tones (no file)¶
from simvx.core import AudioStream, AudioStreamPlayer
beep = AudioStreamPlayer(stream=AudioStream.tone(440)) # 440 Hz, 1 s
root.add_child(beep)
beep.play()
Audio Player Nodes¶
AudioStreamPlayer¶
Non-positional audio for background music and UI sounds.
Properties:
volume_db(float): Volume in decibels (-80 to 24 dB). 0 = full volume.pitch_scale(float): Playback speed multiplier (0.5 to 2.0).bus(str): Audio bus name (“master”, “music”, “sfx”, “ui”).autoplay(bool): Start playing when added to scene tree.loop(bool): Loop playback when finished.
Methods:
play(from_position=0.0)- Start or resume playbackstop()- Stop playbackpause()- Pause playback (resume withplay())is_playing()- Check if currently playing
Example:
music = AudioStreamPlayer(
stream="music/menu.ogg",
volume_db=-10.0,
pitch_scale=1.0,
loop=True,
autoplay=True
)
root.add_child(music)
AudioStreamPlayer2D¶
2D positional audio with stereo panning based on screen position.
Properties:
All
AudioStreamPlayerproperties, plus:max_distance(float): Distance at which audio is inaudible (pixels).attenuation(float): Distance attenuation exponent (1.0 = linear, 2.0 = inverse square).
2D Spatialization:
Volume decreases with distance from listener (camera)
Stereo panning: left/right based on horizontal position
Automatically updates each frame via
_process()
Example:
from simvx.core import AudioStreamPlayer2D, Vec2
footstep = AudioStreamPlayer2D(
stream="sfx/footstep.wav",
position=Vec2(400, 300),
max_distance=200.0,
attenuation=1.5,
bus="sfx"
)
root.add_child(footstep)
# Play when player moves
if player.is_moving:
footstep.position = player.position
footstep.play()
AudioStreamPlayer3D¶
3D spatial audio with distance attenuation, directional panning, and Doppler effect.
Properties:
All
AudioStreamPlayerproperties, plus:max_distance(float): Distance at which audio is inaudible (world units).attenuation(float): Distance attenuation exponent.doppler_scale(float): Doppler effect strength (0.0 = off, 1.0 = realistic).
3D Spatialization:
Volume decreases with distance from listener (Camera3D)
Stereo panning based on left/right position relative to camera
Pitch shifts based on velocity (Doppler effect)
Automatically updates each frame via
_process()
Example:
from simvx.core import AudioStreamPlayer3D, Vec3
engine = AudioStreamPlayer3D(
stream="sfx/car_engine.ogg",
position=Vec3(0, 0, 0),
max_distance=50.0,
attenuation=2.0,
doppler_scale=1.0,
loop=True
)
car.add_child(engine)
engine.play()
# Engine sound follows car and shifts pitch based on velocity
Audio Buses¶
Players route through named buses. Each player sets bus="master" | "music" | "sfx" | "ui". Control bus-level gain via the layout:
from simvx.core.audio_bus import AudioBusLayout
layout = AudioBusLayout.get_default()
layout.get_bus("music").volume_db = -6.0 # quieter
layout.get_bus("sfx").mute = True # silence SFX
Buses route to a parent via send_to; the effective gain is the sum along the chain to the master bus.
Audio Sources¶
AudioStream and AudioStreamPlayer* accept any of:
Source |
Use case |
|---|---|
|
Filesystem audio file (relative paths resolve to CWD) |
|
Asset shipped inside a Python package |
|
Raw |
|
Procedural sine-wave tone (no file) |
Sharing a decoded stream between players:
from pathlib import Path
from simvx.core import AudioStream, AudioStreamPlayer
stream = AudioStream(Path("music/boss_fight.ogg")) # constructed once
player1 = AudioStreamPlayer(stream=stream)
player2 = AudioStreamPlayer(stream=stream)
Audio Listener¶
The audio listener tracks the active camera position for 3D spatialization.
Automatic Updates:
SDL3 backend automatically finds the first Camera3D in the scene
Listener position is updated each frame in
App.tick()2D audio uses
position_2d, 3D audio usesposition_3d
Manual Control (advanced):
from simvx.core import AudioListener, Vec3
listener = AudioListener.get()
listener.position_3d = Vec3(5, 2, -3)
listener.forward = Vec3(0, 0, -1)
listener.up = Vec3(0, 1, 0)
Serialization¶
AudioStreamPlayer, AudioStreamPlayer2D, and AudioStreamPlayer3D are regular nodes — add them as children and they round-trip through the Python scene format like any other node. See Scenes.
Supported Formats¶
Supported formats depend on the audio backend. Common formats:
WAV - Uncompressed audio
OGG - Ogg Vorbis (recommended for music)
MP3 - MPEG audio (patent-free in most countries)
Attenuation & Doppler¶
Distance: volume_db -= 80 · (distance / max_distance) ^ attenuation. Pick attenuation=1.0 (linear), 2.0 (inverse square, physically realistic), or 0.5 (slow falloff for ambience).
Doppler (3D only): pitch shifts with velocity toward/away from the listener. doppler_scale=0.0 disables, 1.0 is physically realistic (sound speed 343 m/s), 2.0 exaggerates for racing games.
Installation¶
Audio support is provided by backends that implement AudioBackend. The default SDL3 backend ships with the streaming extra:
uv pip install -e packages/graphics[streaming]
Performance Tips¶
Use OGG for music - Smaller file size than WAV
Use WAV for short SFX - Fast loading, no decompression overhead
Limit active 3D sounds - Each 3D player updates every frame
Share decoded streams - Construct
AudioStream(...)once and pass it to multiple playersAdjust update rate - 3D spatialization runs in
process(), consider lower physics_fps for audio-heavy scenes
Troubleshooting¶
No sound: confirm the streaming backend is installed (uv pip install -e packages/graphics[streaming]), that audio files resolve to a real path (AudioStream(...).path should exist), and that no bus in the chain is muted (AudioBusLayout.get_default()).
Crackling / stuttering: the backend can be constructed with AudioBackend(buffer_size=1024, channels=8, sample_rate=22050) for lower-end hardware.
3D panning stuck: verify a Camera3D exists in the scene (the listener auto-follows the first one) and that max_distance is large enough to reach the listener.
Example: Complete Game Audio¶
from simvx.core import Node, Camera3D, AudioStreamPlayer, AudioStreamPlayer3D, Input, InputMap, MouseButton, Vec3
from simvx.graphics import App
class GameScene(Node):
def __init__(self):
super().__init__()
# Background music
music = AudioStreamPlayer(
stream="music/gameplay.ogg",
volume_db=-8.0,
loop=True,
autoplay=True,
bus="music"
)
self.add_child(music)
# UI click sound
self.ui_click = AudioStreamPlayer(
stream="ui/button_click.wav",
bus="ui"
)
self.add_child(self.ui_click)
# 3D ambient sound (waterfall)
waterfall = AudioStreamPlayer3D(
stream="ambient/waterfall.ogg",
position=Vec3(20, 0, 10),
max_distance=30.0,
loop=True,
autoplay=True,
bus="sfx"
)
self.add_child(waterfall)
# Camera
camera = Camera3D(position=(0, 5, 10))
self.add_child(camera)
def ready(self):
InputMap.add_action("click", [MouseButton.LEFT])
def process(self, dt):
# Play UI sound on button click
if Input.is_action_just_pressed("click"):
self.ui_click.play()
if __name__ == "__main__":
app = App(width=1280, height=720, title="Game")
app.run(GameScene())
API Reference¶
See source code for complete API:
/packages/core/src/simvx/core/audio.py- Core audio classes/packages/core/tests/test_core.py- Audio tests and examples