Panda3D is a framework for 3D rendering and game development for Python and C++ programs.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Panda3D provides a comprehensive audio system with support for 3D positional audio, sound effects, music playback, and spatial audio processing for immersive 3D environments.
The Audio3DManager handles 3D spatial audio with automatic distance attenuation, doppler effects, and positional sound placement.
class Audio3DManager:
def __init__(self,
audio_manager,
listener_target: NodePath,
root: NodePath = None,
taskPriority: int = 51,
autoUpdate: bool = True) -> None:
"""
Initialize 3D audio manager.
Args:
audio_manager: Base audio manager instance
listener_target: NodePath representing listener position (usually camera)
root: Root node for audio coordinate system
taskPriority: Priority of audio update task
autoUpdate: Whether to automatically update audio positions
"""
def loadSfx(self, filename: str) -> AudioSound:
"""Load sound effect file."""
def loadMusic(self, filename: str) -> AudioSound:
"""Load music file."""
def attachSoundToObject(self, sound: AudioSound, object: NodePath) -> None:
"""Attach sound to 3D object for automatic positioning."""
def detachSound(self, sound: AudioSound) -> None:
"""Detach sound from its 3D object."""
def setListenerVelocity(self, velocity: Vec3) -> None:
"""Set listener velocity for doppler effects."""
def setDistanceFactor(self, factor: float) -> None:
"""Set distance scaling factor."""
def setDopplerFactor(self, factor: float) -> None:
"""Set doppler effect strength."""
def setDropOffFactor(self, factor: float) -> None:
"""Set audio drop-off/attenuation factor."""
def attachSoundToObject(self,
sound: AudioSound,
object: NodePath) -> None:
"""Attach sound to follow an object's position."""
def detachSound(self, sound: AudioSound) -> None:
"""Detach sound from object."""
def setSoundVelocity(self, sound: AudioSound, velocity: Vec3) -> None:
"""Set sound source velocity."""
def setSoundVelocityAuto(self, sound: AudioSound) -> None:
"""Enable automatic velocity calculation."""
def setSoundMinDistance(self, sound: AudioSound, distance: float) -> None:
"""Set minimum distance for audio attenuation."""
def setSoundMaxDistance(self, sound: AudioSound, distance: float) -> None:
"""Set maximum distance for audio attenuation."""
def update(self) -> None:
"""Manually update audio positions."""
def disable(self) -> None:
"""Disable 3D audio processing."""
def enable(self) -> None:
"""Enable 3D audio processing."""AudioSound objects provide detailed control over individual audio sources.
class AudioSound:
def play(self) -> None:
"""Play the sound."""
def stop(self) -> None:
"""Stop playing the sound."""
def setLoop(self, loop: bool) -> None:
"""Set whether sound should loop."""
def getLoop(self) -> bool:
"""Check if sound is set to loop."""
def setLoopStart(self, start_time: float) -> None:
"""Set loop start time in seconds."""
def setLoopEnd(self, end_time: float) -> None:
"""Set loop end time in seconds."""
def setVolume(self, volume: float) -> None:
"""Set sound volume (0.0 to 1.0)."""
def getVolume(self) -> float:
"""Get current volume."""
def setBalance(self, balance: float) -> None:
"""Set left/right balance (-1.0 to 1.0)."""
def getBalance(self) -> float:
"""Get current balance."""
def setPlayRate(self, rate: float) -> None:
"""Set playback speed multiplier."""
def getPlayRate(self) -> float:
"""Get current playback rate."""
def length(self) -> float:
"""Get sound duration in seconds."""
def getTime(self) -> float:
"""Get current playback position in seconds."""
def setTime(self, time: float) -> None:
"""Set playback position."""
def status(self) -> int:
"""Get playback status (READY, PLAYING, BAD)."""
def finished(self) -> bool:
"""Check if sound has finished playing."""
def ready(self) -> bool:
"""Check if sound is ready to play."""Simple audio playback without 3D positioning for music and UI sounds.
class AudioManager:
def getSound(self, filename: str, positional: bool = False) -> AudioSound:
"""Load audio file and return AudioSound object."""
def playSound(self,
sound: AudioSound,
looping: bool = False,
interrupt: bool = True,
volume: float = 1.0,
node: NodePath = None) -> None:
"""Play sound with optional 3D positioning."""
def stopSound(self, sound: AudioSound) -> None:
"""Stop playing sound."""
def setVolume(self, volume: float) -> None:
"""Set global volume."""
def getVolume(self) -> float:
"""Get global volume."""
def setSfxVolume(self, volume: float) -> None:
"""Set sound effects volume."""
def getSfxVolume(self) -> float:
"""Get sound effects volume."""
def setMusicVolume(self, volume: float) -> None:
"""Set music volume."""
def getMusicVolume(self) -> float:
"""Get music volume."""
def stopAllSounds(self) -> None:
"""Stop all currently playing sounds."""
def setSoundsActive(self, active: bool) -> None:
"""Enable or disable all sound playback."""
def getSoundsActive(self) -> bool:
"""Check if sounds are active."""from direct.showbase.ShowBase import ShowBase
from direct.showbase.Audio3DManager import Audio3DManager
from panda3d.core import Vec3
from direct.task import Task
class Audio3DDemo(ShowBase):
def __init__(self):
ShowBase.__init__(self)
# Initialize 3D audio manager
self.audio3d = Audio3DManager(base.sfxManagerList[0], self.camera)
# Load sounds
self.engine_sound = self.audio3d.loadSfx("sounds/engine.wav")
self.footsteps = self.audio3d.loadSfx("sounds/footsteps.wav")
self.ambient = self.audio3d.loadMusic("sounds/ambient.ogg")
# Create moving objects
self.createMovingObjects()
# Setup audio properties
self.setupAudioProperties()
# Start background music
self.audio3d.play(self.ambient, looping=True, volume=0.3)
# Setup controls
self.setupControls()
def createMovingObjects(self):
"""Create objects that will have attached sounds."""
# Car with engine sound
self.car = self.loader.loadModel("models/car")
self.car.reparentTo(self.render)
self.car.setPos(-10, 20, 0)
# Attach engine sound to car
self.audio3d.attachSoundToObject(self.engine_sound, self.car)
self.audio3d.play(self.engine_sound, looping=True)
# Set up car movement
self.taskMgr.add(self.moveCar, "move-car")
# Walking character
self.character = self.loader.loadModel("models/character")
self.character.reparentTo(self.render)
self.character.setPos(5, 15, 0)
# Attach footstep sound
self.audio3d.attachSoundToObject(self.footsteps, self.character)
def setupAudioProperties(self):
"""Configure 3D audio properties."""
# Set distance factors
self.audio3d.setDistanceFactor(1.0)
self.audio3d.setDopplerFactor(1.0)
self.audio3d.setDropOffFactor(0.1)
# Configure individual sounds
self.audio3d.setSoundMinDistance(self.engine_sound, 5.0)
self.audio3d.setSoundMaxDistance(self.engine_sound, 50.0)
self.audio3d.setSoundMinDistance(self.footsteps, 2.0)
self.audio3d.setSoundMaxDistance(self.footsteps, 20.0)
def moveCar(self, task):
"""Move car in circle for doppler demonstration."""
angle = task.time * 30 # 30 degrees per second
radius = 15
x = radius * math.cos(math.radians(angle))
y = 20 + radius * math.sin(math.radians(angle))
self.car.setPos(x, y, 0)
self.car.setH(angle + 90) # Face movement direction
return task.cont
def setupControls(self):
"""Setup keyboard controls."""
self.accept("1", self.toggleEngine)
self.accept("2", self.playFootsteps)
self.accept("3", self.adjustVolume, [0.1])
self.accept("4", self.adjustVolume, [-0.1])
# Movement controls for listener
self.accept("arrow_left", self.moveListener, [Vec3(-1, 0, 0)])
self.accept("arrow_right", self.moveListener, [Vec3(1, 0, 0)])
self.accept("arrow_up", self.moveListener, [Vec3(0, 1, 0)])
self.accept("arrow_down", self.moveListener, [Vec3(0, -1, 0)])
def toggleEngine(self):
"""Toggle engine sound."""
if self.engine_sound.status() == AudioSound.PLAYING:
self.audio3d.stop(self.engine_sound)
else:
self.audio3d.play(self.engine_sound, looping=True)
def playFootsteps(self):
"""Play footstep sound."""
self.audio3d.play(self.footsteps)
def adjustVolume(self, change):
"""Adjust global volume."""
current = base.musicManager.getVolume()
new_volume = max(0.0, min(1.0, current + change))
base.musicManager.setVolume(new_volume)
print(f"Volume: {new_volume:.1f}")
def moveListener(self, direction):
"""Move audio listener (camera)."""
current_pos = self.camera.getPos()
new_pos = current_pos + direction * 2
self.camera.setPos(new_pos)
print(f"Listener position: {new_pos}")
import math
app = Audio3DDemo()
app.run()from direct.showbase.DirectObject import DirectObject
from direct.showbase.Audio3DManager import Audio3DManager
class SoundManager(DirectObject):
"""Centralized sound management system."""
def __init__(self, listener_target):
DirectObject.__init__(self)
# Initialize audio systems
self.audio3d = Audio3DManager(base.sfxManagerList[0], listener_target)
# Sound libraries
self.sounds = {}
self.music = {}
self.current_music = None
# Volume settings
self.master_volume = 1.0
self.sfx_volume = 1.0
self.music_volume = 0.7
# Load sound libraries
self.loadSounds()
def loadSounds(self):
"""Load all game sounds."""
# Sound effects
sound_files = {
"button_click": "sounds/ui/button_click.wav",
"explosion": "sounds/fx/explosion.wav",
"pickup": "sounds/fx/pickup.wav",
"footstep": "sounds/character/footstep.wav",
"jump": "sounds/character/jump.wav"
}
for name, filename in sound_files.items():
try:
self.sounds[name] = self.audio3d.loadSfx(filename)
except:
print(f"Failed to load sound: {filename}")
# Music tracks
music_files = {
"menu": "music/menu_theme.ogg",
"level1": "music/level1_bg.ogg",
"victory": "music/victory.ogg"
}
for name, filename in music_files.items():
try:
self.music[name] = self.audio3d.loadMusic(filename)
except:
print(f"Failed to load music: {filename}")
def playSfx(self, name, volume=None, position=None, looping=False):
"""Play sound effect."""
if name not in self.sounds:
print(f"Sound not found: {name}")
return
sound = self.sounds[name]
# Set volume
if volume is None:
volume = self.sfx_volume
sound.setVolume(volume * self.master_volume)
# Set position if specified
if position:
self.audio3d.attachSoundToObject(sound, position)
# Play sound
self.audio3d.play(sound, looping=looping)
def playMusic(self, name, fade_in=False, fade_out_current=False):
"""Play background music."""
if name not in self.music:
print(f"Music not found: {name}")
return
# Stop current music
if self.current_music:
if fade_out_current:
self.fadeOutMusic(self.current_music, 1.0)
else:
self.audio3d.stop(self.current_music)
# Start new music
music = self.music[name]
music.setVolume(self.music_volume * self.master_volume)
if fade_in:
self.fadeInMusic(music, 2.0)
else:
self.audio3d.play(music, looping=True)
self.current_music = music
def stopMusic(self):
"""Stop current music."""
if self.current_music:
self.audio3d.stop(self.current_music)
self.current_music = None
def fadeInMusic(self, music, duration):
"""Fade in music over time."""
target_volume = self.music_volume * self.master_volume
music.setVolume(0.0)
self.audio3d.play(music, looping=True)
# Create fade task
def fadeTask(task):
progress = task.time / duration
if progress >= 1.0:
music.setVolume(target_volume)
return task.done
volume = target_volume * progress
music.setVolume(volume)
return task.cont
taskMgr.add(fadeTask, f"fade-in-{id(music)}")
def fadeOutMusic(self, music, duration):
"""Fade out music over time."""
start_volume = music.getVolume()
def fadeTask(task):
progress = task.time / duration
if progress >= 1.0:
self.audio3d.stop(music)
return task.done
volume = start_volume * (1.0 - progress)
music.setVolume(volume)
return task.cont
taskMgr.add(fadeTask, f"fade-out-{id(music)}")
def setMasterVolume(self, volume):
"""Set master volume level."""
self.master_volume = max(0.0, min(1.0, volume))
# Update current music volume
if self.current_music:
self.current_music.setVolume(self.music_volume * self.master_volume)
def setSfxVolume(self, volume):
"""Set sound effects volume."""
self.sfx_volume = max(0.0, min(1.0, volume))
def setMusicVolume(self, volume):
"""Set music volume."""
self.music_volume = max(0.0, min(1.0, volume))
# Update current music
if self.current_music:
self.current_music.setVolume(self.music_volume * self.master_volume)
def cleanup(self):
"""Clean up audio resources."""
self.stopMusic()
self.audio3d.disable()
self.ignoreAll()# Audio status constants
class AudioSound:
READY = 2 # Sound loaded and ready
PLAYING = 3 # Sound currently playing
BAD = 4 # Sound failed to load
# Audio file format support
SUPPORTED_AUDIO_FORMATS = [
".wav", # WAV (uncompressed)
".ogg", # Ogg Vorbis (compressed)
".mp3", # MP3 (compressed, if available)
".flac" # FLAC (lossless, if available)
]Install with Tessl CLI
npx tessl i tessl/pypi-panda3d