Browse Source

improvemnts to fmod positional audio handling

Philip Saltzman 20 years ago
parent
commit
f22866dc67

+ 271 - 80
direct/src/showbase/Audio3DManager.py

@@ -1,80 +1,271 @@
-import pandac.AudioSound
-import pandac.AudioManager
-
-class Audio3DManager:
-
-    def __init__(self, audio_manager, listener_target = None):
-        self.audio_manager = audio_manager
-        self.listener_target = listener_target
-
-        self.sound_dict = {}        
-
-
-    def attachSoundToObject(self, sound, object):
-        # sound is an AudioSound
-        # object is any Panda object with coordinates
-        for known_object in self.sound_dict.keys():
-            if self.sound_dict[known_object].count(sound):
-                # This sound is already attached to something
-                return 0
-                
-        if not self.sound_dict.has_key(object):
-            self.sound_dict[object] = []
-            
-        self.sound_dict[object].append(sound)
-        return 1
-
-
-    def detachSound(self, sound):
-        for known_object in self.sound_dict.keys():
-            if self.sound_dict[known_object].count(sound):
-                self.sound_dict[known_object].remove(sound)
-                if len(self.sound_dict[known_object]) == 0:
-                    # if there are no other sounds, don't track
-                    # the object any more
-                    del self.sound_dict[known_object]
-                return 1
-        return 0
-
-
-    def getSoundsOnObject(self, object):
-        if not self.sound_dict.has_key(object):
-            return []
-        sound_list = []
-        sound_list.extend(self.sound_dict[object])
-        return sound_list
-    
-
-    def attachListener(self, object):
-        self.listener_target = object
-        return 1
-
-
-    def detachListener(self):
-        self.listener_target = None
-        return 1
-
-
-    def update(self):
-        # Update the positions of all sounds based on the objects
-        # to which they are attached
-        for known_object in self.sound_dict.keys():
-            tracked_sound = 0
-            while tracked_sound < len(self.sound_dict[known_object]):
-                sound = self.sound_dict[known_object][tracked_sound]
-                if self.listener_target:
-                    pos = known_object.getPos(self.listener_target)
-                else:
-                    pos = known_object.getPos()
-                sound.set3dAttributes(pos[0], pos[1], pos[2], 0,0,0)
-                tracked_sound += 1
-
-        # Update the position of the listener based on the object
-        # to which it is attached
-        if self.listener_target:
-            pos = self.listener_target.getPos()
-            self.audio_manager.audio3dSetListenerAttributes(pos[0], pos[1], pos[2], 0,0,0, 0,1,0, 0,0,1)
-        else:
-            self.audio_manager.audio3dSetListenerAttributes(0,0,0, 0,0,0, 0,1,0, 0,0,1)
-        self.audio_manager.audio3dUpdate()
-        return 1
+from pandac.VBase3 import VBase3
+from direct.task import Task
+
+class Audio3DManager:
+
+    def __init__(self, audio_manager, listener_target = None, root = None,
+                 taskPriority = 51):
+        self.audio_manager = audio_manager
+        self.listener_target = listener_target
+        
+        if (root==None):
+            self.root = render
+        else:
+            self.root = root
+
+        self.sound_dict = {}
+        self.vel_dict = {}
+        self.listener_vel = VBase3(0,0,0)
+        
+        taskMgr.add(self.update, "Audio3DManager-updateTask", taskPriority)
+
+    def loadSfx(self, name):
+        """
+        Use Audio3DManager.loadSfx to load a sound with 3D positioning enabled
+        """
+        sound = None
+        if (name):
+            sound=self.audio_manager.getSound(name,1)
+        return sound
+
+    def setDistanceFactor(self,factor):
+        """
+        Control the scale that sets the distance units for 3D spacialized audio.
+        Default is 1.0 which is adjust in panda to be feet.
+        """
+        self.audio_manager.audio3dSetDistanceFactor(factor)
+
+    def getDistanceFactor(self):
+        """
+        Control the scale that sets the distance units for 3D spacialized audio.
+        Default is 1.0 which is adjust in panda to be feet.
+        """
+        return self.audio_manager.audio3dGetDistanceFactor()
+
+    def setDopplerFactor(self,factor):
+        """
+        Control the presence of the Doppler effect. Default is 1.0
+        Exaggerated Doppler, use >1.0
+        Diminshed Doppler, use <1.0
+        """
+        self.audio_manager.audio3dSetDopplerFactor(factor)
+
+    def getDopplerFactor(self):
+        """
+        Control the presence of the Doppler effect. Default is 1.0
+        Exaggerated Doppler, use >1.0
+        Diminshed Doppler, use <1.0
+        """
+        return self.audio_manager.audio3dGetDopplerFactor()
+
+    def setDropOffFactor(self,factor):
+        """
+        Exaggerate or diminish the effect of distance on sound. Default is 1.0
+        Valid range is 0 to 10
+        Faster drop off, use >1.0
+        Slower drop off, use <1.0
+        """
+        self.audio_manager.audio3dSetDropOffFactor(factor)
+
+    def getDropOffFactor(self):
+        """
+        Exaggerate or diminish the effect of distance on sound. Default is 1.0
+        Valid range is 0 to 10
+        Faster drop off, use >1.0
+        Slower drop off, use <1.0
+        """
+        return self.audio_manager.audio3dGetDropOffFactor()
+
+    def setSoundMinDistance(self,sound,dist):
+        """
+        Controls the distance (in units) that this sound begins to fall off.
+        Also affects the rate it falls off.
+        Default is 1.0
+        Closer/Faster, <1.0
+        Farther/Slower, >1.0
+        """
+        sound.set3dMinDistance(dist)
+
+    def getSoundMinDistance(self,sound):
+        """
+        Controls the distance (in units) that this sound begins to fall off.
+        Also affects the rate it falls off.
+        Default is 1.0
+        Closer/Faster, <1.0
+        Farther/Slower, >1.0
+        """
+        return sound.get3dMinDistance()
+
+    def setSoundMaxDistance(self,sound,dist):
+        """
+        Controls the maximum distance (in units) that this sound stops falling off.
+        The sound does not stop at that point, it just doesn't get any quieter.
+        You should rarely need to adjust this.
+        Default is 1000000000.0
+        """
+        sound.set3dMaxDistance(dist)
+
+    def getSoundMaxDistance(self,sound):
+        """
+        Controls the maximum distance (in units) that this sound stops falling off.
+        The sound does not stop at that point, it just doesn't get any quieter.
+        You should rarely need to adjust this.
+        Default is 1000000000.0
+        """
+        return sound.get3dMaxDistance()
+
+    def setSoundVelocity(self,sound,velocity):
+        """
+        Set the velocity vector (in units/sec) of the sound, for calculating doppler shift.
+        This is relative to the sound root (probably render).
+        Default: VBase3(0,0,0)
+        """
+        if not isinstance(velocity, VBase3):
+            raise TypeError, "Invalid argument 1, expected <VBase3>"
+        self.vel_dict[sound]=velocity
+    
+    def setSoundVelocityAuto(self,sound):
+        """
+        If velocity is set to auto, the velocity will be determined by the
+        previous position of the object the sound is attached to and the frame dt.
+        Make sure if you use this method that you remember to clear the previous
+        transformation between frames.
+        """
+        self.vel_dict[sound]=None
+
+    def getSoundVelocity(self,sound):
+        """
+        Get the velocity of the sound.
+        """
+        if (self.vel_dict.has_key(sound)):
+            vel = self.vel_dict[sound]
+            if (vel!=None):
+                return vel
+            else:
+                for known_object in self.sound_dict.keys():
+                    if self.sound_dict[known_object].count(sound):
+                        return known_object.getPosDelta(self.root)/globalClock.getDt()
+        return VBase3(0,0,0)
+    
+    def setListenerVelocity(self,velocity):
+        """
+        Set the velocity vector (in units/sec) of the listener, for calculating doppler shift.
+        This is relative to the sound root (probably render).
+        Default: VBase3(0,0,0)
+        """
+        if not isinstance(velocity, VBase3):
+            raise TypeError, "Invalid argument 0, expected <VBase3>"
+        self.listener_vel=velocity
+
+    def setListenerVelocityAuto(self):
+        """
+        If velocity is set to auto, the velocity will be determined by the
+        previous position of the object the listener is attached to and the frame dt.
+        Make sure if you use this method that you remember to clear the previous
+        transformation between frames.
+        """
+        self.listener_vel = None
+    
+    def getListenerVelocity(self):
+        """
+        Get the velocity of the listener.
+        """
+        if (self.listener_vel!=None):
+            return self.listener_vel
+        elif (self.listener_target!=None):
+            return self.listener_target.getPosDelta(self.root)/globalClock.getDt()
+        else:
+            return VBase3(0,0,0)
+
+    def attachSoundToObject(self, sound, object):
+        """
+        Sound will come from the location of the object it is attached to
+        """
+        # sound is an AudioSound
+        # object is any Panda object with coordinates
+        for known_object in self.sound_dict.keys():
+            if self.sound_dict[known_object].count(sound):
+                # This sound is already attached to something
+                #return 0
+                # detach sound
+                self.sound_dict[known_object].remove(sound)
+                if len(self.sound_dict[known_object]) == 0:
+                    # if there are no other sounds, don't track
+                    # the object any more
+                    del self.sound_dict[known_object]
+                
+        if not self.sound_dict.has_key(object):
+            self.sound_dict[object] = []
+            
+        self.sound_dict[object].append(sound)
+        return 1
+
+
+    def detachSound(self, sound):
+        """
+        sound will no longer have it's 3D position updated
+        """
+        for known_object in self.sound_dict.keys():
+            if self.sound_dict[known_object].count(sound):
+                self.sound_dict[known_object].remove(sound)
+                if len(self.sound_dict[known_object]) == 0:
+                    # if there are no other sounds, don't track
+                    # the object any more
+                    del self.sound_dict[known_object]
+                return 1
+        return 0
+
+
+    def getSoundsOnObject(self, object):
+        """
+        returns a list of sounds attached to an object
+        """
+        if not self.sound_dict.has_key(object):
+            return []
+        sound_list = []
+        sound_list.extend(self.sound_dict[object])
+        return sound_list
+    
+
+    def attachListener(self, object):
+        """
+        Sounds will be heard relative to this object. Should probably be the camera.
+        """
+        self.listener_target = object
+        return 1
+
+
+    def detachListener(self):
+        """
+        Sounds will be heard relative to the root, probably render.
+        """
+        self.listener_target = None
+        return 1
+
+
+    def update(self,task=None):
+        """
+        Updates position of sounds in the 3D audio system. Will be called automatically
+        in a task.
+        """
+        # Update the positions of all sounds based on the objects
+        # to which they are attached
+        for known_object in self.sound_dict.keys():
+            tracked_sound = 0
+            while tracked_sound < len(self.sound_dict[known_object]):
+                sound = self.sound_dict[known_object][tracked_sound]
+                pos = known_object.getPos(self.root)
+                vel = self.getSoundVelocity(sound)
+                sound.set3dAttributes(pos[0], pos[1], pos[2], vel[0],vel[1],vel[2])
+                tracked_sound += 1
+
+        # Update the position of the listener based on the object
+        # to which it is attached
+        if self.listener_target:
+            pos = self.listener_target.getPos(self.root)
+            vel = self.getListenerVelocity()
+            self.audio_manager.audio3dSetListenerAttributes(pos[0], pos[1], pos[2], vel[0],vel[1],vel[2], 0,1,0, 0,0,1)
+        else:
+            self.audio_manager.audio3dSetListenerAttributes(0,0,0, 0,0,0, 0,1,0, 0,0,1)
+        self.audio_manager.audio3dUpdate()
+        return Task.cont

