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)
Resource Caching - Automatic loading and caching of audio files
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="audio://music/theme.ogg",
volume_db=-5.0,
loop=True,
autoplay=True,
bus="music"
)
root.add_child(music)
2. 2D Sound Effects¶
from simvx.core import AudioStreamPlayer2D, Vec2
explosion = AudioStreamPlayer2D(
stream="audio://sfx/explosion.wav",
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="audio://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()
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="audio://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="audio://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="audio://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¶
Audio buses allow independent volume control per category.
Default Buses:
master- Global volume (affects all buses)music- Background music (default 70% volume)sfx- Sound effects (default 100% volume)ui- UI sounds (default 80% volume)
Controlling Bus Volume (SDL3 backend):
# Get audio backend from scene tree
audio_backend = scene_tree._audio_backend
# Set bus volumes (0.0 to 1.0)
audio_backend.set_bus_volume("music", 0.5) # 50% volume
audio_backend.set_bus_volume("sfx", 0.8) # 80% volume
Resource Caching¶
Audio files are automatically loaded and cached using URI syntax.
URI Format:
audio://path/to/file.wav
audio://music/theme.ogg
audio://sfx/explosion.wav
Manual Loading:
from simvx.core import ResourceCache
cache = ResourceCache.get()
stream = cache.resolve_audio("audio://music/boss_fight.ogg")
# Use cached stream in multiple players
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¶
Audio players can be saved and loaded in scene files.
Example Scene:
{
"__type__": "Node",
"name": "GameScene",
"children": [
{
"__type__": "AudioStreamPlayer",
"name": "BackgroundMusic",
"stream": "audio://music/level1.ogg",
"settings": {
"volume_db": -5.0,
"loop": true,
"autoplay": true,
"bus": "music"
}
},
{
"__type__": "AudioStreamPlayer3D",
"name": "Campfire",
"position": {"__vec3__": [10, 0, 5]},
"stream": "audio://sfx/fire_crackling.ogg",
"settings": {
"max_distance": 20.0,
"loop": true,
"autoplay": true
}
}
]
}
Save/Load:
from simvx.core import save_scene, load_scene
# Save
save_scene(root_node, "scenes/level1.json")
# Load
scene = load_scene("scenes/level1.json")
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)
Distance Attenuation Formula¶
Volume decreases with distance using the formula:
volume_db = base_volume_db - 80 * (distance / max_distance) ^ attenuation
Attenuation Values:
1.0- Linear falloff2.0- Inverse square (realistic for point sources)0.5- Slow falloff (large ambient sounds)4.0- Rapid falloff (small sounds)
Doppler Effect (3D Only)¶
Pitch shifts based on velocity towards/away from listener:
pitch = pitch_scale * (1 - (velocity_towards / speed_of_sound) * doppler_scale)
Doppler Scale Values:
0.0- No Doppler effect1.0- Realistic Doppler (speed of sound = 343 m/s)2.0- Exaggerated effect (race games)
Note: pygame mixer doesn’t support runtime pitch shifting, so Doppler effect is calculated but not applied in the SDL3 backend. Use a different audio backend (OpenAL, FMOD) for full Doppler support.
Installation¶
Audio support is provided by backends that implement AudioBackend. The SDL3 backend includes audio out of the box:
pip install simvx-sdl3
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
Cache audio streams - Use
ResourceCache.resolve_audio()for shared soundsAdjust update rate - 3D spatialization runs in
process(), consider lower physics_fps for audio-heavy scenes
Troubleshooting¶
No sound output:
Check audio backend is installed and configured
Verify audio files exist at specified paths
Check bus volumes:
audio_backend.set_bus_volume("master", 1.0)Verify App is using a backend with audio support
Crackling/stuttering:
Increase buffer size:
AudioBackend(buffer_size=1024)Reduce number of active channels:
AudioBackend(channels=8)Use lower sample rate:
AudioBackend(sample_rate=22050)
3D audio not panning:
Verify listener position is updating (check
_update_audio_listener()is called)Check camera exists in scene:
tree.root.find_all(Camera3D)Ensure max_distance is large enough
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="audio://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="audio://ui/button_click.wav",
bus="ui"
)
self.add_child(self.ui_click)
# 3D ambient sound (waterfall)
waterfall = AudioStreamPlayer3D(
stream="audio://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