Browse Source

volume attenuation with distance

Joe Shochet 23 years ago
parent
commit
c231386f16

+ 2 - 2
direct/src/interval/IntervalTest.py

@@ -16,8 +16,8 @@ donald.reparentTo(boat)
 dock = loader.loadModel('models/misc/smiley')
 dock = loader.loadModel('models/misc/smiley')
 dock.reparentTo(render)
 dock.reparentTo(render)
 
 
-sound = loader.loadSound('phase_6/audio/sfx/SZ_DD_waterlap.mp3')
-foghorn = loader.loadSound('phase_6/audio/sfx/SZ_DD_foghorn.mp3')
+sound = loader.loadSfx('phase_6/audio/sfx/SZ_DD_waterlap.mp3')
+foghorn = loader.loadSfx('phase_6/audio/sfx/SZ_DD_foghorn.mp3')
 
 
 mp = Mopath.Mopath()
 mp = Mopath.Mopath()
 mp.loadFile(Filename('phase_6/paths/dd-e-w'))
 mp.loadFile(Filename('phase_6/paths/dd-e-w'))

+ 5 - 11
direct/src/interval/SoundInterval.py

@@ -19,7 +19,8 @@ class SoundInterval(Interval.Interval):
     # seems to be some timing in the audio such that the stop doesn't
     # seems to be some timing in the audio such that the stop doesn't
     # kill the looping sound until the next time around if duration
     # kill the looping sound until the next time around if duration
     # of the interval equals duration of the sound
     # of the interval equals duration of the sound
-    def __init__(self, sound, loop = 0, duration = 0.0, name = None, volume = 1.0, startTime = 0.0):
+    def __init__(self, sound, loop = 0, duration = 0.0, name = None,
+                 volume = 1.0, startTime = 0.0, node=None):
         """__init__(sound, loop, name)
         """__init__(sound, loop, name)
         """
         """
         # Generate unique name
         # Generate unique name
@@ -30,6 +31,7 @@ class SoundInterval(Interval.Interval):
         self.fLoop = loop
         self.fLoop = loop
         self.volume = volume
         self.volume = volume
         self.startTime = startTime
         self.startTime = startTime
+        self.node = node
         # If no duration given use sound's duration as interval's duration
         # If no duration given use sound's duration as interval's duration
         if duration == 0.0 and self.sound != None:
         if duration == 0.0 and self.sound != None:
             duration = max(self.sound.length() - self.startTime, 0)
             duration = max(self.sound.length() - self.startTime, 0)
@@ -52,22 +54,14 @@ class SoundInterval(Interval.Interval):
         t1 = t + self.startTime
         t1 = t + self.startTime
         if (t1 < 0.1):
         if (t1 < 0.1):
             t1 = 0.0
             t1 = 0.0
-        if self.sound != None:
-            self.sound.setVolume(self.volume)
-            self.sound.setTime(t1)
-            self.sound.setLoop(self.fLoop)
-            self.sound.play()
+        base.sfxPlayer.playSfx(self.sound, self.fLoop, 1, self.volume, t1, self.node)
         self.state = CInterval.SStarted
         self.state = CInterval.SStarted
         self.currT = t1
         self.currT = t1
 
 
     def privStep(self, t):
     def privStep(self, t):
         if self.state == CInterval.SPaused:
         if self.state == CInterval.SPaused:
             # Restarting from a pause.
             # Restarting from a pause.
-            if self.sound != None:
-                self.sound.setVolume(self.volume)
-                self.sound.setTime(t)
-                self.sound.setLoop(self.fLoop)
-                self.sound.play()
+            base.sfxPlayer.playSfx(self.sound, self.fLoop, 1, self.volume, t, self.node)
         self.state = CInterval.SStarted
         self.state = CInterval.SStarted
         self.currT = t
         self.currT = t
 
 

+ 31 - 19
direct/src/showbase/Loader.py