+ 3 - 1
panda/src/audio/audioManager.h

@@ -128,9 +128,10 @@ PUBLISHED:
                                                 float *fx, float *fy, float *fz,
                                                 float *ux, float *uy, float *uz);
  
-  // Control the "relative distance factor" for 3D spacialized audio. Default is 1.0
+  // Control the "relative scale that sets the distance factor" units for 3D spacialized audio. Default is 1.0
   // Fmod uses meters internally, so give a float in Units-per meter
   // Don't know what Miles uses.
+  // Default is 1.0 which is adjust in panda to be feet.
   virtual void audio_3d_set_distance_factor(float factor);
   virtual float audio_3d_get_distance_factor() const;
 
@@ -141,6 +142,7 @@ PUBLISHED:
   virtual float audio_3d_get_doppler_factor() const;
 
   // Exaggerate or diminish the effect of distance on sound. Default is 1.0
+  // Valid range is 0 to 10
   // Faster drop off, use >1.0
   // Slower drop off, use <1.0
   virtual void audio_3d_set_drop_off_factor(float factor);

+ 22 - 0
panda/src/audio/audioSound.cxx

@@ -50,3 +50,25 @@ void AudioSound::
 get_3d_attributes(float *px, float *py, float *pz, float *vx, float *vy, float *vz) {
   // Intentionally blank.
 }
