using System; using OpenTK.Audio.OpenAL; using Microsoft.Xna.Framework; using System.Collections.Generic; namespace Barotrauma.Sounds { public class SoundSourcePool : IDisposable { public uint[] ALSources { get; private set; } public SoundSourcePool(int sourceCount = SoundManager.SOURCE_COUNT) { ALError alError = ALError.NoError; ALSources = new uint[sourceCount]; for (int i = 0; i < sourceCount; i++) { AL.GenSource(out ALSources[i]); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Error generating alSource[" + i.ToString() + "]: " + AL.GetErrorString(alError)); } if (!AL.IsSource(ALSources[i])) { throw new Exception("Generated alSource[" + i.ToString() + "] is invalid!"); } AL.SourceStop(ALSources[i]); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Error stopping newly generated alSource[" + i.ToString() + "]: " + AL.GetErrorString(alError)); } AL.Source(ALSources[i], ALSourcef.MinGain, 0.0f); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Error setting min gain: " + AL.GetErrorString(alError)); } AL.Source(ALSources[i], ALSourcef.MaxGain, 1.0f); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Error setting max gain: " + AL.GetErrorString(alError)); } AL.Source(ALSources[i], ALSourcef.RolloffFactor, 1.0f); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Error setting rolloff factor: " + AL.GetErrorString(alError)); } } } public void Dispose() { for (int i = 0; i < ALSources.Length; 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)); } } ALSources = null; } } public class SoundChannel : IDisposable { private const int STREAM_BUFFER_SIZE = 65536; private short[] streamShortBuffer; private Vector3? position; public Vector3? Position { get { return position; } set { position = value; if (ALSourceIndex < 0) return; if (position != null) { uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex); AL.Source(alSource, ALSourceb.SourceRelative, false); ALError alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to enable source's relative flag: " + AL.GetErrorString(alError)); } AL.Source(alSource, ALSource3f.Position, position.Value.X, position.Value.Y, position.Value.Z); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to set source's position: " + AL.GetErrorString(alError)); } } else { uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex); AL.Source(alSource, ALSourceb.SourceRelative, true); ALError alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to disable source's relative flag: " + AL.GetErrorString(alError)); } AL.Source(alSource, ALSource3f.Position, 0.0f, 0.0f, 0.0f); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to reset source's position: " + AL.GetErrorString(alError)); } } } } private float near; public float Near { get { return near; } set { near = value; if (ALSourceIndex < 0) return; uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex); AL.Source(alSource, ALSourcef.ReferenceDistance, near); ALError alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to set source's reference distance: " + AL.GetErrorString(alError)); } } } private float far; public float Far { get { return far; } set { far = value; if (ALSourceIndex < 0) return; uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex); AL.Source(alSource, ALSourcef.MaxDistance, far); ALError alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to set source's max distance: " + AL.GetErrorString(alError)); } } } private float gain; public float Gain { get { return gain; } set { gain = Math.Max(Math.Min(value, 1.0f), 0.0f); if (ALSourceIndex < 0) return; uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex); float effectiveGain = gain; if (category != null) effectiveGain *= Sound.Owner.GetCategoryGainMultiplier(category); AL.Source(alSource, ALSourcef.Gain, effectiveGain); ALError alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to set source's gain: " + AL.GetErrorString(alError)); } } } private bool looping; public bool Looping { get { return looping; } set { looping = value; if (ALSourceIndex < 0) return; if (!IsStream) { uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex); AL.Source(alSource, ALSourceb.Looping, looping); ALError alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to set source's looping state: " + AL.GetErrorString(alError)); } } } } public bool FilledByNetwork { get; private set; } private int decayTimer; private bool muffled; public bool Muffled { get { return muffled; } set { if (muffled == value) return; muffled = value; if (ALSourceIndex < 0) return; if (!IsPlaying) return; if (!IsStream) { uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex); int playbackPos; AL.GetSource(alSource, ALGetSourcei.SampleOffset, out playbackPos); ALError alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to get source's playback position: " + AL.GetErrorString(alError)); } AL.SourceStop(alSource); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to stop source: " + AL.GetErrorString(alError)); } AL.BindBufferToSource(alSource,(uint)(muffled ? Sound.ALMuffledBuffer : Sound.ALBuffer)); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to bind buffer to source: " + AL.GetErrorString(alError)); } AL.SourcePlay(alSource); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to replay source: " + AL.GetErrorString(alError)); } AL.Source(alSource, ALSourcei.SampleOffset, playbackPos); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to reset playback position: " + AL.GetErrorString(alError)); } } } } private string category; public string Category { get { return category; } set { category = value; Gain = gain; } } public Sound Sound { get; private set; } public int ALSourceIndex { get; private set; } = -1; public bool IsStream { get; private set; } private int streamSeekPos; private bool startedPlaying; private bool reachedEndSample; private readonly uint[] streamBuffers; private readonly List emptyBuffers; private object mutex; public bool IsPlaying { get { if (ALSourceIndex < 0) return false; if (IsStream && !reachedEndSample) return true; bool playing = AL.GetSourceState(Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex)) == ALSourceState.Playing; ALError alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to determine playing state from source: " + AL.GetErrorString(alError)); } return playing; } } public SoundChannel(Sound sound, float gain, Vector3? position, float near, float far, string category, bool muffle = false) { Sound = sound; IsStream = sound.Stream; FilledByNetwork = sound is VoipSound; decayTimer = 0; streamSeekPos = 0; reachedEndSample = false; startedPlaying = true; mutex = new object(); lock (mutex) { if (sound.Owner.CountPlayingInstances(sound) < sound.MaxSimultaneousInstances) { ALSourceIndex = sound.Owner.AssignFreeSourceToChannel(this); } if (ALSourceIndex >= 0) { if (!IsStream) { AL.BindBufferToSource(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), 0); ALError alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to reset source buffer: " + AL.GetErrorString(alError)); } if (!AL.IsBuffer(sound.ALBuffer)) { throw new Exception(sound.Filename + " has an invalid buffer!"); } uint alBuffer = sound.Owner.GetCategoryMuffle(category) || muffle ? sound.ALMuffledBuffer : sound.ALBuffer; AL.BindBufferToSource(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), alBuffer); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to bind buffer to source (" + ALSourceIndex.ToString() + ":" + sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex) + "," + sound.ALBuffer.ToString() + "): " + AL.GetErrorString(alError)); } AL.SourcePlay(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex)); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to play source: " + AL.GetErrorString(alError)); } } else { AL.BindBufferToSource(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), (uint)sound.ALBuffer); ALError alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to reset source buffer: " + AL.GetErrorString(alError)); } AL.Source(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), ALSourceb.Looping, false); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to set stream looping state: " + AL.GetErrorString(alError)); } streamShortBuffer = new short[STREAM_BUFFER_SIZE]; streamBuffers = new uint[4]; emptyBuffers = new List(); for (int i = 0; i < 4; i++) { AL.GenBuffer(out streamBuffers[i]); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to generate stream buffers: " + AL.GetErrorString(alError)); } if (!AL.IsBuffer(streamBuffers[i])) { throw new Exception("Generated streamBuffer[" + i.ToString() + "] is invalid!"); } } Sound.Owner.InitStreamThread(); } } this.Position = position; this.Gain = gain; this.Looping = false; this.Near = near; this.Far = far; this.Category = category; } } public bool FadingOutAndDisposing; public void FadeOutAndDispose() { FadingOutAndDisposing = true; } public void Dispose() { lock (mutex) { if (ALSourceIndex >= 0) { AL.SourceStop(Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex)); ALError alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to stop source: " + AL.GetErrorString(alError)); } if (IsStream) { uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex); AL.SourceStop(alSource); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to stop streamed source: " + AL.GetErrorString(alError)); } int buffersToUnqueue = 0; int[] unqueuedBuffers = null; buffersToUnqueue = 0; AL.GetSource(alSource, ALGetSourcei.BuffersProcessed, out buffersToUnqueue); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to determine processed buffers from streamed source: " + AL.GetErrorString(alError)); } unqueuedBuffers = new int[buffersToUnqueue]; AL.SourceUnqueueBuffers((int)alSource, buffersToUnqueue, unqueuedBuffers); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to unqueue buffers from streamed source: " + AL.GetErrorString(alError)); } AL.BindBufferToSource(alSource, 0); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to reset buffer for streamed source: " + AL.GetErrorString(alError)); } for (int i = 0; i < 4; i++) { AL.DeleteBuffer(ref streamBuffers[i]); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to delete streamBuffers[" + i.ToString() + "] ("+streamBuffers[i].ToString()+"): " + AL.GetErrorString(alError)); } } reachedEndSample = true; } else { AL.BindBufferToSource(Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), 0); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to unbind buffer to non-streamed source: " + AL.GetErrorString(alError)); } } ALSourceIndex = -1; } } } public void UpdateStream() { if (!IsStream) throw new Exception("Called UpdateStream on a non-streamed sound channel!"); lock (mutex) { if (!reachedEndSample) { uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex); bool playing = AL.GetSourceState(alSource) == ALSourceState.Playing; ALError alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to determine playing state from streamed source: " + AL.GetErrorString(alError)); } int buffersToUnqueue = 0; int[] unqueuedBuffers = null; if (!startedPlaying) { buffersToUnqueue = 0; AL.GetSource(alSource, ALGetSourcei.BuffersProcessed, out buffersToUnqueue); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to determine processed buffers from streamed source: " + AL.GetErrorString(alError)); } unqueuedBuffers = new int[buffersToUnqueue+emptyBuffers.Count]; AL.SourceUnqueueBuffers((int)alSource, buffersToUnqueue, unqueuedBuffers); for (int i = 0; i < emptyBuffers.Count; i++) { unqueuedBuffers[buffersToUnqueue + i] = (int)emptyBuffers[i]; } buffersToUnqueue += emptyBuffers.Count; emptyBuffers.Clear(); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to unqueue buffers from streamed source: " + AL.GetErrorString(alError)); } } else { startedPlaying = false; buffersToUnqueue = 4; unqueuedBuffers = new int[4]; for (int i = 0; i < 4; i++) { unqueuedBuffers[i] = (int)streamBuffers[i]; } } for (int i = 0; i < buffersToUnqueue; i++) { short[] buffer = streamShortBuffer; int readSamples = Sound.FillStreamBuffer(streamSeekPos, buffer); if (FilledByNetwork) { if (Sound is VoipSound voipSound) { voipSound.ApplyFilters(buffer, readSamples); } if (readSamples <= 0) { decayTimer++; if (decayTimer > 120) //TODO: replace magic number { reachedEndSample = true; } } else { decayTimer = 0; } } else if (Sound.StreamsReliably) { streamSeekPos += readSamples; if (readSamples < STREAM_BUFFER_SIZE) { if (looping) { streamSeekPos = 0; } else { reachedEndSample = true; } } } if (readSamples > 0) { AL.BufferData(unqueuedBuffers[i], Sound.ALFormat, buffer, readSamples, Sound.SampleRate); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to assign data to stream buffer: " + AL.GetErrorString(alError) + ": " + unqueuedBuffers[i].ToString() + "/" + unqueuedBuffers.Length + ", readSamples: " + readSamples); } AL.SourceQueueBuffer((int)alSource, unqueuedBuffers[i]); alError = AL.GetError(); if (alError != ALError.NoError) { throw new Exception("Failed to queue buffer[" + i.ToString() + "] to stream: " + AL.GetErrorString(alError)); } } else if (readSamples < 0) { reachedEndSample = true; } else { emptyBuffers.Add((uint)unqueuedBuffers[i]); } } if (AL.GetSourceState(alSource) != ALSourceState.Playing) { AL.SourcePlay(alSource); } } } } } }