@@ -13,7 +13,6 @@ class Loader:
     """Loader class: contains method to load models, sounds and code"""
     """Loader class: contains method to load models, sounds and code"""
 
 
     notify = directNotify.newCategory("Loader")
     notify = directNotify.newCategory("Loader")
-    #notify.setDebug(1)
     modelCount = 0
     modelCount = 0
     
     
     # special methods
     # special methods
@@ -28,7 +27,7 @@ class Loader:
         """loadModel(self, string)
         """loadModel(self, string)
         Attempt to load a model from given file path, return
         Attempt to load a model from given file path, return
         a nodepath to the model if successful or None otherwise."""
         a nodepath to the model if successful or None otherwise."""
-        Loader.notify.debug("Loading model: %s" % (modelPath) )
+        assert(Loader.notify.debug("Loading model: %s" % (modelPath) ))
         if phaseChecker:
         if phaseChecker:
             phaseChecker(modelPath)
             phaseChecker(modelPath)
         node = self.loader.loadSync(Filename(modelPath))
         node = self.loader.loadSync(Filename(modelPath))
@@ -45,7 +44,7 @@ class Loader:
         Attempt to load a model from modelPool, if not present
         Attempt to load a model from modelPool, if not present
         then attempt to load it from disk. Return a nodepath to
         then attempt to load it from disk. Return a nodepath to
         the model if successful or None otherwise"""
         the model if successful or None otherwise"""
-        Loader.notify.debug("Loading model once: %s" % (modelPath))
+        assert(Loader.notify.debug("Loading model once: %s" % (modelPath)))
         if phaseChecker:
         if phaseChecker:
             phaseChecker(modelPath)
             phaseChecker(modelPath)
         node = ModelPool.loadModel(modelPath)
         node = ModelPool.loadModel(modelPath)
@@ -71,7 +70,7 @@ class Loader:
         
         
         """
         """
 
 
-        Loader.notify.debug("Loading model once: %s under %s" % (modelPath, underNode))
+        assert(Loader.notify.debug("Loading model once: %s under %s" % (modelPath, underNode)))
         if phaseChecker:
         if phaseChecker:
             phaseChecker(modelPath)
             phaseChecker(modelPath)
         node = ModelPool.loadModel(modelPath)
         node = ModelPool.loadModel(modelPath)
@@ -87,7 +86,7 @@ class Loader:
         Attempt to load a model from modelPool, if not present
         Attempt to load a model from modelPool, if not present
         then attempt to load it from disk. Return a nodepath to
         then attempt to load it from disk. Return a nodepath to
         a copy of the model if successful or None otherwise"""
         a copy of the model if successful or None otherwise"""
-        Loader.notify.debug("Loading model copy: %s" % (modelPath))
+        assert(Loader.notify.debug("Loading model copy: %s" % (modelPath)))
         if phaseChecker:
         if phaseChecker:
             phaseChecker(modelPath)
             phaseChecker(modelPath)
         node = ModelPool.loadModel(modelPath)
         node = ModelPool.loadModel(modelPath)
@@ -108,8 +107,7 @@ class Loader:
 
 
         However, if you're loading a font, see loadFont(), below.
         However, if you're loading a font, see loadFont(), below.
         """
         """
-        
-        Loader.notify.debug("Loading model once node: %s" % (modelPath))
+        assert(Loader.notify.debug("Loading model once node: %s" % (modelPath)))
         if phaseChecker:
         if phaseChecker:
             phaseChecker(modelPath)
             phaseChecker(modelPath)
         return ModelPool.loadModel(modelPath)
         return ModelPool.loadModel(modelPath)
@@ -117,7 +115,7 @@ class Loader:
     def unloadModel(self, modelPath):
     def unloadModel(self, modelPath):
         """unloadModel(self, string)
         """unloadModel(self, string)
         """
         """
-        Loader.notify.debug("Unloading model: %s" % (modelPath))
+        assert(Loader.notify.debug("Unloading model: %s" % (modelPath)))
         ModelPool.releaseModel(modelPath)
         ModelPool.releaseModel(modelPath)
 
 
     # font loading funcs
     # font loading funcs