+
+void AudioSound::
+set_3d_min_distance(float dist) {
+  // Intentionally blank.
+}
+
+float AudioSound::
+get_3d_min_distance() const {
+  // Intentionally blank.
+  return 0.0f;
+}
+
+void AudioSound::
+set_3d_max_distance(float dist) {
+  // Intentionally blank.
+}
+
+float AudioSound::
+get_3d_max_distance() const {
+  // Intentionally blank.
+  return 0.0f;
+}

+ 15 - 0
panda/src/audio/audioSound.h

@@ -104,6 +104,21 @@ PUBLISHED:
                                  float *vx, float *vy, float *vz);
 
 
+  // Controls the distance (in units) that this sound begins to fall off.
+  // Also affects the rate it falls off.
+  // Default is 1.0
+  // Closer/Faster, <1.0
+  // Farther/Slower, >1.0
+  virtual void set_3d_min_distance(float dist);
+  virtual float get_3d_min_distance() const;
+
+  // Controls the maximum distance (in units) that this sound stops falling off.
+  // The sound does not stop at that point, it just doesn't get any quieter.
+  // You should rarely need to adjust this.
+  // Default is 1000000000.0
+  virtual void set_3d_max_distance(float dist);
+  virtual float get_3d_max_distance() const;
+
   enum SoundStatus { BAD, READY, PLAYING };
   virtual SoundStatus status() const = 0;
 

