# 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 ```python 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 ```python 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 ```python 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:** ```python 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:** ```python 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:** ```python 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):** ```python # 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:** ```python 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):** ```python 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:** ```json { "__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:** ```python 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: ```bash 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 ```python 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