using System; using System.Threading; using System.Collections.Generic; using System.Xml.Linq; using OpenTK.Audio.OpenAL; using Microsoft.Xna.Framework; using System.Linq; using System.IO; namespace Barotrauma.Sounds { public class SoundManager : IDisposable { public const int SOURCE_COUNT = 32; private IntPtr alcDevice; private OpenTK.ContextHandle alcContext; private List alcCaptureDeviceNames; private uint[] alSources; private List loadedSounds; private SoundChannel[] playingChannels; private Thread streamingThread; private Vector3 listenerPosition; public Vector3 ListenerPosition { get { return listenerPosition; } set { listenerPosition = value; AL.Listener(ALListener3f.Position,value.X,value.Y,value.Z); ALError alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to set listener position: " + AL.GetErrorString(alError)); } } } private float[] listenerOrientation; public Vector3 ListenerTargetVector { get { return new Vector3(listenerOrientation[0], listenerOrientation[1], listenerOrientation[2]); } set { listenerOrientation[0] = value.X; listenerOrientation[1] = value.Y; listenerOrientation[2] = value.Z; AL.Listener(ALListenerfv.Orientation, ref listenerOrientation); ALError alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to set listener target vector: " + AL.GetErrorString(alError)); } } } public Vector3 ListenerUpVector { get { return new Vector3(listenerOrientation[3], listenerOrientation[4], listenerOrientation[5]); } set { listenerOrientation[3] = value.X; listenerOrientation[4] = value.Y; listenerOrientation[5] = value.Z; AL.Listener(ALListenerfv.Orientation, ref listenerOrientation); ALError alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to set listener up vector: " + AL.GetErrorString(alError)); } } } private float listenerGain; public float ListenerGain { get { return listenerGain; } set { listenerGain = value; AL.Listener(ALListenerf.Gain, listenerGain); ALError alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to set listener gain: " + AL.GetErrorString(alError)); } } } public int LoadedSoundCount { get { return loadedSounds.Count; } } public int UniqueLoadedSoundCount { get { return loadedSounds.Select(s => s.Filename).Distinct().Count(); } } private Dictionary> categoryModifiers; public SoundManager() { loadedSounds = new List(); playingChannels = new SoundChannel[SOURCE_COUNT]; streamingThread = null; categoryModifiers = null; alcDevice = Alc.OpenDevice(null); if (alcDevice == null) { throw new Exception("Failed to open an ALC device!"); } AlcError alcError = Alc.GetError(alcDevice); if (alcError != AlcError.NoError) { //The audio device probably wasn't ready, this happens quite often //Just wait a while and try again Thread.Sleep(100); alcDevice = Alc.OpenDevice(null); alcError = Alc.GetError(alcDevice); if (alcError != AlcError.NoError) { throw new Exception("Error initializing ALC device: " + alcError.ToString()); } } int[] alcContextAttrs = new int[] { }; alcContext = Alc.CreateContext(alcDevice, alcContextAttrs); if (alcContext == null) { throw new Exception("Failed to create an ALC context! (error code: "+Alc.GetError(alcDevice).ToString()+")"); } if (!Alc.MakeContextCurrent(alcContext)) { throw new Exception("Failed to assign the current ALC context! (error code: " + Alc.GetError(alcDevice).ToString() + ")"); } alcError = Alc.GetError(alcDevice); if (alcError != AlcError.NoError) { throw new Exception("Error after assigning ALC context: " + alcError.ToString()); } ALError alError = ALError.NoError; alSources = new uint[SOURCE_COUNT]; for (int i=0;i(Alc.GetString(IntPtr.Zero, AlcGetStringList.CaptureDeviceSpecifier)); } else { alcCaptureDeviceNames = null; } listenerOrientation = new float[6]; ListenerPosition = Vector3.Zero; ListenerTargetVector = new Vector3(0.0f, 0.0f, 1.0f); ListenerUpVector = new Vector3(0.0f, -1.0f, 0.0f); } public Sound LoadSound(string filename, bool stream = false) { if (!File.Exists(filename)) { throw new FileNotFoundException("Sound file \"" + filename + "\" doesn't exist!"); } Sound newSound = new OggSound(this, filename, stream); loadedSounds.Add(newSound); return newSound; } public Sound LoadSound(XElement element, bool stream = false) { string filePath = element.GetAttributeString("file", ""); if (!File.Exists(filePath)) { throw new FileNotFoundException("Sound file \"" + filePath + "\" doesn't exist!"); } var newSound = new OggSound(this, filePath, stream); if (newSound != null) { newSound.BaseGain = element.GetAttributeFloat("volume", 1.0f); float range = element.GetAttributeFloat("range", 1000.0f); newSound.BaseNear = range * 0.4f; newSound.BaseFar = range; } loadedSounds.Add(newSound); return newSound; } public SoundChannel GetSoundChannelFromIndex(int ind) { if (ind < 0 || ind >= SOURCE_COUNT) return null; return playingChannels[ind]; } public uint GetSourceFromIndex(int ind) { if (ind < 0 || ind >= SOURCE_COUNT) return 0; if (!AL.IsSource(alSources[ind])) { throw new Exception("alSources[" + ind.ToString() + "] is invalid!"); } return alSources[ind]; } public int AssignFreeSourceToChannel(SoundChannel newChannel) { lock (playingChannels) { //remove a channel that has stopped //or hasn't even been assigned for (int i = 0; i < SOURCE_COUNT; i++) { if (playingChannels[i]==null || !playingChannels[i].IsPlaying) { if (playingChannels[i]!=null) playingChannels[i].Dispose(); playingChannels[i] = newChannel; if (!AL.IsSource(alSources[i])) { throw new Exception("alSources[" + i.ToString() + "] is invalid!"); } return i; } } //we couldn't get a free source to assign to this channel! return -1; } } #if DEBUG public void DebugSource(int ind) { for (int i=0;i>(); if (!categoryModifiers.ContainsKey(category)) { categoryModifiers.Add(category, new Pair(gain, false)); } else { categoryModifiers[category].First = gain; } for (int i = 0; i < SOURCE_COUNT; i++) { if (playingChannels[i] != null && playingChannels[i].IsPlaying) { playingChannels[i].Gain = playingChannels[i].Gain; //force all channels to recalculate their gain } } } public float GetCategoryGainMultiplier(string category) { category = category.ToLower(); if (categoryModifiers == null || !categoryModifiers.ContainsKey(category)) return 1.0f; return categoryModifiers[category].First; } public void SetCategoryMuffle(string category,bool muffle) { category = category.ToLower(); if (categoryModifiers == null) categoryModifiers = new Dictionary>(); if (!categoryModifiers.ContainsKey(category)) { categoryModifiers.Add(category, new Pair(1.0f, muffle)); } else { categoryModifiers[category].Second = muffle; } for (int i = 0; i < SOURCE_COUNT; i++) { if (playingChannels[i] != null && playingChannels[i].IsPlaying) { if (playingChannels[i].Category.ToLower() == category) playingChannels[i].Muffled = muffle; } } } public bool GetCategoryMuffle(string category) { category = category.ToLower(); if (categoryModifiers == null || !categoryModifiers.ContainsKey(category)) return false; return categoryModifiers[category].Second; } public void InitStreamThread() { if (streamingThread == null || streamingThread.ThreadState.HasFlag(ThreadState.Stopped)) { streamingThread = new Thread(UpdateStreaming) { IsBackground = true //this should kill the thread if the game crashes }; streamingThread.Start(); } } void UpdateStreaming() { bool areStreamsPlaying = true; while (areStreamsPlaying) { areStreamsPlaying = false; lock (playingChannels) { for (int i=0;i= 0; i--) { loadedSounds[i].Dispose(); } for (int i = 0; i < SOURCE_COUNT; i++) { AL.DeleteSource(ref alSources[i]); ALError alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to delete alSources[" + i.ToString() + "]: " + AL.GetErrorString(alError)); } } if (!Alc.MakeContextCurrent(OpenTK.ContextHandle.Zero)) { throw new Exception("Failed to detach the current ALC context! (error code: " + Alc.GetError(alcDevice).ToString() + ")"); } Alc.DestroyContext(alcContext); if (!Alc.CloseDevice(alcDevice)) { throw new Exception("Failed to close ALC device!"); } } } }