+ 18 - 0
panda/src/audio/nullAudioSound.cxx

@@ -118,6 +118,24 @@ void NullAudioSound::get_3d_attributes(float *px, float *py, float *pz, float *v
   // Intentionally blank.
 }
 
+void NullAudioSound::set_3d_min_distance(float dist) {
+  // Intentionally blank.
+}
+
+float NullAudioSound::get_3d_min_distance() const {
+  // Intentionally blank.
+  return 0.0f;
+}
+
+void NullAudioSound::set_3d_max_distance(float dist) {
+  // Intentionally blank.
+}
+
+float NullAudioSound::get_3d_max_distance() const {
+  // Intentionally blank.
+  return 0.0f;
+}
+
 AudioSound::SoundStatus NullAudioSound::status() const {
   return AudioSound::READY; 
 }

+ 4 - 0
panda/src/audio/nullAudioSound.h

@@ -64,6 +64,10 @@ public:
 
   void set_3d_attributes(float px, float py, float pz, float vx, float vy, float vz);
   void get_3d_attributes(float *px, float *py, float *pz, float *vx, float *vy, float *vz);
+  void set_3d_min_distance(float dist);
+  float get_3d_min_distance() const;
+  void set_3d_max_distance(float dist);
+  float get_3d_max_distance() const;
   
   AudioSound::SoundStatus status() const;
 

+ 13 - 14
panda/src/audiotraits/fmodAudioManager.cxx

