Audio3DManager.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. """Undocumented Module"""
  2. __all__ = ['Audio3DManager']
  3. from pandac.PandaModules import Vec3, VBase3
  4. from direct.task import Task
  5. #
  6. class Audio3DManager:
  7. def __init__(self, audio_manager, listener_target = None, root = None,
  8. taskPriority = 51):
  9. self.audio_manager = audio_manager
  10. self.listener_target = listener_target
  11. if (root==None):
  12. self.root = render
  13. else:
  14. self.root = root
  15. self.sound_dict = {}
  16. self.vel_dict = {}
  17. self.listener_vel = VBase3(0, 0, 0)
  18. taskMgr.add(self.update, "Audio3DManager-updateTask", taskPriority)
  19. def loadSfx(self, name):
  20. """
  21. Use Audio3DManager.loadSfx to load a sound with 3D positioning enabled
  22. """
  23. sound = None
  24. if (name):
  25. sound=self.audio_manager.getSound(name, 1)
  26. return sound
  27. def setDistanceFactor(self, factor):
  28. """
  29. Control the scale that sets the distance units for 3D spacialized audio.
  30. Default is 1.0 which is adjust in panda to be feet.
  31. When you change this, don't forget that this effects the scale of setSoundMinDistance
  32. """
  33. self.audio_manager.audio3dSetDistanceFactor(factor)
  34. def getDistanceFactor(self):
  35. """
  36. Control the scale that sets the distance units for 3D spacialized audio.
  37. Default is 1.0 which is adjust in panda to be feet.
  38. """
  39. return self.audio_manager.audio3dGetDistanceFactor()
  40. def setDopplerFactor(self, factor):
  41. """
  42. Control the presence of the Doppler effect. Default is 1.0
  43. Exaggerated Doppler, use >1.0
  44. Diminshed Doppler, use <1.0
  45. """
  46. self.audio_manager.audio3dSetDopplerFactor(factor)
  47. def getDopplerFactor(self):
  48. """
  49. Control the presence of the Doppler effect. Default is 1.0
  50. Exaggerated Doppler, use >1.0
  51. Diminshed Doppler, use <1.0
  52. """
  53. return self.audio_manager.audio3dGetDopplerFactor()
  54. def setDropOffFactor(self, factor):
  55. """
  56. Exaggerate or diminish the effect of distance on sound. Default is 1.0
  57. Valid range is 0 to 10
  58. Faster drop off, use >1.0
  59. Slower drop off, use <1.0
  60. """
  61. self.audio_manager.audio3dSetDropOffFactor(factor)
  62. def getDropOffFactor(self):
  63. """
  64. Exaggerate or diminish the effect of distance on sound. Default is 1.0
  65. Valid range is 0 to 10
  66. Faster drop off, use >1.0
  67. Slower drop off, use <1.0
  68. """
  69. return self.audio_manager.audio3dGetDropOffFactor()
  70. def setSoundMinDistance(self, sound, dist):
  71. """
  72. Controls the distance (in units) that this sound begins to fall off.
  73. Also affects the rate it falls off.
  74. Default is 3.28 (in feet, this is 1 meter)
  75. Don't forget to change this when you change the DistanceFactor
  76. """
  77. sound.set3dMinDistance(dist)
  78. def getSoundMinDistance(self, sound):
  79. """
  80. Controls the distance (in units) that this sound begins to fall off.
  81. Also affects the rate it falls off.
  82. Default is 3.28 (in feet, this is 1 meter)
  83. """
  84. return sound.get3dMinDistance()
  85. def setSoundMaxDistance(self, sound, dist):
  86. """
  87. Controls the maximum distance (in units) that this sound stops falling off.
  88. The sound does not stop at that point, it just doesn't get any quieter.
  89. You should rarely need to adjust this.
  90. Default is 1000000000.0
  91. """
  92. sound.set3dMaxDistance(dist)
  93. def getSoundMaxDistance(self, sound):
  94. """
  95. Controls the maximum distance (in units) that this sound stops falling off.
  96. The sound does not stop at that point, it just doesn't get any quieter.
  97. You should rarely need to adjust this.
  98. Default is 1000000000.0
  99. """
  100. return sound.get3dMaxDistance()
  101. def setSoundVelocity(self, sound, velocity):
  102. """
  103. Set the velocity vector (in units/sec) of the sound, for calculating doppler shift.
  104. This is relative to the sound root (probably render).
  105. Default: VBase3(0, 0, 0)
  106. """
  107. if not isinstance(velocity, VBase3):
  108. raise TypeError, "Invalid argument 1, expected <VBase3>"
  109. self.vel_dict[sound]=velocity
  110. def setSoundVelocityAuto(self, sound):
  111. """
  112. If velocity is set to auto, the velocity will be determined by the
  113. previous position of the object the sound is attached to and the frame dt.
  114. Make sure if you use this method that you remember to clear the previous
  115. transformation between frames.
  116. """
  117. self.vel_dict[sound]=None
  118. def getSoundVelocity(self, sound):
  119. """
  120. Get the velocity of the sound.
  121. """
  122. if (self.vel_dict.has_key(sound)):
  123. vel = self.vel_dict[sound]
  124. if (vel!=None):
  125. return vel
  126. else:
  127. for known_object in self.sound_dict.keys():
  128. if self.sound_dict[known_object].count(sound):
  129. return known_object.getPosDelta(self.root)/globalClock.getDt()
  130. return VBase3(0, 0, 0)
  131. def setListenerVelocity(self, velocity):
  132. """
  133. Set the velocity vector (in units/sec) of the listener, for calculating doppler shift.
  134. This is relative to the sound root (probably render).
  135. Default: VBase3(0, 0, 0)
  136. """
  137. if not isinstance(velocity, VBase3):
  138. raise TypeError, "Invalid argument 0, expected <VBase3>"
  139. self.listener_vel=velocity
  140. def setListenerVelocityAuto(self):
  141. """
  142. If velocity is set to auto, the velocity will be determined by the
  143. previous position of the object the listener is attached to and the frame dt.
  144. Make sure if you use this method that you remember to clear the previous
  145. transformation between frames.
  146. """
  147. self.listener_vel = None
  148. def getListenerVelocity(self):
  149. """
  150. Get the velocity of the listener.
  151. """
  152. if (self.listener_vel!=None):
  153. return self.listener_vel
  154. elif (self.listener_target!=None):
  155. return self.listener_target.getPosDelta(self.root)/globalClock.getDt()
  156. else:
  157. return VBase3(0, 0, 0)
  158. def attachSoundToObject(self, sound, object):
  159. """
  160. Sound will come from the location of the object it is attached to
  161. """
  162. # sound is an AudioSound
  163. # object is any Panda object with coordinates
  164. for known_object in self.sound_dict.keys():
  165. if self.sound_dict[known_object].count(sound):
  166. # This sound is already attached to something
  167. #return 0
  168. # detach sound
  169. self.sound_dict[known_object].remove(sound)
  170. if len(self.sound_dict[known_object]) == 0:
  171. # if there are no other sounds, don't track
  172. # the object any more
  173. del self.sound_dict[known_object]
  174. if not self.sound_dict.has_key(object):
  175. self.sound_dict[object] = []
  176. self.sound_dict[object].append(sound)
  177. return 1
  178. def detachSound(self, sound):
  179. """
  180. sound will no longer have it's 3D position updated
  181. """
  182. for known_object in self.sound_dict.keys():
  183. if self.sound_dict[known_object].count(sound):
  184. self.sound_dict[known_object].remove(sound)
  185. if len(self.sound_dict[known_object]) == 0:
  186. # if there are no other sounds, don't track
  187. # the object any more
  188. del self.sound_dict[known_object]
  189. return 1
  190. return 0
  191. def getSoundsOnObject(self, object):
  192. """
  193. returns a list of sounds attached to an object
  194. """
  195. if not self.sound_dict.has_key(object):
  196. return []
  197. sound_list = []
  198. sound_list.extend(self.sound_dict[object])
  199. return sound_list
  200. def attachListener(self, object):
  201. """
  202. Sounds will be heard relative to this object. Should probably be the camera.
  203. """
  204. self.listener_target = object
  205. return 1
  206. def detachListener(self):
  207. """
  208. Sounds will be heard relative to the root, probably render.
  209. """
  210. self.listener_target = None
  211. return 1
  212. def update(self, task=None):
  213. """
  214. Updates position of sounds in the 3D audio system. Will be called automatically
  215. in a task.
  216. """
  217. # Update the positions of all sounds based on the objects
  218. # to which they are attached
  219. # The audio manager is not active so do nothing
  220. if hasattr(self.audio_manager, "getActive"):
  221. if self.audio_manager.getActive()==0:
  222. return Task.cont
  223. for known_object in self.sound_dict.keys():
  224. tracked_sound = 0
  225. while tracked_sound < len(self.sound_dict[known_object]):
  226. sound = self.sound_dict[known_object][tracked_sound]
  227. pos = known_object.getPos(self.root)
  228. vel = self.getSoundVelocity(sound)
  229. sound.set3dAttributes(pos[0], pos[1], pos[2], vel[0], vel[1], vel[2])
  230. tracked_sound += 1
  231. # Update the position of the listener based on the object
  232. # to which it is attached
  233. if self.listener_target:
  234. pos = self.listener_target.getPos(self.root)
  235. forward = self.listener_target.getRelativeVector(self.root, Vec3.forward())
  236. up = self.listener_target.getRelativeVector(self.root, Vec3.up())
  237. vel = self.getListenerVelocity()
  238. self.audio_manager.audio3dSetListenerAttributes(pos[0], pos[1], pos[2], vel[0], vel[1], vel[2], forward[0], forward[1], forward[2], up[0], up[1], up[2])
  239. else:
  240. self.audio_manager.audio3dSetListenerAttributes(0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1)
  241. return Task.cont
  242. def disable(self):
  243. """
  244. Detaches any existing sounds and removes the update task
  245. """
  246. taskMgr.remove("Audio3DManager-updateTask")
  247. self.detachListener()
  248. for object in self.sound_dict.keys():
  249. for sound in self.sound_dict[object]:
  250. self.detachSound(sound)