AudioManager.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. using UnityEngine;
  2. using UnityEngine.Audio;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. #if UNITY_EDITOR
  6. using UnityEditor;
  7. using System;
  8. using System.Reflection;
  9. #endif
  10. namespace OVR
  11. {
  12. public enum PreloadSounds {
  13. Default, // default unity behavior
  14. Preload, // audio clips are forced to preload
  15. ManualPreload, // audio clips are forced to not preload, preloading must be done manually
  16. }
  17. public enum Fade
  18. {
  19. In,
  20. Out
  21. }
  22. [System.Serializable]
  23. public class SoundGroup {
  24. public SoundGroup( string name ) {
  25. this.name = name;
  26. }
  27. public SoundGroup() {
  28. mixerGroup = null;
  29. maxPlayingSounds = 0;
  30. preloadAudio = PreloadSounds.Default;
  31. volumeOverride = 1.0f;
  32. }
  33. public void IncrementPlayCount() {
  34. playingSoundCount = Mathf.Clamp( ++playingSoundCount, 0, maxPlayingSounds );
  35. }
  36. public void DecrementPlayCount() {
  37. playingSoundCount = Mathf.Clamp( --playingSoundCount, 0, maxPlayingSounds );
  38. }
  39. public bool CanPlaySound() {
  40. return ( maxPlayingSounds == 0 ) || ( playingSoundCount < maxPlayingSounds );
  41. }
  42. public string name = string.Empty;
  43. public SoundFX[] soundList = new SoundFX[0];
  44. public AudioMixerGroup mixerGroup = null; // default = AudioManager.defaultMixerGroup
  45. [Range(0,64)]
  46. public int maxPlayingSounds = 0; // default = 0, unlimited
  47. // TODO: this preload behavior is not yet implemented
  48. public PreloadSounds preloadAudio = PreloadSounds.Default; // default = true, audio clip data will be preloaded
  49. public float volumeOverride = 1.0f; // default = 1.0
  50. [HideInInspector]
  51. public int playingSoundCount = 0;
  52. }
  53. /*
  54. -----------------------
  55. AudioManager
  56. -----------------------
  57. */
  58. public partial class AudioManager : MonoBehaviour {
  59. [Tooltip("Make the audio manager persistent across all scene loads")]
  60. public bool makePersistent = true; // true = don't destroy on load
  61. [Tooltip("Enable the OSP audio plugin features")]
  62. public bool enableSpatializedAudio = true; // true = enable spatialized audio
  63. [Tooltip("Always play spatialized sounds with no reflections (Default)")]
  64. public bool enableSpatializedFastOverride = false; // true = disable spatialized reflections override
  65. [Tooltip("The audio mixer asset used for snapshot blends, etc.")]
  66. public AudioMixer audioMixer = null;
  67. [Tooltip( "The audio mixer group used for the pooled emitters" )]
  68. public AudioMixerGroup defaultMixerGroup = null;
  69. [Tooltip( "The audio mixer group used for the reserved pool emitter" )]
  70. public AudioMixerGroup reservedMixerGroup = null;
  71. [Tooltip( "The audio mixer group used for voice chat" )]
  72. public AudioMixerGroup voiceChatMixerGroup = null;
  73. [Tooltip("Log all PlaySound calls to the Unity console")]
  74. public bool verboseLogging = false; // true = log all PlaySounds
  75. [Tooltip("Maximum sound emitters")]
  76. public int maxSoundEmitters = 32; // total number of sound emitters created
  77. [Tooltip("Default volume for all sounds modulated by individual sound FX volumes")]
  78. public float volumeSoundFX = 1.0f; // user pref: volume of all sound FX
  79. [Tooltip("Sound FX fade time")]
  80. public float soundFxFadeSecs = 1.0f; // sound FX fade time
  81. public float audioMinFallOffDistance = 1.0f; // minimum falloff distance
  82. public float audioMaxFallOffDistance = 25.0f; // maximum falloff distance
  83. public SoundGroup[] soundGroupings = new SoundGroup[0];
  84. private Dictionary<string,SoundFX> soundFXCache = null;
  85. static private AudioManager theAudioManager = null;
  86. static private FastList<string> names = new FastList<string>();
  87. static private string[] defaultSound = new string[1] { "Default Sound" };
  88. static private SoundFX nullSound = new SoundFX();
  89. static private bool hideWarnings = false;
  90. static public bool enableSpatialization { get { return ( theAudioManager !=null ) ? theAudioManager.enableSpatializedAudio : false; } }
  91. static public AudioManager Instance { get { return theAudioManager; } }
  92. static public float NearFallOff { get { return theAudioManager.audioMinFallOffDistance; } }
  93. static public float FarFallOff { get { return theAudioManager.audioMaxFallOffDistance; } }
  94. static public AudioMixerGroup EmitterGroup { get { return theAudioManager.defaultMixerGroup; } }
  95. static public AudioMixerGroup ReservedGroup { get { return theAudioManager.reservedMixerGroup; } }
  96. static public AudioMixerGroup VoipGroup { get { return theAudioManager.voiceChatMixerGroup; } }
  97. /*
  98. -----------------------
  99. Awake()
  100. -----------------------
  101. */
  102. void Awake() {
  103. Init();
  104. }
  105. /*
  106. -----------------------
  107. OnDestroy()
  108. -----------------------
  109. */
  110. void OnDestroy() {
  111. // we only want the initialized audio manager instance cleaning up the sound emitters
  112. if ( theAudioManager == this ) {
  113. if ( soundEmitterParent != null ) {
  114. Destroy( soundEmitterParent );
  115. }
  116. }
  117. ///TODO - if you change scenes you'll want to call OnPreSceneLoad to detach the sound emitters
  118. ///from anything they might be parented to or they will get destroyed with that object
  119. ///there should only be one instance of the AudioManager across the life of the game/app
  120. ///GameManager.OnPreSceneLoad -= OnPreSceneLoad;
  121. }
  122. /*
  123. -----------------------
  124. Init()
  125. -----------------------
  126. */
  127. void Init() {
  128. if ( theAudioManager != null ) {
  129. if ( Application.isPlaying && ( theAudioManager != this ) ) {
  130. enabled = false;
  131. }
  132. return;
  133. }
  134. theAudioManager = this;
  135. ///TODO - if you change scenes you'll want to call OnPreSceneLoad to detach the sound emitters
  136. ///from anything they might be parented to or they will get destroyed with that object
  137. ///there should only be one instance of the AudioManager across the life of the game/app
  138. ///GameManager.OnPreSceneLoad += OnPreSceneLoad;
  139. // make sure the first one is a null sound
  140. nullSound.name = "Default Sound";
  141. // build the sound FX cache
  142. RebuildSoundFXCache();
  143. // create the sound emitters
  144. if ( Application.isPlaying ) {
  145. InitializeSoundSystem();
  146. if ( makePersistent && ( transform.parent == null ) ) {
  147. // don't destroy the audio manager on scene loads
  148. DontDestroyOnLoad( gameObject );
  149. }
  150. }
  151. #if UNITY_EDITOR
  152. Debug.Log( "[AudioManager] Initialized..." );
  153. #endif
  154. }
  155. /*
  156. -----------------------
  157. Update()
  158. -----------------------
  159. */
  160. void Update() {
  161. // update the free and playing lists
  162. UpdateFreeEmitters();
  163. }
  164. /*
  165. -----------------------
  166. RebuildSoundFXCache()
  167. -----------------------
  168. */
  169. void RebuildSoundFXCache() {
  170. // build the SoundFX dictionary for quick name lookups
  171. int count = 0;
  172. for ( int group = 0; group < soundGroupings.Length; group++ ) {
  173. count += soundGroupings[group].soundList.Length;
  174. }
  175. soundFXCache = new Dictionary<string,SoundFX>( count + 1 );
  176. // add the null sound
  177. soundFXCache.Add( nullSound.name, nullSound );
  178. // add the rest
  179. for ( int group = 0; group < soundGroupings.Length; group++ ) {
  180. for ( int i = 0; i < soundGroupings[group].soundList.Length; i++ ) {
  181. if ( soundFXCache.ContainsKey( soundGroupings[group].soundList[i].name ) ) {
  182. Debug.LogError( "ERROR: Duplicate Sound FX name in the audio manager: '" + soundGroupings[group].name + "' > '" + soundGroupings[group].soundList[i].name + "'" );
  183. } else {
  184. soundGroupings[group].soundList[i].Group = soundGroupings[group];
  185. soundFXCache.Add( soundGroupings[group].soundList[i].name, soundGroupings[group].soundList[i] );
  186. }
  187. }
  188. soundGroupings[group].playingSoundCount = 0;
  189. }
  190. }
  191. /*
  192. -----------------------
  193. FindSoundFX()
  194. -----------------------
  195. */
  196. static public SoundFX FindSoundFX( string name, bool rebuildCache = false ) {
  197. #if UNITY_EDITOR
  198. if ( theAudioManager == null ) {
  199. Debug.LogError( "ERROR: audio manager not yet initialized or created!" + " Time: " + Time.time );
  200. return null;
  201. }
  202. #endif
  203. if ( string.IsNullOrEmpty( name ) ) {
  204. return nullSound;
  205. }
  206. if ( rebuildCache ) {
  207. theAudioManager.RebuildSoundFXCache();
  208. }
  209. if ( !theAudioManager.soundFXCache.ContainsKey( name ) ) {
  210. #if DEBUG_BUILD || UNITY_EDITOR
  211. Debug.LogError( "WARNING: Missing Sound FX in cache: " + name );
  212. #endif
  213. return nullSound;
  214. }
  215. return theAudioManager.soundFXCache[name];
  216. }
  217. /*
  218. -----------------------
  219. FindAudioManager()
  220. -----------------------
  221. */
  222. static private bool FindAudioManager() {
  223. GameObject audioManagerObject = GameObject.Find( "AudioManager" );
  224. if ( ( audioManagerObject == null ) || ( audioManagerObject.GetComponent<AudioManager>() == null ) ) {
  225. if ( !hideWarnings ) {
  226. Debug.LogError( "[ERROR] AudioManager object missing from hierarchy!" );
  227. hideWarnings = true;
  228. }
  229. return false;
  230. } else {
  231. audioManagerObject.GetComponent<AudioManager>().Init();
  232. }
  233. return true;
  234. }
  235. /*
  236. -----------------------
  237. GetGameObject()
  238. -----------------------
  239. */
  240. static public GameObject GetGameObject() {
  241. if ( theAudioManager == null ) {
  242. if ( !FindAudioManager() ) {
  243. return null;
  244. }
  245. }
  246. return theAudioManager.gameObject;
  247. }
  248. /*
  249. -----------------------
  250. NameMinusGroup()
  251. strip off the sound group from the inspector dropdown
  252. -----------------------
  253. */
  254. static public string NameMinusGroup( string name ) {
  255. if ( name.IndexOf( "/" ) > -1 ) {
  256. return name.Substring( name.IndexOf( "/" ) + 1 );
  257. }
  258. return name;
  259. }
  260. /*
  261. -----------------------
  262. GetSoundFXNames()
  263. used by the inspector
  264. -----------------------
  265. */
  266. static public string[] GetSoundFXNames( string currentValue, out int currentIdx ) {
  267. currentIdx = 0;
  268. names.Clear();
  269. if ( theAudioManager == null ) {
  270. if ( !FindAudioManager() ) {
  271. return defaultSound;
  272. }
  273. }
  274. names.Add( nullSound.name );
  275. for ( int group = 0; group < theAudioManager.soundGroupings.Length; group++ ) {
  276. for ( int i = 0; i < theAudioManager.soundGroupings[group].soundList.Length; i++ ) {
  277. if ( string.Compare( currentValue, theAudioManager.soundGroupings[group].soundList[i].name, true ) == 0 ) {
  278. currentIdx = names.Count;
  279. }
  280. names.Add( theAudioManager.soundGroupings[group].name + "/" + theAudioManager.soundGroupings[group].soundList[i].name );
  281. }
  282. }
  283. //names.Sort( delegate( string s1, string s2 ) { return s1.CompareTo( s2 ); } );
  284. return names.ToArray();
  285. }
  286. #if UNITY_EDITOR
  287. /*
  288. -----------------------
  289. OnPrefabReimported()
  290. -----------------------
  291. */
  292. static public void OnPrefabReimported() {
  293. if ( theAudioManager != null ) {
  294. Debug.Log( "[AudioManager] Reimporting the sound FX cache." );
  295. theAudioManager.RebuildSoundFXCache();
  296. }
  297. }
  298. /*
  299. -----------------------
  300. PlaySound()
  301. used in the editor
  302. -----------------------
  303. */
  304. static public void PlaySound( string soundFxName ) {
  305. if ( theAudioManager == null ) {
  306. if ( !FindAudioManager() ) {
  307. return;
  308. }
  309. }
  310. SoundFX soundFX = FindSoundFX( soundFxName, true );
  311. if ( soundFX == null ) {
  312. return;
  313. }
  314. AudioClip clip = soundFX.GetClip();
  315. if ( clip != null ) {
  316. Assembly unityEditorAssembly = typeof(AudioImporter).Assembly;
  317. Type audioUtilClass = unityEditorAssembly.GetType("UnityEditor.AudioUtil");
  318. MethodInfo method = audioUtilClass.GetMethod(
  319. "PlayClip",
  320. BindingFlags.Static | BindingFlags.Public,
  321. null,
  322. new System.Type[] { typeof(AudioClip) },
  323. null );
  324. method.Invoke( null, new object[] { clip } );
  325. }
  326. }
  327. /*
  328. -----------------------
  329. IsSoundPlaying()
  330. used in the editor
  331. -----------------------
  332. */
  333. static public bool IsSoundPlaying( string soundFxName ) {
  334. if ( theAudioManager == null ) {
  335. if ( !FindAudioManager() ) {
  336. return false;
  337. }
  338. }
  339. SoundFX soundFX = FindSoundFX( soundFxName, true );
  340. if ( soundFX == null ) {
  341. return false;
  342. }
  343. AudioClip clip = soundFX.GetClip();
  344. if ( clip != null ) {
  345. Assembly unityEditorAssembly = typeof(AudioImporter).Assembly;
  346. Type audioUtilClass = unityEditorAssembly.GetType("UnityEditor.AudioUtil");
  347. MethodInfo method = audioUtilClass.GetMethod(
  348. "IsClipPlaying",
  349. BindingFlags.Static | BindingFlags.Public,
  350. null,
  351. new System.Type[] { typeof(AudioClip) },
  352. null );
  353. return Convert.ToBoolean( method.Invoke( null, new object[] { clip } ) );
  354. }
  355. return false;
  356. }
  357. /*
  358. -----------------------
  359. StopSound()
  360. used in the editor
  361. -----------------------
  362. */
  363. static public void StopSound(string soundFxName)
  364. {
  365. if (theAudioManager == null)
  366. {
  367. if (!FindAudioManager())
  368. {
  369. return;
  370. }
  371. }
  372. SoundFX soundFX = FindSoundFX(soundFxName, true);
  373. if (soundFX == null)
  374. {
  375. return;
  376. }
  377. AudioClip clip = soundFX.GetClip();
  378. if (clip != null)
  379. {
  380. Assembly unityEditorAssembly = typeof(AudioImporter).Assembly;
  381. Type audioUtilClass = unityEditorAssembly.GetType("UnityEditor.AudioUtil");
  382. MethodInfo method = audioUtilClass.GetMethod(
  383. "StopClip",
  384. BindingFlags.Static | BindingFlags.Public,
  385. null,
  386. new System.Type[] { typeof(AudioClip) },
  387. null);
  388. method.Invoke(null, new object[] { clip });
  389. }
  390. }
  391. #endif
  392. }
  393. } // namespace OVR