@@ -59,9 +59,9 @@ FmodAudioManager() {
   _listener_vel[0]     = 0.0f; _listener_vel[1]     = 0.0f;     _listener_vel[2] = 0.0f;
   _listener_forward[0] = 0.0f; _listener_forward[1] = 1.0f; _listener_forward[2] = 0.0f;
   _listener_up[0]      = 0.0f; _listener_up[1]      = 0.0f;      _listener_up[2] = 1.0f;
-  _distance_factor     = audio_distance_factor;
-  _doppler_factor      = audio_doppler_factor;
-  _drop_off_factor     = audio_drop_off_factor;
+  _distance_factor     = .3048f;
+  _doppler_factor      = 1.0f;
+  _drop_off_factor     = 1.0f;
 
   _cache_limit = audio_cache_limit;
   _concurrent_sound_limit = 0;
@@ -642,15 +642,6 @@ stop_all_sounds() {
 void FmodAudioManager::
 audio_3d_update() {
     audio_debug("FmodAudioManager::audio_3d_update()");
-    //convert panda coordinates to fmod coordinates
-    float fmod_pos [] = {_listener_pos[0], _listener_pos[2], _listener_pos[1]};
-    float fmod_vel [] = {_listener_vel[0], _listener_vel[2], _listener_vel[1]};
-    float fmod_forward [] = {_listener_forward[0], _listener_forward[2], _listener_forward[1]};
-    float fmod_up [] = {_listener_up[0], _listener_up[2], _listener_up[1]};
-
-    FSOUND_3D_Listener_SetAttributes(fmod_pos, fmod_vel,
-                                         fmod_forward[0], fmod_forward[1], fmod_forward[2],
-                                         fmod_up[0], fmod_up[1], fmod_up[2]);
     FSOUND_Update();
 }
 
@@ -668,7 +659,15 @@ audio_3d_set_listener_attributes(float px, float py, float pz, float vx, float v
     _listener_forward[0] = fx; _listener_forward[1] = fy; _listener_forward[2] = fz;
     _listener_up[0]      = ux; _listener_up[1]      = uy; _listener_up[2]      = uz;
 
-    //FSOUND_3D_Listener_SetAttributes(_listener_pos, _listener_vel, fx, fz, fy, ux, uz, uy);
+    //convert panda coordinates to fmod coordinates
+    float fmod_pos [] = {_listener_pos[0], _listener_pos[2], _listener_pos[1]};
+    float fmod_vel [] = {_listener_vel[0], _listener_vel[2], _listener_vel[1]};
+    float fmod_forward [] = {_listener_forward[0], _listener_forward[2], _listener_forward[1]};
+    float fmod_up [] = {_listener_up[0], _listener_up[2], _listener_up[1]};
+
+    FSOUND_3D_Listener_SetAttributes(fmod_pos, fmod_vel,
+                                         fmod_forward[0], fmod_forward[1], fmod_forward[2],
+                                         fmod_up[0], fmod_up[1], fmod_up[2]);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -715,7 +714,7 @@ audio_3d_set_distance_factor(float factor) {
     }
     if (_distance_factor != factor){
         _distance_factor = factor;
-        FSOUND_3D_SetDistanceFactor(_distance_factor);
+        FSOUND_3D_SetDistanceFactor(_distance_factor*3.28f); // convert from feet to meters
     }
 }
 

+ 61 - 0
panda/src/audiotraits/fmodAudioSound.cxx

@@ -79,6 +79,7 @@ FmodAudioSound(FmodAudioManager* manager, FSOUND_STREAM *audio_data,
     _active(true), _paused(false), _bExclusive(false),_channel(-1) {
   _pos[0] = 0.0f; _pos[1] = 0.0f; _pos[2] = 0.0f;
   _vel[0] = 0.0f; _vel[1] = 0.0f; _vel[2] = 0.0f;
+  _min_dist = 1.0f; _max_dist = 1000000000.0f;
   nassertv(!file_name.empty());
   nassertv(audio_data != NULL);
 
@@ -143,6 +144,12 @@ if (_bExclusive) {
       if(!FSOUND_3D_SetAttributes(_channel, fmod_pos, fmod_vel)) {
           audio_error("Unable to set 3d attributes for "<<_file_name<<"!");
       }
+
+      if(!FSOUND_3D_SetMinMaxDistance(_channel, _min_dist, _max_dist)) {
+        //Seems like the return value is documented incorrectly, so this error gets
+        //needlessly spammed
+        //audio_error("Unable to set 3d min/max distance for "<<_file_name<<"!");
+      }
   }
   // Set looping -- unimplemented
   
@@ -424,6 +431,13 @@ set_3d_attributes(float px, float py, float pz, float vx, float vy, float vz) {
     fmod_audio_debug("Set 3d position and velocity (px="<<px<<", py="<<py<<", pz="<<pz<<", vx="<<vx<<", vy="<<vy<<", vz="<<vz<<")");
     _pos[0] = px; _pos[1] = py; _pos[2] = pz;
     _vel[0] = vx; _vel[1] = vy; _vel[2] = vz;
+
+    if (FSOUND_Stream_GetMode(_audio) & FSOUND_HW3D) {
+        // Convert from Panda coordinates to Fmod coordinates
+        float fmod_pos [] = {_pos[0], _pos[2], _pos[1]};
+        float fmod_vel [] = {_vel[0], _vel[2], _vel[1]};
+        FSOUND_3D_SetAttributes(_channel, fmod_pos, fmod_vel);
+    }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -450,6 +464,53 @@ get_3d_attributes(float *px, float *py, float *pz, float *vx, float *vy, float *
     //FSOUND_3D_GetAttributes(_channel, pos, vel);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: FmodAudioSound::set_3d_min_distance
+//       Access: public
+//  Description: Set the distance that this sound begins to fall off. Also
+//               affects the rate it falls off.
+////////////////////////////////////////////////////////////////////
+void FmodAudioSound::set_3d_min_distance(float dist) {
+    fmod_audio_debug("Set 3d min distance (min="<<dist<<")");
+    _min_dist = dist;
+
+    if (FSOUND_Stream_GetMode(_audio) & FSOUND_HW3D) {
+        FSOUND_3D_SetMinMaxDistance(_channel, _min_dist,_max_dist);
+    }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FmodAudioSound::get_3d_min_distance
+//       Access: public
+//  Description: Get the distance that this sound begins to fall off
+////////////////////////////////////////////////////////////////////
+float FmodAudioSound::get_3d_min_distance() const {
+  return _min_dist;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FmodAudioSound::set_3d_max_distance
+//       Access: public
+//  Description: Set the distance that this sound stops falling off
+////////////////////////////////////////////////////////////////////
+void FmodAudioSound::set_3d_max_distance(float dist) {
+    fmod_audio_debug("Set 3d max distance (max="<<dist<<")");
+    _max_dist = dist;
+
+    if (FSOUND_Stream_GetMode(_audio) & FSOUND_HW3D) {
+        FSOUND_3D_SetMinMaxDistance(_channel, _min_dist,_max_dist);
+    }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FmodAudioSound::get_3d_max_distance
+//       Access: public
+//  Description: Get the distance that this sound stops falling off
+////////////////////////////////////////////////////////////////////
+float FmodAudioSound::get_3d_max_distance() const {
+  return _max_dist;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: FmodAudioSound::status
 //       Access: public

+ 6 - 0
panda/src/audiotraits/fmodAudioSound.h

@@ -85,6 +85,10 @@ public:
                          float vx, float vy, float vz);
   void get_3d_attributes(float *px, float *py, float *pz, 
                          float *vx, float *vy, float *vz);
+  void set_3d_min_distance(float dist);
+  float get_3d_min_distance() const;
+  void set_3d_max_distance(float dist);
+  float get_3d_max_distance() const;
   
   AudioSound::SoundStatus status() const;
 
@@ -101,6 +105,8 @@ private:
   float _balance; // -1..1
   float _pos [3];
   float _vel [3];
+  float _min_dist;
+  float _max_dist;
   unsigned long _loop_count;
   mutable float _length; // in seconds.
   bool _active;