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 playback

  • stop() - Stop playback

  • pause() - Pause playback (resume with play())

  • 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 AudioStreamPlayer properties, 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 AudioStreamPlayer properties, 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 uses position_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 falloff

  • 2.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 effect

  • 1.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

  1. Use OGG for music - Smaller file size than WAV

  2. Use WAV for short SFX - Fast loading, no decompression overhead

  3. Limit active 3D sounds - Each 3D player updates every frame

  4. Cache audio streams - Use ResourceCache.resolve_audio() for shared sounds

  5. Adjust 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