@@ -136,7 +134,7 @@ class Loader:
         standard font file (like a TTF file) that is supported by
         standard font file (like a TTF file) that is supported by
         FreeType.
         FreeType.
         """
         """
-        Loader.notify.debug("Loading font: %s" % (modelPath))
+        assert(Loader.notify.debug("Loading font: %s" % (modelPath)))
         if phaseChecker:
         if phaseChecker:
             phaseChecker(modelPath)
             phaseChecker(modelPath)
 
 
@@ -202,12 +200,12 @@ class Loader:
         TexturePool class. Returns None if not found"""
         TexturePool class. Returns None if not found"""
 
 
         if alphaPath == None:
         if alphaPath == None:
-            Loader.notify.debug("Loading texture: %s" % (texturePath) )
+            assert(Loader.notify.debug("Loading texture: %s" % (texturePath) ))
             if phaseChecker:
             if phaseChecker:
                 phaseChecker(texturePath)
                 phaseChecker(texturePath)
             texture = TexturePool.loadTexture(texturePath)
             texture = TexturePool.loadTexture(texturePath)
         else:
         else:
-            Loader.notify.debug("Loading texture: %s %s" % (texturePath, alphaPath) )
+            assert(Loader.notify.debug("Loading texture: %s %s" % (texturePath, alphaPath) ))
             if phaseChecker:
             if phaseChecker:
                 phaseChecker(texturePath)
                 phaseChecker(texturePath)
             texture = TexturePool.loadTexture(texturePath, alphaPath)
             texture = TexturePool.loadTexture(texturePath, alphaPath)
@@ -216,20 +214,34 @@ class Loader:
     def unloadTexture(self, texture):
     def unloadTexture(self, texture):
         """unloadTexture(self, texture)
         """unloadTexture(self, texture)
         """
         """
-        Loader.notify.debug("Unloading texture: %s" % (texture) )
+        assert(Loader.notify.debug("Unloading texture: %s" % (texture) ))
         TexturePool.releaseTexture(texture)
         TexturePool.releaseTexture(texture)
 
 
     # sound loading funcs
     # sound loading funcs
-    def loadSound(self, soundPath):
-        """loadSound(self, string)
-        Attempt to load a sound from the given file path using
-        Cary's sound class. Returns None if not found"""
-        Loader.notify.debug("Loading sound: %s" % (soundPath) )
+    def loadSfx(self, name):
+        assert(Loader.notify.debug("Loading sound: %s" % (name) ))
         if phaseChecker:
         if phaseChecker:
-            phaseChecker(soundPath)
-        sound = base.sfxManager.getSound(soundPath)
+            phaseChecker(name)
+        # should return a valid sound obj even if soundMgr is invalid
+        sound = None
+        if (name):
+            # showbase-created sfxManager should always be at front of list
+            sound=base.sfxManagerList[0].getSound(name)
+        if sound == None:
+            Loader.notify.warning("Could not load sound file %s." % name)
+        return sound
+
+    def loadMusic(self, name):
+        assert(Loader.notify.debug("Loading sound: %s" % (name) ))
+        # should return a valid sound obj even if musicMgr is invalid
+        sound = None
+        if (name):
+            sound=base.musicManager.getSound(name)
+        if sound == None:
+            Loader.notify.warning("Could not load music file %s." % name)
         return sound
         return sound
 
 
+
     def makeNodeNamesUnique(self, nodePath, nodeCount):
     def makeNodeNamesUnique(self, nodePath, nodeCount):
         if nodeCount == 0:
         if nodeCount == 0:
             Loader.modelCount += 1
             Loader.modelCount += 1

+ 45 - 0
direct/src/showbase/SfxPlayer.py

