"""Contains the SfxPlayer class, a thin utility class for playing sounds at
a particular location."""
__all__ = ['SfxPlayer']
import math
from panda3d.core import *
[docs]class SfxPlayer:
"""
Play sound effects, potentially localized.
"""
UseInverseSquare = 0
[docs] def __init__(self):
# volume attenuates according to the inverse square of the
# distance from the source. volume = 1/(distance^2)
# this is the volume at which a sound is nearly inaudible
self.cutoffVolume = .02
# cutoff for inverse square attenuation
if SfxPlayer.UseInverseSquare:
self.setCutoffDistance(300.0)
else:
# Distance at which sounds can no longer be heard
# This was determined experimentally
self.setCutoffDistance(120.0)
[docs] def setCutoffDistance(self, d):
self.cutoffDistance = d
# this is the 'raw' distance at which the volume of a sound will
# be equal to the cutoff volume
rawCutoffDistance = math.sqrt(1./self.cutoffVolume)
# this is a scale factor to convert distances so that a sound
# located at self.cutoffDistance will have a volume
# of self.cutoffVolume
self.distanceScale = rawCutoffDistance / self.cutoffDistance
[docs] def getCutoffDistance(self):
"""Return the curent cutoff distance."""
return self.cutoffDistance
[docs] def getLocalizedVolume(self, node, listenerNode = None, cutoff = None):
"""
Get the volume that a sound should be played at if it is
localized at this node. We compute this wrt the camera
or to listenerNode.
"""
d = None
if not node.isEmpty():
if listenerNode and not listenerNode.isEmpty():
d = node.getDistance(listenerNode)
else:
d = node.getDistance(base.cam)
if not cutoff:
cutoff = self.cutoffDistance
if d is None or d > cutoff:
volume = 0
else:
if SfxPlayer.UseInverseSquare:
sd = d*self.distanceScale
volume = min(1, 1 / (sd*sd or 1))
#print d, sd, volume
else:
volume = 1 - (d / (cutoff or 1))
#print d, volume
return volume
[docs] def playSfx(
self, sfx, looping = 0, interrupt = 1, volume = None,
time = 0.0, node=None, listenerNode = None, cutoff = None):
if sfx:
self.setFinalVolume(sfx, node, volume, listenerNode, cutoff)
# don't start over if it's already playing, unless
# "interrupt" was specified
if interrupt or (sfx.status() != AudioSound.PLAYING):
sfx.setTime(time)
sfx.setLoop(looping)
sfx.play()
[docs] def setFinalVolume(self, sfx, node, volume, listenerNode, cutoff = None):
"""Calculate the final volume based on all contributed factors."""
# If we have either a node or a volume, we need to adjust the sfx
# The volume passed in multiplies the distance base volume
if node or (volume is not None):
if node:
finalVolume = self.getLocalizedVolume(node, listenerNode, cutoff)
else:
finalVolume = 1
if volume is not None:
finalVolume *= volume
if node is not None:
finalVolume *= node.getNetAudioVolume()
sfx.setVolume(finalVolume)