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 playback

  • stop() - Stop playback

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

  • 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 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="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="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

str / pathlib.Path

Filesystem audio file (relative paths resolve to CWD)

Resource(package, name)

Asset shipped inside a Python package

importlib.resources.Traversable

Raw importlib.resources.files(pkg) / name handle

AudioStream.tone(freq_hz, duration=1)

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 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

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

  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. Share decoded streams - Construct AudioStream(...) once and pass it to multiple players

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