@@ -0,0 +1,45 @@
+
+
+
+class SfxPlayer:
+    """
+    Play sound effects, potentially localized.
+    """
+
+    def __init__(self):
+        # Distance at which sounds can no longer be heard
+        # This was determined experimentally
+        self.cutoffDistance = 120.0
+
+    def getLocalizedVolume(self, node):
+        """
+        Get the volume that a sound should be played at if it is
+        localized at this node. We compute this wrt the camera
+        """
+        import math
+        d = node.getDistance(base.cam)
+        if d > self.cutoffDistance:
+            volume = 0
+        else:
+            volume = (1 - (d / self.cutoffDistance))
+        return volume
+
+    def playSfx(self, sfx, looping = 0, interrupt = 1, volume = None, time = 0.0, node=None):
+        if sfx:
+            # 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)
+                else:
+                    finalVolume = 1
+                if volume is not None:
+                    finalVolume *= volume
+                sfx.setVolume(finalVolume)
+
+            # dont 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()
+        

+ 13 - 26
direct/src/showbase/ShowBase.py

@@ -23,6 +23,7 @@ import time
 import FSM
 import FSM
 import State
 import State
 import DirectObject
 import DirectObject
+import SfxPlayer
 
 
 __builtins__["FADE_SORT_INDEX"] = 1000
 __builtins__["FADE_SORT_INDEX"] = 1000
 __builtins__["NO_FADE_SORT_INDEX"] = 2000
 __builtins__["NO_FADE_SORT_INDEX"] = 2000
@@ -692,6 +693,7 @@ class ShowBase(DirectObject.DirectObject):
             extraSfxManager.setActive(self.sfxActive)
             extraSfxManager.setActive(self.sfxActive)
 
 
     def createBaseAudioManagers(self):
     def createBaseAudioManagers(self):
+        self.sfxPlayer = SfxPlayer.SfxPlayer()
         sfxManager = AudioManager.createAudioManager()
         sfxManager = AudioManager.createAudioManager()
         self.addSfxManager(sfxManager)
         self.addSfxManager(sfxManager)
 
 
@@ -748,36 +750,21 @@ class ShowBase(DirectObject.DirectObject):
             self.musicManager.setActive(self.musicActive)
             self.musicManager.setActive(self.musicActive)
         self.notify.debug("Enabling audio")
         self.notify.debug("Enabling audio")
 
 
+    # This function should only be in the loader but is here for
+    # backwards compatibility. Please do not add code here, add
+    # it to the loader.
     def loadSfx(self, name):
     def loadSfx(self, name):
-        # should return a valid sound obj even if soundMgr is invalid
-        sound = None
-        if (name):
-            # showbase-created sfxManager should always be at front of list
-            sound=self.sfxManagerList[0].getSound(name)
-        if sound == None:
-            self.notify.warning("Could not load sound file %s." % name)
-        return sound
-
+        return self.loader.loadSfx(name)
 
 
+    # This function should only be in the loader but is here for
+    # backwards compatibility. Please do not add code here, add
+    # it to the loader.
     def loadMusic(self, name):
     def loadMusic(self, name):
-        # should return a valid sound obj even if musicMgr is invalid
-        sound = None
-        if (name):
-            sound=self.musicManager.getSound(name)
-        if sound == None:
-            self.notify.warning("Could not load music file %s." % name)
-        return sound
-
-    def playSfx(self, sfx, looping = 0, interrupt = 1, volume = None, time = 0.0):
-        if sfx:
-            if volume != None:
-                sfx.setVolume(volume)
+        return self.loader.loadMusic(name)
 
 
-            # dont 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()
+    def playSfx(self, sfx, looping = 0, interrupt = 1, volume = None, time = 0.0, node = None):
+        # This goes through a special player for potential localization
+        return self.sfxPlayer.playSfx(sfx, looping, interrupt, volume, time, node)
 
 
     def playMusic(self, music, looping = 0, interrupt = 1, volume = None, time = 0.0):
     def playMusic(self, music, looping = 0, interrupt = 1, volume = None, time = 0.0):
         if music:
         if music: