38f1ddb...178a853: v0.8.9.1, removed content folder

This commit is contained in:
Joonas Rikkonen
2019-03-18 19:46:58 +02:00
parent 38f1ddb6fe
commit 6c0679c297
1054 changed files with 151673 additions and 144931 deletions
@@ -1,119 +1,122 @@
using NVorbis;
using System;
using OpenTK.Audio.OpenAL;
using System;
using NVorbis;
namespace Barotrauma.Sounds
{
class OggSound : IDisposable
public class OggSound : Sound
{
//internal VorbisReader Reader { get; private set; }
private VorbisReader reader;
//const int DefaultBufferSize = 44100;
//private VorbisReader reader;
//private SoundEffect effect;
//SoundEffectInstance instance;
public const int DefaultBufferCount = 3;
private short[] castBuffer;
private int sampleRate;
private ALFormat format;
private string file;
int alBufferId;
public int AlBufferId
public OggSound(SoundManager owner,string filename,bool stream) : base(owner,filename,stream)
{
get { return alBufferId; }
}
//public bool IsLooped { get; set; }
public static OggSound Load(string oggFile, int bufferCount = DefaultBufferCount)
{
OggSound sound = new OggSound();
sound.file = oggFile;
using (VorbisReader reader = new VorbisReader(oggFile))
if (!ToolBox.IsProperFilenameCase(filename))
{
int bufferSize = (int)reader.TotalSamples * reader.Channels;
float[] buffer = new float[bufferSize];
sound.castBuffer = new short[bufferSize];
int readSamples = reader.ReadSamples(buffer, 0, bufferSize);
CastBuffer(buffer, sound.castBuffer, readSamples);
sound.alBufferId = AL.GenBuffer();
sound.format = reader.Channels == 1 ? ALFormat.Mono16 : ALFormat.Stereo16;
sound.sampleRate = reader.SampleRate;
ALHelper.Check();
//alSourceId = AL.GenSource();
AL.BufferData(sound.alBufferId, reader.Channels == 1 ? ALFormat.Mono16 : ALFormat.Stereo16, sound.castBuffer,
readSamples * sizeof(short), reader.SampleRate);
ALHelper.Check(oggFile);
DebugConsole.ThrowError("Sound file \"" + filename + "\" has incorrect case!");
}
//AL.Source(alSourceId, ALSourcei.Buffer, alBufferId);
reader = new VorbisReader(filename);
//if (ALHelper.XRam.IsInitialized)
//{
// ALHelper.XRam.SetBufferMode(bufferCount, ref alBufferId, XRamExtension.XRamStorage.Hardware);
// ALHelper.Check();
//}
ALFormat = reader.Channels == 1 ? ALFormat.Mono16 : ALFormat.Stereo16;
SampleRate = reader.SampleRate;
//Volume = 1;
if (!stream)
{
int bufferSize = (int)reader.TotalSamples*reader.Channels;
//if (ALHelper.Efx.IsInitialized)
//{
// alFilterId = ALHelper.Efx.GenFilter();
// ALHelper.Efx.Filter(alFilterId, EfxFilteri.FilterType, (int)EfxFilterType.Lowpass);
// ALHelper.Efx.Filter(alFilterId, EfxFilterf.LowpassGain, 1);
// LowPassHFGain = 1;
//}
return sound;
float[] floatBuffer = new float[bufferSize];
short[] shortBuffer = new short[bufferSize];
int readSamples = reader.ReadSamples(floatBuffer, 0, bufferSize);
CastBuffer(floatBuffer, shortBuffer, readSamples);
AL.BufferData((int)ALBuffer, ALFormat, shortBuffer,
readSamples * sizeof(short), SampleRate);
ALError alError = AL.GetError();
if (alError != ALError.NoError)
{
throw new Exception("Failed to set buffer data for non-streamed audio! "+AL.GetErrorString(alError));
}
MuffleBuffer(floatBuffer, reader.Channels);
CastBuffer(floatBuffer, shortBuffer, readSamples);
AL.BufferData((int)ALMuffledBuffer, ALFormat, shortBuffer,
readSamples * sizeof(short), SampleRate);
alError = AL.GetError();
if (alError != ALError.NoError)
{
throw new Exception("Failed to set buffer data for non-streamed audio! " + AL.GetErrorString(alError));
}
reader.Dispose();
}
}
public void SetBufferData(int alBufferId)
public override int FillStreamBuffer(int samplePos, short[] buffer)
{
AL.BufferData(alBufferId, format, castBuffer,
castBuffer.Length * sizeof(short), sampleRate);
if (!Stream) throw new Exception("Called FillStreamBuffer on a non-streamed sound!");
if (samplePos >= reader.TotalSamples * reader.Channels * 2) return 0;
samplePos /= reader.Channels*2;
reader.DecodedPosition = samplePos;
float[] floatBuffer = new float[buffer.Length];
int readSamples = reader.ReadSamples(floatBuffer, 0, buffer.Length/2);
//MuffleBuffer(floatBuffer, reader.Channels);
CastBuffer(floatBuffer, buffer, readSamples);
return readSamples*2;
}
static void MuffleBuffer(float[] buffer,int channelCount)
{
//this function will probably have to replace EFX on OSX
float[] avgvals = new float[channelCount];
for (int j = 0; j < channelCount; j++)
{
avgvals[j] = buffer[j];
}
for (int i = 0; i < buffer.Length; i+=channelCount)
{
for (int j = 0; j < channelCount; j++)
{
float fval = buffer[i + j];
float weight = 0.7f;
weight = 1.0f - weight;
weight *= weight * weight;
avgvals[j] = (avgvals[j] * (1.0f - weight) + fval * weight);
fval = avgvals[j]*1.7f;
buffer[i + j] = fval;
}
}
}
static void CastBuffer(float[] inBuffer, short[] outBuffer, int length)
{
for (int i = 0; i < length; i++)
{
int temp = (int)(32767f * inBuffer[i]);
float fval = Math.Max(Math.Min(inBuffer[i], 1.0f), -1.0f);
int temp = (int)(32767f * fval);
if (temp > short.MaxValue) temp = short.MaxValue;
else if (temp < short.MinValue) temp = short.MinValue;
outBuffer[i] = (short)temp;
}
}
public void Dispose()
{
System.Diagnostics.Debug.WriteLine(alBufferId);
if (alBufferId > 0)
{
AL.DeleteBuffer(alBufferId);
alBufferId = 0;
}
//if (ALHelper.Efx.IsInitialized)
// ALHelper.Efx.DeleteFilter(alFilterId);
ALHelper.Check();
public override void Dispose()
{
if (Stream)
{
reader.Dispose();
}
base.Dispose();
}
}
}
}
@@ -1,540 +0,0 @@
using NVorbis;
using OpenTK.Audio.OpenAL;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
namespace Barotrauma.Sounds
{
internal static class ALHelper
{
public static readonly XRamExtension XRam = new XRamExtension();
public static readonly EffectsExtension Efx = new EffectsExtension();
static ALHelper()
{
try
{
Debug.WriteLine("OpenAL Soft [" + (AL.Get(ALGetString.Version).Contains("SOFT") ? "X" : " ") + "], ");
Debug.WriteLine("X-RAM [" + (XRam.IsInitialized ? "X" : " ") + "], ");
Debug.WriteLine("Effect Extensions [" + (Efx.IsInitialized ? "X" : " ") + "]");
}
catch (Exception e)
{
DebugConsole.ThrowError("OpenAL error!", e);
}
}
[Conditional("TRACE")]
public static void TraceMemoryUsage(Action<string, int, int> logHandler)
{
var usedHeap = (double)GC.GetTotalMemory(true);
string[] sizes = { "B", "KB", "MB", "GB" };
int order = 0;
while (usedHeap >= 1024 && order + 1 < sizes.Length)
{
order++;
usedHeap = usedHeap / 1024;
}
//logHandler(String.Format("Total memory : {0:0.###} {1} ", usedHeap, sizes[order]), 0, 6);
}
public static void Check(string extraErrorMsg = "")
{
ALError error;
if ((error = AL.GetError()) != ALError.NoError)
{
string errorMsg = "OpenAL error: " + AL.GetErrorString(error);
if (!string.IsNullOrEmpty(extraErrorMsg)) errorMsg += " {" + extraErrorMsg + "} ";
errorMsg += "\n" + Environment.StackTrace;
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#else
DebugConsole.NewMessage(errorMsg, Microsoft.Xna.Framework.Color.Red);
#endif
GameAnalyticsManager.AddErrorEventOnce(
"OggStream.Check:" + AL.GetErrorString(error) + extraErrorMsg,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
errorMsg);
}
}
}
public class OggStream : IDisposable
{
public const int DefaultBufferCount = 3;
internal readonly object stopMutex = new object();
internal readonly object prepareMutex = new object();
internal readonly int alSourceId;
internal readonly int[] alBufferIds;
//readonly int alFilterId;
readonly Stream underlyingStream;
internal VorbisReader Reader { get; private set; }
internal bool Ready { get; private set; }
internal bool Preparing { get; private set; }
public int BufferCount { get; private set; }
#if TRACE
public int logX, logY;
public Action<string, int, int> LogHandler;
#endif
public string FileName
{
get;
private set;
}
public OggStream(string filename, int bufferCount = DefaultBufferCount) : this(File.OpenRead(filename), filename, bufferCount) { }
public OggStream(Stream stream, string fileName, int bufferCount = DefaultBufferCount)
{
this.FileName = fileName;
BufferCount = bufferCount;
alBufferIds = AL.GenBuffers(bufferCount);
alSourceId = AL.GenSource();
if (ALHelper.XRam.IsInitialized)
{
ALHelper.XRam.SetBufferMode(BufferCount, ref alBufferIds[0], XRamExtension.XRamStorage.Hardware);
ALHelper.Check(fileName);
}
if (ALHelper.Efx.IsInitialized)
{
//alFilterId = ALHelper.Efx.GenFilter();
//ALHelper.Efx.Filter(alFilterId, EfxFilteri.FilterType, (int)EfxFilterType.Lowpass);
//ALHelper.Efx.Filter(alFilterId, EfxFilterf.LowpassGain, 1);
//ALHelper.Efx.BindFilterToSource(alSourceId, alFilterId);
//LowPassHFGain = 1;
}
underlyingStream = stream;
IsLooped = true;
}
public void Prepare()
{
if (Preparing) return;
var state = AL.GetSourceState(alSourceId);
lock (stopMutex)
{
switch (state)
{
case ALSourceState.Playing:
case ALSourceState.Paused:
return;
case ALSourceState.Stopped:
lock (prepareMutex)
{
Reader.DecodedTime = TimeSpan.Zero;
Ready = false;
Empty();
}
break;
}
if (!Ready)
{
lock (prepareMutex)
{
Preparing = true;
Open(precache: true);
}
}
}
}
public void Play(float volume)
{
var state = AL.GetSourceState(alSourceId);
switch (state)
{
case ALSourceState.Playing: return;
case ALSourceState.Paused:
Resume();
return;
}
Prepare();
AL.SourcePlay(alSourceId);
this.Volume = volume;
ALHelper.Check(FileName);
Preparing = false;
OggStreamer.Instance.AddStream(this);
}
public void Pause()
{
if (AL.GetSourceState(alSourceId) != ALSourceState.Playing)
return;
OggStreamer.Instance.RemoveStream(this);
AL.SourcePause(alSourceId);
ALHelper.Check(FileName);
}
public void Resume()
{
if (AL.GetSourceState(alSourceId) != ALSourceState.Paused)
return;
OggStreamer.Instance.AddStream(this);
AL.SourcePlay(alSourceId);
ALHelper.Check(FileName);
}
public void Stop()
{
var state = AL.GetSourceState(alSourceId);
if (state == ALSourceState.Playing || state == ALSourceState.Paused)
{
StopPlayback();
}
lock (stopMutex)
{
OggStreamer.Instance.RemoveStream(this);
}
}
/*float lowPassHfGain;
public float LowPassHFGain
{
get { return lowPassHfGain; }
set
{
if (ALHelper.Efx.IsInitialized)
{
ALHelper.Efx.Filter(alFilterId, EfxFilterf.LowpassGainHF, lowPassHfGain = value);
ALHelper.Efx.BindFilterToSource(alSourceId, alFilterId);
ALHelper.Check();
}
}
}*/
float volume;
public float Volume
{
get { return volume; }
set
{
AL.Source(alSourceId, ALSourcef.Gain, volume = value);
ALHelper.Check(FileName);
}
}
public bool IsLooped { get; set; }
public void Dispose()
{
var state = AL.GetSourceState(alSourceId);
if (state == ALSourceState.Playing || state == ALSourceState.Paused)
StopPlayback();
lock (prepareMutex)
{
OggStreamer.Instance.RemoveStream(this);
if (state != ALSourceState.Initial)
Empty();
Close();
underlyingStream.Dispose();
}
AL.DeleteSource(alSourceId);
AL.DeleteBuffers(alBufferIds);
/*if (ALHelper.Efx.IsInitialized)
ALHelper.Efx.DeleteFilter(alFilterId);*/
ALHelper.Check(FileName);
}
void StopPlayback()
{
AL.SourceStop(alSourceId);
ALHelper.Check(FileName);
}
void Empty()
{
int queued;
AL.GetSource(alSourceId, ALGetSourcei.BuffersQueued, out queued);
ALHelper.Check(FileName);
if (queued > 0)
{
try
{
AL.SourceUnqueueBuffers(alSourceId, queued);
if (AL.GetError() != ALError.NoError)
{
throw new InvalidOperationException();
}
}
catch (InvalidOperationException)
{
// This is a bug in the OpenAL implementation
// Salvage what we can
int processed;
AL.GetSource(alSourceId, ALGetSourcei.BuffersProcessed, out processed);
var salvaged = new int[processed];
if (processed > 0)
{
AL.SourceUnqueueBuffers(alSourceId, processed, salvaged);
ALHelper.Check(FileName);
}
// Try turning it off again?
AL.SourceStop(alSourceId);
ALHelper.Check(FileName);
Empty();
}
}
}
internal void Open(bool precache = false)
{
underlyingStream.Seek(0, SeekOrigin.Begin);
Reader = new VorbisReader(underlyingStream, false);
if (precache)
{
// Fill first buffer synchronously
OggStreamer.Instance.FillBuffer(this, alBufferIds[0]);
AL.SourceQueueBuffer(alSourceId, alBufferIds[0]);
ALHelper.Check(FileName);
// Schedule the others asynchronously
OggStreamer.Instance.AddStream(this);
}
Ready = true;
}
internal void Close()
{
if (Reader != null)
{
Reader.Dispose();
Reader = null;
}
Ready = false;
}
}
public class OggStreamer : IDisposable
{
const float DefaultUpdateRate = 10;
const int DefaultBufferSize = 44100;
static readonly object singletonMutex = new object();
readonly object iterationMutex = new object();
readonly object readMutex = new object();
readonly float[] readSampleBuffer;
readonly short[] castBuffer;
readonly HashSet<OggStream> streams = new HashSet<OggStream>();
readonly List<OggStream> threadLocalStreams = new List<OggStream>();
readonly Thread underlyingThread;
volatile bool cancelled;
public float UpdateRate { get; private set; }
public int BufferSize { get; private set; }
static OggStreamer instance;
public static OggStreamer Instance
{
get
{
lock (singletonMutex)
{
if (instance == null)
throw new InvalidOperationException("No instance running");
return instance;
}
}
private set { lock (singletonMutex) instance = value; }
}
public OggStreamer(int bufferSize = DefaultBufferSize, float updateRate = DefaultUpdateRate)
{
lock (singletonMutex)
{
if (instance != null)
throw new InvalidOperationException("Already running");
Instance = this;
underlyingThread = new Thread(EnsureBuffersFilled) { Priority = ThreadPriority.Lowest };
//background threads are automatically stopped when all foreground threads have been stopped
// -> the streaming thread won't stay running in the background if the main thread crashes
underlyingThread.IsBackground = true;
underlyingThread.Start();
}
UpdateRate = updateRate;
BufferSize = bufferSize;
readSampleBuffer = new float[bufferSize];
castBuffer = new short[bufferSize];
}
public void Dispose()
{
lock (singletonMutex)
{
Debug.Assert(Instance == this, "Two instances running, somehow...?");
cancelled = true;
lock (iterationMutex)
streams.Clear();
Instance = null;
}
}
internal bool AddStream(OggStream stream)
{
lock (iterationMutex)
return streams.Add(stream);
}
internal bool RemoveStream(OggStream stream)
{
lock (iterationMutex)
return streams.Remove(stream);
}
public bool FillBuffer(OggStream stream, int bufferId)
{
int readSamples;
lock (readMutex)
{
readSamples = stream.Reader.ReadSamples(readSampleBuffer, 0, BufferSize);
CastBuffer(readSampleBuffer, castBuffer, readSamples);
}
AL.BufferData(bufferId, stream.Reader.Channels == 1 ? ALFormat.Mono16 : ALFormat.Stereo16, castBuffer,
readSamples * sizeof(short), stream.Reader.SampleRate);
ALHelper.Check(stream.FileName);
return readSamples != BufferSize;
}
static void CastBuffer(float[] inBuffer, short[] outBuffer, int length)
{
for (int i = 0; i < length; i++)
{
var temp = (int)(32767f * inBuffer[i]);
if (temp > short.MaxValue) temp = short.MaxValue;
else if (temp < short.MinValue) temp = short.MinValue;
outBuffer[i] = (short)temp;
}
}
void EnsureBuffersFilled()
{
while (!cancelled)
{
Thread.Sleep((int)(1000 / UpdateRate));
if (cancelled) break;
threadLocalStreams.Clear();
lock (iterationMutex) threadLocalStreams.AddRange(streams);
foreach (var stream in threadLocalStreams)
{
lock (stream.prepareMutex)
{
lock (iterationMutex)
if (!streams.Contains(stream))
continue;
bool finished = false;
int queued;
AL.GetSource(stream.alSourceId, ALGetSourcei.BuffersQueued, out queued);
ALHelper.Check(stream.FileName);
int processed;
AL.GetSource(stream.alSourceId, ALGetSourcei.BuffersProcessed, out processed);
ALHelper.Check(stream.FileName);
if (processed == 0 && queued == stream.BufferCount) continue;
int[] tempBuffers;
if (processed > 0)
tempBuffers = AL.SourceUnqueueBuffers(stream.alSourceId, processed);
else
tempBuffers = stream.alBufferIds.Skip(queued).ToArray();
for (int i = 0; i < tempBuffers.Length; i++)
{
finished |= FillBuffer(stream, tempBuffers[i]);
if (finished)
{
if (stream.IsLooped)
stream.Reader.DecodedTime = TimeSpan.Zero;
else
{
streams.Remove(stream);
i = tempBuffers.Length;
}
}
}
AL.SourceQueueBuffers(stream.alSourceId, tempBuffers.Length, tempBuffers);
ALHelper.Check(stream.FileName);
if (finished && !stream.IsLooped)
continue;
}
lock (stream.stopMutex)
{
if (stream.Preparing) continue;
lock (iterationMutex)
if (!streams.Contains(stream))
continue;
var state = AL.GetSourceState(stream.alSourceId);
if (state == ALSourceState.Stopped)
{
AL.SourcePlay(stream.alSourceId);
ALHelper.Check(stream.FileName);
}
}
}
}
}
}
}
+118 -226
View File
@@ -1,265 +1,157 @@
using Barotrauma.Sounds;
using System;
using OpenTK.Audio.OpenAL;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Linq;
namespace Barotrauma
namespace Barotrauma.Sounds
{
public class Sound
public abstract class Sound : IDisposable
{
public static Vector3 CameraPos;
public SoundManager Owner
{
get;
protected set;
}
private static List<Sound> loadedSounds = new List<Sound>();
public string Filename
{
get;
protected set;
}
private static OggStream stream;
public bool Stream
{
get;
protected set;
}
private OggSound oggSound;
private uint alBuffer;
public uint ALBuffer
{
get { return !Stream ? alBuffer : 0; }
}
private readonly string filePath;
private readonly bool destroyOnGameEnd;
private uint alMuffledBuffer;
public uint ALMuffledBuffer
{
get { return !Stream ? alMuffledBuffer : 0; }
}
private float baseVolume;
private float range;
public ALFormat ALFormat
{
get;
protected set;
}
private int alSourceId;
public int SampleRate
{
get;
protected set;
}
public float BaseGain;
public float BaseNear;
public float BaseFar;
public bool IsPlaying
public Sound(SoundManager owner,string filename,bool stream)
{
get
{
return SoundManager.IsPlaying(alSourceId);
}
}
Owner = owner;
Filename = Path.GetFullPath(filename);
Stream = stream;
private Sound(string file, bool destroyOnGameEnd)
{
filePath = file;
foreach (Sound loadedSound in loadedSounds)
{
if (loadedSound.filePath == file) oggSound = loadedSound.oggSound;
}
if (oggSound == null && !SoundManager.Disabled)
{
try
{
DebugConsole.Log("Loading sound " + file);
oggSound = OggSound.Load(file);
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to load sound "+file+"!", e);
}
ALHelper.Check(file);
}
baseVolume = 1.0f;
range = 1000.0f;
this.destroyOnGameEnd = destroyOnGameEnd;
loadedSounds.Add(this);
}
public string FilePath
{
get { return filePath; }
}
public int AlBufferId
{
get { return oggSound==null ? -1 : oggSound.AlBufferId; }
}
public static void Init()
{
SoundManager.Init();
}
public static Sound Load(string file, bool destroyOnGameEnd = true)
{
if (!File.Exists(file))
{
DebugConsole.ThrowError("File \"" + file + "\" not found!");
return null;
}
BaseGain = 1.0f;
BaseNear = 100.0f;
BaseFar = 200.0f;
return new Sound(file, destroyOnGameEnd);
}
public static Sound Load(XElement element, bool destroyOnGameEnd = true)
{
string filePath = element.GetAttributeString("file", "");
var newSound = new Sound(filePath, destroyOnGameEnd);
if (newSound != null)
if (!stream)
{
newSound.baseVolume = element.GetAttributeFloat("volume", 1.0f);
newSound.range = element.GetAttributeFloat("range", 1000.0f);
}
return newSound;
}
public int Play(float volume = 1.0f)
{
if (volume <= 0.0f) return -1;
alSourceId = SoundManager.Play(this, volume);
return alSourceId;
}
public int Play(Vector2 position)
{
return Play(baseVolume, range, position);
}
public int Play(float baseVolume, float range, Vector2 position)
{
Vector2 relativePos = GetRelativePosition(position);
float volume = GetVolume(relativePos, range, baseVolume);
if (volume <= 0.0f) return -1;
alSourceId = SoundManager.Play(this, relativePos, volume);
return alSourceId;
}
public void UpdatePosition(Vector2 position)
{
int sourceIndex = -1;
if (SoundManager.IsPlaying(this, out sourceIndex))
{
Vector2 relativePos = GetRelativePosition(position);
float volume = GetVolume(relativePos, range, baseVolume);
if (volume <= 0.0f)
AL.GenBuffer(out alBuffer);
ALError alError = AL.GetError();
if (alError != ALError.NoError)
{
SoundManager.Stop(this);
return;
throw new Exception("Failed to create OpenAL buffer for non-streamed sound: " + AL.GetErrorString(alError));
}
if (!AL.IsBuffer(alBuffer))
{
throw new Exception("Generated OpenAL buffer is invalid!");
}
AL.GenBuffer(out alMuffledBuffer);
alError = AL.GetError();
if (alError != ALError.NoError)
{
throw new Exception("Failed to create OpenAL buffer for non-streamed sound: " + AL.GetErrorString(alError));
}
SoundManager.UpdateSoundPosition(sourceIndex, relativePos, volume);
if (!AL.IsBuffer(alMuffledBuffer))
{
throw new Exception("Generated OpenAL buffer is invalid!");
}
}
else
{
alBuffer = 0;
}
}
public override string ToString()
{
return GetType().ToString() + " (" + Filename + ")";
}
public bool IsPlaying()
{
return Owner.IsPlaying(this);
}
public SoundChannel Play(float gain, float range, Vector2 position, bool muffle = false)
{
return new SoundChannel(this, gain, new Vector3(position.X, position.Y, 0.0f), range * 0.4f, range, "default", muffle);
}
private float GetVolume(Vector2 relativePosition, float range, float baseVolume)
public SoundChannel Play(Vector3? position, float gain, bool muffle = false)
{
float volume = (range == 0.0f) ? 0.0f : MathHelper.Clamp(baseVolume * (range - (relativePosition.Length() * 100.0f)) / range, 0.0f, 1.0f);
return volume;
return new SoundChannel(this, gain, position, BaseNear, BaseFar, "default", muffle);
}
private Vector2 GetRelativePosition(Vector2 position)
public SoundChannel Play(float gain)
{
return new Vector2(position.X - CameraPos.X, position.Y - CameraPos.Y) / 100.0f;
return Play(null, gain);
}
public int Loop(int sourceIndex, float volume)
public SoundChannel Play()
{
if (volume <= 0.0f)
return Play(BaseGain);
}
public SoundChannel Play(float? gain, string category)
{
return new SoundChannel(this, gain ?? BaseGain, null, BaseNear, BaseFar, category);
}
public abstract int FillStreamBuffer(int samplePos, short[] buffer);
public virtual void Dispose()
{
Owner.KillChannels(this);
if (alBuffer != 0)
{
if (sourceIndex > 0)
if (!AL.IsBuffer(alBuffer))
{
SoundManager.Stop(sourceIndex);
sourceIndex = -1;
throw new Exception("Buffer to delete is invalid!");
}
AL.DeleteBuffer(ref alBuffer); alBuffer = 0;
return sourceIndex;
}
int newIndex = SoundManager.Loop(this, sourceIndex, volume);
return newIndex;
}
public int Loop(int sourceIndex, float baseVolume, Vector2 position, float range)
{
Vector2 relativePos = GetRelativePosition(position);
float volume = GetVolume(relativePos, range, baseVolume);
if (volume <= 0.0f)
{
if (sourceIndex > 0)
ALError alError = AL.GetError();
if (alError != ALError.NoError)
{
SoundManager.Stop(sourceIndex);
sourceIndex = -1;
throw new Exception("Failed to delete OpenAL buffer for non-streamed sound: " + AL.GetErrorString(alError));
}
return sourceIndex;
}
alSourceId = SoundManager.Loop(this, sourceIndex, relativePos, volume);
return alSourceId;
Owner.RemoveSound(this);
}
public static void OnGameEnd()
{
List<Sound> removableSounds = loadedSounds.FindAll(s => s.destroyOnGameEnd);
foreach (Sound sound in removableSounds)
{
sound.Remove();
}
}
public void Remove()
{
//sound already removed?
if (!loadedSounds.Contains(this)) return;
loadedSounds.Remove(this);
if (alSourceId > 0 &&
(SoundManager.IsPlaying(alSourceId) || SoundManager.IsPaused(alSourceId)))
{
SoundManager.Stop(alSourceId);
ALHelper.Check(filePath);
}
foreach (Sound s in loadedSounds)
{
if (s.oggSound == oggSound) return;
}
SoundManager.ClearAlSource(AlBufferId);
ALHelper.Check(filePath);
if (oggSound != null)
{
oggSound.Dispose();
oggSound = null;
}
}
public static void StartStream(string file, float volume = 1.0f)
{
if (SoundManager.Disabled) return;
stream = SoundManager.StartStream(file, volume);
}
public static void StreamVolume(float volume = 1.0f)
{
if (SoundManager.Disabled || stream == null) return;
stream.Volume = volume;
}
public static void StopStream()
{
if (stream != null) SoundManager.StopStream();
}
public static void Dispose()
{
SoundManager.Dispose();
}
}
}
}
@@ -0,0 +1,507 @@
using System;
using OpenTK.Audio.OpenAL;
using Microsoft.Xna.Framework;
namespace Barotrauma.Sounds
{
public class SoundChannel : IDisposable
{
private const int STREAM_BUFFER_SIZE = 65536;
private Vector3? position;
public Vector3? Position
{
get { return position; }
set
{
position = value;
if (ALSourceIndex < 0) return;
if (position != null)
{
uint alSource = Sound.Owner.GetSourceFromIndex(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(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(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(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(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(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));
}
}
}
}
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(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;
}
public bool IsStream
{
get;
private set;
}
private int streamSeekPos;
private bool startedPlaying;
private bool reachedEndSample;
private uint[] streamBuffers;
private object mutex;
public bool IsPlaying
{
get
{
if (ALSourceIndex < 0) return false;
if (IsStream && !reachedEndSample) return true;
bool playing = AL.GetSourceState(Sound.Owner.GetSourceFromIndex(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;
streamSeekPos = 0; reachedEndSample = false;
startedPlaying = true;
mutex = new object();
ALSourceIndex = sound.Owner.AssignFreeSourceToChannel(this);
if (ALSourceIndex>=0)
{
if (!IsStream)
{
AL.BindBufferToSource(sound.Owner.GetSourceFromIndex(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(ALSourceIndex), alBuffer);
alError = AL.GetError();
if (alError != ALError.NoError)
{
throw new Exception("Failed to bind buffer to source (" +ALSourceIndex.ToString()+":"+sound.Owner.GetSourceFromIndex(ALSourceIndex)+"," +sound.ALBuffer.ToString()+"): " + AL.GetErrorString(alError));
}
AL.SourcePlay(sound.Owner.GetSourceFromIndex(ALSourceIndex));
alError = AL.GetError();
if (alError != ALError.NoError)
{
throw new Exception("Failed to play source: " + AL.GetErrorString(alError));
}
}
else
{
AL.BindBufferToSource(sound.Owner.GetSourceFromIndex(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(ALSourceIndex), ALSourceb.Looping, false);
alError = AL.GetError();
if (alError != ALError.NoError)
{
throw new Exception("Failed to set stream looping state: " + AL.GetErrorString(alError));
}
streamBuffers = new uint[4];
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 void Dispose()
{
lock (mutex)
{
if (ALSourceIndex >= 0)
{
AL.SourceStop(Sound.Owner.GetSourceFromIndex(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(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(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(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];
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));
}
}
else
{
startedPlaying = false;
buffersToUnqueue = 4;
unqueuedBuffers = (int[])streamBuffers.Clone();
}
for (int i = 0; i < buffersToUnqueue; i++)
{
short[] buffer = new short[STREAM_BUFFER_SIZE];
int readSamples = Sound.FillStreamBuffer(streamSeekPos, buffer);
streamSeekPos += readSamples;
if (readSamples < STREAM_BUFFER_SIZE)
{
if (looping)
{
streamSeekPos = 0;
}
else
{
reachedEndSample = true;
}
}
if (readSamples > 0)
{
AL.BufferData<short>(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));
}
}
}
if (AL.GetSourceState(alSource) != ALSourceState.Playing)
{
AL.SourcePlay(alSource);
}
}
}
}
}
}
@@ -1,391 +1,501 @@
using Microsoft.Xna.Framework;
using OpenTK.Audio;
using OpenTK.Audio.OpenAL;
using System;
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
{
static class SoundManager
public class SoundManager : IDisposable
{
public static bool Disabled
{
get;
private set;
}
public const int DefaultSourceCount = 16;
private static readonly List<int> alSources = new List<int>();
private static readonly int[] alBuffers = new int[DefaultSourceCount];
private static int lowpassFilterId;
private static readonly Sound[] soundsPlaying = new Sound[DefaultSourceCount];
public const int SOURCE_COUNT = 32;
private static AudioContext AC;
private IntPtr alcDevice;
private OpenTK.ContextHandle alcContext;
private List<string> alcCaptureDeviceNames;
private uint[] alSources;
private static OggStreamer oggStreamer;
private static OggStream oggStream;
private List<Sound> loadedSounds;
private SoundChannel[] playingChannels;
public static float MasterVolume = 1.0f;
private Thread streamingThread;
public static void Init()
private Vector3 listenerPosition;
public Vector3 ListenerPosition
{
var availableDevices = AudioContext.AvailableDevices;
if (availableDevices.Count == 0)
get { return listenerPosition; }
set
{
DebugConsole.ThrowError("No audio devices found. Disabling audio playback.");
Disabled = true;
return;
}
try
{
AC = new AudioContext();
ALHelper.Check();
}
catch (DllNotFoundException)
{
Program.CrashMessageBox("OpenAL32.dll not found");
throw;
}
for (int i = 0 ; i < DefaultSourceCount; i++)
{
alSources.Add(OpenTK.Audio.OpenAL.AL.GenSource());
}
ALHelper.Check();
if (ALHelper.Efx.IsInitialized)
{
lowpassFilterId = ALHelper.Efx.GenFilter();
//alFilters.Add(alFilterId);
ALHelper.Efx.Filter(lowpassFilterId, OpenTK.Audio.OpenAL.EfxFilteri.FilterType, (int)OpenTK.Audio.OpenAL.EfxFilterType.Lowpass);
LowPassHFGain = 1.0f;
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));
}
}
}
public static int Play(Sound sound, float volume = 1.0f)
private float[] listenerOrientation;
public Vector3 ListenerTargetVector
{
if (Disabled) return -1;
return Play(sound, Vector2.Zero, volume, 0.0f);
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));
}
}
}
public static int Play(Sound sound, Vector2 position, float volume = 1.0f, float lowPassGain = 0.0f, bool loop=false)
private float listenerGain;
public float ListenerGain
{
if (Disabled || sound.AlBufferId == -1) return -1;
int sourceIndex = FindAudioSource(volume);
if (sourceIndex > -1)
get { return listenerGain; }
set
{
soundsPlaying[sourceIndex] = sound;
alBuffers[sourceIndex] = sound.AlBufferId;
OpenTK.Audio.OpenAL.AL.Source(alSources[sourceIndex], OpenTK.Audio.OpenAL.ALSourceb.Looping, loop);
OpenTK.Audio.OpenAL.AL.Source(alSources[sourceIndex], OpenTK.Audio.OpenAL.ALSourcei.Buffer, sound.AlBufferId);
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<string, Pair<float,bool>> categoryModifiers;
public SoundManager()
{
loadedSounds = new List<Sound>();
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);
UpdateSoundPosition(sourceIndex, position, volume);
alcDevice = Alc.OpenDevice(null);
OpenTK.Audio.OpenAL.AL.SourcePlay(alSources[sourceIndex]);
}
return sourceIndex;
}
private static int FindAudioSource(float volume)
{
//find a source that's free to use (not playing or paused)
for (int i = 1; i < DefaultSourceCount; i++)
{
if (OpenTK.Audio.OpenAL.AL.GetSourceState(alSources[i]) == OpenTK.Audio.OpenAL.ALSourceState.Initial
|| OpenTK.Audio.OpenAL.AL.GetSourceState(alSources[i]) == OpenTK.Audio.OpenAL.ALSourceState.Stopped)
alcError = Alc.GetError(alcDevice);
if (alcError != AlcError.NoError)
{
return i;
throw new Exception("Error initializing ALC device: " + alcError.ToString());
}
}
//not found -> take up the channel that is playing at the lowest volume
float lowestVolume = volume;
int quietestSourceIndex = -1;
for (int i = 1; i < DefaultSourceCount; i++)
int[] alcContextAttrs = new int[] { };
alcContext = Alc.CreateContext(alcDevice, alcContextAttrs);
if (alcContext == null)
{
float vol;
OpenTK.Audio.OpenAL.AL.GetSource(alSources[i], ALSourcef.Gain, out vol);
if (vol < lowestVolume)
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<SOURCE_COUNT;i++)
{
AL.GenSource(out alSources[i]);
alError = AL.GetError();
if (alError!=ALError.NoError)
{
quietestSourceIndex = i;
lowestVolume = vol;
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));
}
}
if (quietestSourceIndex > -1)
AL.DistanceModel(ALDistanceModel.LinearDistanceClamped);
alError = AL.GetError();
if (alError != ALError.NoError)
{
Stop(quietestSourceIndex);
throw new Exception("Error setting distance model: " + AL.GetErrorString(alError));
}
return quietestSourceIndex;
}
public static int Loop(Sound sound, int sourceIndex, float volume = 1.0f)
{
if (Disabled) return -1;
return Loop(sound,sourceIndex, Vector2.Zero, volume);
}
public static int Loop(Sound sound, int sourceIndex, Vector2 position, float volume = 1.0f)
{
if (Disabled) return -1;
if (!MathUtils.IsValid(volume))
if (Alc.IsExtensionPresent(IntPtr.Zero, "ALC_EXT_CAPTURE"))
{
volume = 0.0f;
}
if (sourceIndex < 1 || soundsPlaying[sourceIndex] != sound)
{
sourceIndex = Play(sound, position, volume, 0.0f, true);
alcCaptureDeviceNames = new List<string>(Alc.GetString(IntPtr.Zero, AlcGetStringList.CaptureDeviceSpecifier));
}
else
{
UpdateSoundPosition(sourceIndex, position, volume);
AL.Source(alSources[sourceIndex], ALSourceb.Looping, true);
alcCaptureDeviceNames = null;
}
ALHelper.Check(sound?.FilePath);
return sourceIndex;
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 static void Pause(int sourceIndex)
public Sound LoadSound(string filename, bool stream = false)
{
if (Disabled) return;
if (AL.GetSourceState(alSources[sourceIndex]) != ALSourceState.Playing)
return;
AL.SourcePause(alSources[sourceIndex]);
ALHelper.Check(soundsPlaying[sourceIndex]?.FilePath);
}
public static void Resume(int sourceIndex)
{
if (Disabled) return;
if (AL.GetSourceState(alSources[sourceIndex]) != ALSourceState.Paused)
return;
AL.SourcePlay(alSources[sourceIndex]);
ALHelper.Check(soundsPlaying[sourceIndex]?.FilePath);
}
public static void Stop(int sourceIndex)
{
if (Disabled) return;
if (sourceIndex < 1) return;
var state = AL.GetSourceState(alSources[sourceIndex]);
if (state == ALSourceState.Playing || state == ALSourceState.Paused)
if (!File.Exists(filename))
{
AL.SourceStop(alSources[sourceIndex]);
AL.Source(alSources[sourceIndex], ALSourceb.Looping, false);
soundsPlaying[sourceIndex] = null;
throw new FileNotFoundException("Sound file \"" + filename + "\" doesn't exist!");
}
Sound newSound = new OggSound(this, filename, stream);
loadedSounds.Add(newSound);
return newSound;
}
public static void Stop(Sound sound)
public Sound LoadSound(XElement element, bool stream = false)
{
if (Disabled) return;
for (int i = 0; i < soundsPlaying.Length; i++)
string filePath = element.GetAttributeString("file", "");
if (!File.Exists(filePath))
{
if (soundsPlaying[i] == sound)
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++)
{
Stop(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<SOURCE_COUNT;i++)
{
AL.Source(alSources[i], ALSourcef.MaxGain, i == ind ? 1.0f : 0.0f);
AL.Source(alSources[i], ALSourcef.MinGain, 0.0f);
}
}
#endif
public bool IsPlaying(Sound sound)
{
lock (playingChannels)
{
for (int i = 0; i < SOURCE_COUNT; i++)
{
if (playingChannels[i] != null && playingChannels[i].Sound == sound)
{
if (playingChannels[i].IsPlaying) return true;
}
}
}
}
public static Sound GetPlayingSound(int sourceIndex)
{
if (Disabled) return null;
if (sourceIndex < 1 || sourceIndex>alSources.Count-1) return null;
if (AL.GetSourceState(alSources[sourceIndex]) != ALSourceState.Playing) return null;
return soundsPlaying[sourceIndex];
}
public static bool IsPlaying(int sourceIndex)
{
if (Disabled) return false;
if (sourceIndex < 1 || sourceIndex>alSources.Count-1) return false;
return AL.GetSourceState(alSources[sourceIndex]) == ALSourceState.Playing;
}
public static bool IsPlaying(Sound sound)
{
int temp;
return IsPlaying(sound, out temp);
}
public static bool IsPlaying(Sound sound, out int sourceIndex)
{
sourceIndex = -1;
if (Disabled) return false;
for (int i = 0; i < soundsPlaying.Length; i++)
{
if (soundsPlaying[i] == sound && AL.GetSourceState(alSources[i]) == ALSourceState.Playing)
{
sourceIndex = i;
return true;
}
}
return false;
}
public static bool IsPaused(int sourceIndex)
public SoundChannel GetChannelFromSound(Sound sound)
{
if (Disabled) return false;
if (sourceIndex < 1 || sourceIndex > alSources.Count - 1) return false;
return AL.GetSourceState(alSources[sourceIndex]) == ALSourceState.Paused;
}
public static bool IsLooping(int sourceIndex)
{
if (Disabled) return false;
if (sourceIndex < 1 || sourceIndex > alSources.Count - 1) return false;
bool isLooping;
OpenTK.Audio.OpenAL.AL.GetSource(alSources[sourceIndex], OpenTK.Audio.OpenAL.ALSourceb.Looping, out isLooping);
return isLooping;
}
static float lowPassHfGain;
public static float LowPassHFGain
{
get { return lowPassHfGain; }
set
lock (playingChannels)
{
if (Disabled) return;
if (ALHelper.Efx.IsInitialized)
for (int i = 0; i < SOURCE_COUNT; i++)
{
lowPassHfGain = value;
for (int i = 0; i < DefaultSourceCount; i++)
if (playingChannels[i] != null && playingChannels[i].Sound == sound)
{
//find a source that's free to use (not playing or paused)
if (OpenTK.Audio.OpenAL.AL.GetSourceState(alSources[i]) != OpenTK.Audio.OpenAL.ALSourceState.Playing
&& OpenTK.Audio.OpenAL.AL.GetSourceState(alSources[i])!= OpenTK.Audio.OpenAL.ALSourceState.Paused) continue;
if (playingChannels[i].IsPlaying) return playingChannels[i];
}
}
}
return null;
}
ALHelper.Efx.Filter(lowpassFilterId, OpenTK.Audio.OpenAL.EfxFilterf.LowpassGainHF, lowPassHfGain = value);
ALHelper.Efx.BindFilterToSource(alSources[i], lowpassFilterId);
ALHelper.Check(soundsPlaying[i]?.FilePath);
public void KillChannels(Sound sound)
{
lock (playingChannels)
{
for (int i = 0; i < SOURCE_COUNT; i++)
{
if (playingChannels[i]!=null && playingChannels[i].Sound == sound)
{
playingChannels[i].Dispose();
playingChannels[i] = null;
}
}
}
}
public static void UpdateSoundPosition(int sourceIndex, Vector2 position, float baseVolume = 1.0f)
public void RemoveSound(Sound sound)
{
if (sourceIndex < 1 || Disabled) return;
if (!MathUtils.IsValid(position))
for (int i=0;i<loadedSounds.Count;i++)
{
position = Vector2.Zero;
}
position /= 1000.0f;
OpenTK.Audio.OpenAL.AL.Source(alSources[sourceIndex], OpenTK.Audio.OpenAL.ALSourcef.Gain, baseVolume * MasterVolume);
OpenTK.Audio.OpenAL.AL.Source(alSources[sourceIndex], OpenTK.Audio.OpenAL.ALSource3f.Position, position.X, position.Y, 0.0f);
float lowPassGain = lowPassHfGain / Math.Max(position.Length() * 5.0f, 1.0f);
ALHelper.Efx.Filter(lowpassFilterId, OpenTK.Audio.OpenAL.EfxFilterf.LowpassGainHF, lowPassGain);
ALHelper.Efx.BindFilterToSource(alSources[sourceIndex], lowpassFilterId);
ALHelper.Check(soundsPlaying[sourceIndex]?.FilePath);
}
public static OggStream StartStream(string file, float volume = 1.0f)
{
if (Disabled) return null;
if (oggStreamer == null)
oggStreamer = new OggStreamer();
oggStream = new OggStream(file);
oggStreamer.AddStream(oggStream);
oggStream.Play(volume);
ALHelper.Check(file);
return oggStream;
}
public static void StopStream()
{
if (oggStream != null) oggStream.Stop();
}
public static void ClearAlSource(int bufferId)
{
for (int i = 1; i < DefaultSourceCount; i++)
{
if (alBuffers[i] != bufferId) continue;
OpenTK.Audio.OpenAL.AL.Source(alSources[i], OpenTK.Audio.OpenAL.ALSourceb.Looping, false);
OpenTK.Audio.OpenAL.AL.Source(alSources[i], OpenTK.Audio.OpenAL.ALSourcei.Buffer, 0);
}
}
public static void Dispose()
{
if (Disabled) return;
if (ALHelper.Efx.IsInitialized)
ALHelper.Efx.DeleteFilter(lowpassFilterId);
for (int i = 0; i < DefaultSourceCount; i++)
{
string soundPath = soundsPlaying[i]?.FilePath;
var state = OpenTK.Audio.OpenAL.AL.GetSourceState(alSources[i]);
if (state == OpenTK.Audio.OpenAL.ALSourceState.Playing || state == OpenTK.Audio.OpenAL.ALSourceState.Paused)
if (loadedSounds[i]==sound)
{
Stop(i);
loadedSounds.RemoveAt(i);
return;
}
OpenTK.Audio.OpenAL.AL.DeleteSource(alSources[i]);
ALHelper.Check(soundPath);
}
if (oggStream != null)
{
oggStream.Stop();
oggStream.Dispose();
oggStream = null;
}
if (oggStreamer != null)
{
oggStreamer.Dispose();
oggStreamer = null;
}
}
public void SetCategoryGainMultiplier(string category, float gain)
{
category = category.ToLower();
if (categoryModifiers == null) categoryModifiers = new Dictionary<string, Pair<float, bool>>();
if (!categoryModifiers.ContainsKey(category))
{
categoryModifiers.Add(category, new Pair<float, bool>(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<string, Pair<float, bool>>();
if (!categoryModifiers.ContainsKey(category))
{
categoryModifiers.Add(category, new Pair<float, bool>(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<SOURCE_COUNT;i++)
{
if (playingChannels[i]!=null && playingChannels[i].IsStream)
{
if (playingChannels[i].IsPlaying)
{
areStreamsPlaying = true;
playingChannels[i].UpdateStream();
}
else
{
playingChannels[i].Dispose();
}
}
}
}
Thread.Sleep(300);
}
}
public void Dispose()
{
lock (playingChannels)
{
for (int i=0;i<SOURCE_COUNT;i++)
{
if (playingChannels[i]!=null) playingChannels[i].Dispose();
}
}
if (streamingThread != null && streamingThread.ThreadState == ThreadState.Running)
{
streamingThread.Join();
}
for (int i = loadedSounds.Count - 1; 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!");
}
}
}
}
@@ -1,9 +1,8 @@
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
using Barotrauma.Sounds;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Xml.Linq;
@@ -34,16 +33,16 @@ namespace Barotrauma
public class BackgroundMusic
{
public readonly string file;
public readonly string type;
public readonly string File;
public readonly string Type;
public readonly Vector2 priorityRange;
public BackgroundMusic(string file, string type, Vector2 priorityRange)
public readonly Vector2 IntensityRange;
public BackgroundMusic(XElement element)
{
this.file = file;
this.type = type;
this.priorityRange = priorityRange;
this.File = Path.GetFullPath(element.GetAttributeString("file", ""));
this.Type = element.GetAttributeString("type", "").ToLowerInvariant();
this.IntensityRange = element.GetAttributeVector2("intensityrange", new Vector2(0.0f, 100.0f));
}
}
@@ -52,20 +51,21 @@ namespace Barotrauma
private static ILookup<string, Sound> miscSounds;
//music
public static float MusicVolume = 1.0f;
private const float MusicLerpSpeed = 1.0f;
private const float UpdateMusicInterval = 5.0f;
private static BackgroundMusic currentMusic;
private static BackgroundMusic targetMusic;
const int MaxMusicChannels = 6;
private readonly static Sound[] currentMusic = new Sound[MaxMusicChannels];
private readonly static SoundChannel[] musicChannel = new SoundChannel[MaxMusicChannels];
private readonly static BackgroundMusic[] targetMusic = new BackgroundMusic[MaxMusicChannels];
private static List<BackgroundMusic> musicClips;
private static float currMusicVolume;
private static float updateMusicTimer;
//ambience
private static List<Sound> waterAmbiences = new List<Sound>();
private static int[] waterAmbienceIndexes = new int[2];
private static SoundChannel[] waterAmbienceChannels = new SoundChannel[2];
private static float ambientSoundTimer;
private static Vector2 ambientSoundInterval = new Vector2(20.0f, 40.0f); //x = min, y = max
@@ -73,6 +73,19 @@ namespace Barotrauma
//misc
public static List<Sound> FlowSounds = new List<Sound>();
public static List<Sound> SplashSounds = new List<Sound>();
private static SoundChannel[] flowSoundChannels;
private static float[] flowVolumeLeft;
private static float[] flowVolumeRight;
const float FlowSoundRange = 1500.0f;
const float MaxFlowStrength = 400.0f; //the heaviest water sound effect is played when the water flow is this strong
private static SoundChannel[] fireSoundChannels;
private static float[] fireVolumeLeft;
private static float[] fireVolumeRight;
const float FireSoundRange = 1000.0f;
const float FireSoundLargeLimit = 200.0f; //switch to large fire sound when the size of a firesource is above this
private static List<DamageSound> damageSounds;
@@ -94,7 +107,7 @@ namespace Barotrauma
{
OverrideMusicType = null;
List<string> soundFiles = GameMain.Config.SelectedContentPackage.GetFilesOfType(ContentType.Sounds);
var soundFiles = GameMain.Instance.GetFilesOfType(ContentType.Sounds);
List<XElement> soundElements = new List<XElement>();
foreach (string soundFile in soundFiles)
@@ -111,7 +124,7 @@ namespace Barotrauma
var startUpSoundElement = soundElements.Find(e => e.Name.ToString().ToLowerInvariant() == "startupsound");
if (startUpSoundElement != null)
{
startUpSound = Sound.Load(startUpSoundElement, false);
startUpSound = GameMain.SoundManager.LoadSound(startUpSoundElement, false);
startUpSound.Play();
}
@@ -125,48 +138,59 @@ namespace Barotrauma
{
yield return CoroutineStatus.Running;
switch (soundElement.Name.ToString().ToLowerInvariant())
try
{
case "music":
string file = soundElement.GetAttributeString("file", "");
string type = soundElement.GetAttributeString("type", "").ToLowerInvariant();
Vector2 priority = soundElement.GetAttributeVector2("priorityrange", new Vector2(0.0f, 100.0f));
musicClips.Add(new BackgroundMusic(file, type, priority));
break;
case "splash":
SplashSounds.Add(Sound.Load(soundElement, false));
break;
case "flow":
FlowSounds.Add(Sound.Load(soundElement, false));
break;
case "waterambience":
waterAmbiences.Add(Sound.Load(soundElement, false));
break;
case "damagesound":
Sound damageSound = Sound.Load(soundElement.GetAttributeString("file", ""), false);
if (damageSound == null) continue;
switch (soundElement.Name.ToString().ToLowerInvariant())
{
case "music":
musicClips.Add(new BackgroundMusic(soundElement));
break;
case "splash":
SplashSounds.Add(GameMain.SoundManager.LoadSound(soundElement, false));
break;
case "flow":
FlowSounds.Add(GameMain.SoundManager.LoadSound(soundElement, false));
break;
case "waterambience":
waterAmbiences.Add(GameMain.SoundManager.LoadSound(soundElement, false));
break;
case "damagesound":
Sound damageSound = GameMain.SoundManager.LoadSound(soundElement, false);
if (damageSound == null) continue;
string damageSoundType = soundElement.GetAttributeString("damagesoundtype", "None");
string damageSoundType = soundElement.GetAttributeString("damagesoundtype", "None");
damageSounds.Add(new DamageSound(
damageSound,
soundElement.GetAttributeVector2("damagerange", new Vector2(0.0f, 100.0f)),
damageSoundType,
soundElement.GetAttributeString("requiredtag", "")));
damageSounds.Add(new DamageSound(
damageSound,
soundElement.GetAttributeVector2("damagerange", new Vector2(0.0f, 100.0f)),
damageSoundType,
soundElement.GetAttributeString("requiredtag", "")));
break;
default:
Sound sound = Sound.Load(soundElement.GetAttributeString("file", ""), false);
if (sound != null)
{
miscSoundList.Add(new KeyValuePair<string, Sound>(soundElement.Name.ToString().ToLowerInvariant(), sound));
}
break;
default:
Sound sound = GameMain.SoundManager.LoadSound(soundElement, false);
if (sound != null)
{
miscSoundList.Add(new KeyValuePair<string, Sound>(soundElement.Name.ToString().ToLowerInvariant(), sound));
}
break;
break;
}
}
catch (FileNotFoundException e)
{
DebugConsole.ThrowError("Error while initializing SoundPlayer.", e);
}
}
flowSoundChannels = new SoundChannel[FlowSounds.Count];
flowVolumeLeft = new float[FlowSounds.Count];
flowVolumeRight = new float[FlowSounds.Count];
fireSoundChannels = new SoundChannel[2];
fireVolumeLeft = new float[2];
fireVolumeRight = new float[2];
miscSounds = miscSoundList.ToLookup(kvp => kvp.Key, kvp => kvp.Value);
Initialized = true;
@@ -180,27 +204,31 @@ namespace Barotrauma
{
UpdateMusic(deltaTime);
if (startUpSound != null && !startUpSound.IsPlaying)
if (startUpSound != null && !GameMain.SoundManager.IsPlaying(startUpSound))
{
startUpSound.Remove();
startUpSound.Dispose();
startUpSound = null;
}
//stop submarine ambient sounds if no sub is loaded
if (Submarine.MainSub == null)
//stop water sounds if no sub is loaded
if (Submarine.MainSub == null || Screen.Selected != GameMain.GameScreen)
{
for (int i = 0; i < waterAmbienceIndexes.Length; i++)
for (int i = 0; i < waterAmbienceChannels.Length; i++)
{
if (waterAmbienceIndexes[i] <= 0) continue;
SoundManager.Stop(waterAmbienceIndexes[i]);
waterAmbienceIndexes[i] = 0;
}
if (waterAmbienceChannels[i] == null) continue;
waterAmbienceChannels[i].Dispose();
waterAmbienceChannels[i] = null;
}
for (int i = 0; i < FlowSounds.Count; i++)
{
if (flowSoundChannels[i] == null) continue;
flowSoundChannels[i].Dispose();
flowSoundChannels[i] = null;
}
return;
}
float ambienceVolume = 0.8f;
float lowpassHFGain = 1.0f;
if (Character.Controlled != null)
{
AnimController animController = Character.Controlled.AnimController;
@@ -208,13 +236,17 @@ namespace Barotrauma
{
ambienceVolume = 1.0f;
ambienceVolume += animController.Limbs[0].LinearVelocity.Length();
lowpassHFGain = 0.2f;
}
lowpassHFGain *= Character.Controlled.LowPassMultiplier;
}
UpdateWaterAmbience(ambienceVolume);
UpdateWaterFlowSounds(deltaTime);
UpdateRandomAmbience(deltaTime);
UpdateFireSounds(deltaTime);
}
private static void UpdateWaterAmbience(float ambienceVolume)
{
//how fast the sub is moving, scaled to 0.0 -> 1.0
float movementSoundVolume = 0.0f;
@@ -223,7 +255,7 @@ namespace Barotrauma
float movementFactor = (sub.Velocity == Vector2.Zero) ? 0.0f : sub.Velocity.Length() / 10.0f;
movementFactor = MathHelper.Clamp(movementFactor, 0.0f, 1.0f);
if (Character.Controlled==null || Character.Controlled.Submarine != sub)
if (Character.Controlled == null || Character.Controlled.Submarine != sub)
{
float dist = Vector2.Distance(GameMain.GameScreen.Cam.WorldViewCenter, sub.WorldPosition);
movementFactor = movementFactor / Math.Max(dist / 1000.0f, 1.0f);
@@ -232,9 +264,172 @@ namespace Barotrauma
movementSoundVolume = Math.Max(movementSoundVolume, movementFactor);
}
if (waterAmbiences.Count > 1)
{
if (waterAmbienceChannels[0] == null || !waterAmbienceChannels[0].IsPlaying)
{
waterAmbienceChannels[0] = waterAmbiences[0].Play(ambienceVolume * (1.0f - movementSoundVolume),"waterambience");
//waterAmbiences[0].Loop(waterAmbienceIndexes[0], ambienceVolume * (1.0f - movementSoundVolume));
waterAmbienceChannels[0].Looping = true;
}
else
{
waterAmbienceChannels[0].Gain = ambienceVolume * (1.0f - movementSoundVolume);
}
if (waterAmbienceChannels[1] == null || !waterAmbienceChannels[1].IsPlaying)
{
waterAmbienceChannels[1] = waterAmbiences[1].Play(ambienceVolume * movementSoundVolume, "waterambience");
//waterAmbienceIndexes[1] = waterAmbiences[1].Loop(waterAmbienceIndexes[1], ambienceVolume * movementSoundVolume);
waterAmbienceChannels[1].Looping = true;
}
else
{
waterAmbienceChannels[1].Gain = ambienceVolume * movementSoundVolume;
}
}
}
private static void UpdateWaterFlowSounds(float deltaTime)
{
if (FlowSounds.Count == 0) { return; }
float[] targetFlowLeft = new float[FlowSounds.Count];
float[] targetFlowRight = new float[FlowSounds.Count];
Vector2 listenerPos = new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y);
foreach (Gap gap in Gap.GapList)
{
if (gap.Open < 0.01f) continue;
float gapFlow = Math.Abs(gap.LerpedFlowForce.X) + Math.Abs(gap.LerpedFlowForce.Y) * 2.5f;
if (gapFlow < 10.0f) continue;
int flowSoundIndex = (int)Math.Floor(MathHelper.Clamp(gapFlow / MaxFlowStrength, 0, FlowSounds.Count));
flowSoundIndex = Math.Min(flowSoundIndex, FlowSounds.Count - 1);
Vector2 diff = gap.WorldPosition - listenerPos;
if (Math.Abs(diff.X) < FlowSoundRange && Math.Abs(diff.Y) < FlowSoundRange)
{
float dist = diff.Length();
float distFallOff = dist / FlowSoundRange;
if (distFallOff >= 0.99f) continue;
//flow at the left side
if (diff.X < 0)
{
targetFlowLeft[flowSoundIndex] = 1.0f - distFallOff;
}
else
{
targetFlowRight[flowSoundIndex] = 1.0f - distFallOff;
}
}
}
for (int i = 0; i < FlowSounds.Count; i++)
{
flowVolumeLeft[i] = (targetFlowLeft[i] < flowVolumeLeft[i]) ?
Math.Max(targetFlowLeft[i], flowVolumeLeft[i] - deltaTime) :
Math.Min(targetFlowLeft[i], flowVolumeLeft[i] + deltaTime);
flowVolumeRight[i] = (targetFlowRight[i] < flowVolumeRight[i]) ?
Math.Max(targetFlowRight[i], flowVolumeRight[i] - deltaTime) :
Math.Min(targetFlowRight[i], flowVolumeRight[i] + deltaTime);
if (flowVolumeLeft[i] < 0.05f && flowVolumeRight[i] < 0.05f)
{
if (flowSoundChannels[i] != null)
{
flowSoundChannels[i].Dispose();
flowSoundChannels[i] = null;
}
}
else
{
Vector2 soundPos = new Vector2(GameMain.SoundManager.ListenerPosition.X + (flowVolumeRight[i] - flowVolumeLeft[i]) * 100, GameMain.SoundManager.ListenerPosition.Y);
if (flowSoundChannels[i] == null || !flowSoundChannels[i].IsPlaying)
{
flowSoundChannels[i] = FlowSounds[i].Play(1.0f, FlowSoundRange, soundPos);
flowSoundChannels[i].Looping = true;
}
flowSoundChannels[i].Gain = Math.Max(flowVolumeRight[i], flowVolumeLeft[i]);
flowSoundChannels[i].Position = new Vector3(soundPos, 0.0f);
}
}
}
private static void UpdateFireSounds(float deltaTime)
{
for (int i = 0; i < fireVolumeLeft.Length; i++)
{
fireVolumeLeft[i] = 0.0f;
fireVolumeRight[i] = 0.0f;
}
Vector2 listenerPos = new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y);
foreach (Hull hull in Hull.hullList)
{
foreach (FireSource fs in hull.FireSources)
{
Vector2 diff = fs.WorldPosition + fs.Size / 2 - listenerPos;
if (Math.Abs(diff.X) < FireSoundRange && Math.Abs(diff.Y) < FireSoundRange)
{
Vector2 diffLeft = (fs.WorldPosition + new Vector2(fs.Size.X, fs.Size.Y / 2)) - listenerPos;
if (diff.X < fs.Size.X / 2.0f) diff.X = 0.0f;
if (diffLeft.X <= 0)
{
float distFallOffLeft = diffLeft.Length() / FireSoundRange;
if (distFallOffLeft < 0.99f)
{
fireVolumeLeft[0] += (1.0f - distFallOffLeft) * (fs.Size.X / FireSoundLargeLimit);
if (fs.Size.X > FireSoundLargeLimit) fireVolumeLeft[1] += (1.0f - distFallOffLeft) * ((fs.Size.X - FireSoundLargeLimit) / FireSoundLargeLimit);
}
}
Vector2 diffRight = (fs.WorldPosition + new Vector2(0.0f, fs.Size.Y / 2)) - listenerPos;
if (diff.X < fs.Size.X / 2.0f) diff.X = 0.0f;
if (diffRight.X >= 0)
{
float distFallOffRight = diffRight.Length() / FireSoundRange;
if (distFallOffRight < 0.99f)
{
fireVolumeRight[0] += 1.0f - distFallOffRight;
if (fs.Size.X > FireSoundLargeLimit) fireVolumeRight[1] += (1.0f - distFallOffRight) * ((fs.Size.X - FireSoundLargeLimit) / FireSoundLargeLimit);
}
}
}
}
}
for (int i = 0; i < fireVolumeLeft.Length; i++)
{
if (fireVolumeLeft[i] < 0.05f && fireVolumeRight[i] < 0.05f)
{
if (fireSoundChannels[i] != null)
{
fireSoundChannels[i].Dispose();
fireSoundChannels[i] = null;
}
}
else
{
Vector2 soundPos = new Vector2(GameMain.SoundManager.ListenerPosition.X + (fireVolumeRight[i] - fireVolumeLeft[i]) * 100, GameMain.SoundManager.ListenerPosition.Y);
if (fireSoundChannels[i] == null || !fireSoundChannels[i].IsPlaying)
{
fireSoundChannels[i] = GetSound(i == 0 ? "fire" : "firelarge").Play(1.0f, FlowSoundRange, soundPos);
fireSoundChannels[i].Looping = true;
}
fireSoundChannels[i].Gain = Math.Max(fireVolumeRight[i], fireVolumeLeft[i]);
fireSoundChannels[i].Position = new Vector3(soundPos, 0.0f);
}
}
}
private static void UpdateRandomAmbience(float deltaTime)
{
if (ambientSoundTimer > 0.0f)
{
ambientSoundTimer -= (float)Timing.Step;
ambientSoundTimer -= deltaTime;
}
else
{
@@ -242,18 +437,10 @@ namespace Barotrauma
"ambient",
Rand.Range(0.5f, 1.0f),
1000.0f,
new Vector2(Sound.CameraPos.X, Sound.CameraPos.Y) + Rand.Vector(100.0f));
new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y) + Rand.Vector(100.0f));
ambientSoundTimer = Rand.Range(ambientSoundInterval.X, ambientSoundInterval.Y);
}
SoundManager.LowPassHFGain = lowpassHFGain;
if (waterAmbiences.Count > 1)
{
waterAmbienceIndexes[0] = waterAmbiences[0].Loop(waterAmbienceIndexes[0], ambienceVolume * (1.0f - movementSoundVolume));
waterAmbienceIndexes[1] = waterAmbiences[1].Loop(waterAmbienceIndexes[1], ambienceVolume * movementSoundVolume);
}
}
public static Sound GetSound(string soundTag)
@@ -264,16 +451,23 @@ namespace Barotrauma
return matchingSounds[Rand.Int(matchingSounds.Count)];
}
public static void PlaySound(string soundTag, float volume = 1.0f)
public static SoundChannel PlaySound(string soundTag, float volume = 1.0f)
{
var sound = GetSound(soundTag);
if (sound != null) sound.Play(volume);
return sound?.Play(volume);
}
public static void PlaySound(string soundTag, float volume, float range, Vector2 position)
public static SoundChannel PlaySound(string soundTag, float volume, float range, Vector2 position, Hull hullGuess = null)
{
var sound = GetSound(soundTag);
if (sound != null) sound.Play(volume, range, position);
if (sound == null) return null;
return PlaySound(sound, sound.BaseGain * volume, range, position, hullGuess);
}
public static SoundChannel PlaySound(Sound sound, float volume, float range, Vector2 position, Hull hullGuess = null)
{
if (Vector2.DistanceSquared(new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y), position) > range * range) return null;
return sound.Play(sound.BaseGain * volume, range, position, muffle: ShouldMuffleSound(Character.Controlled, position, range, hullGuess));
}
private static void UpdateMusic(float deltaTime)
@@ -293,66 +487,110 @@ namespace Barotrauma
updateMusicTimer -= deltaTime;
if (updateMusicTimer <= 0.0f)
{
List<BackgroundMusic> suitableMusic = GetSuitableMusicClips();
//find appropriate music for the current situation
string currentMusicType = GetCurrentMusicType();
float currentIntensity = GameMain.GameSession?.EventManager != null ?
GameMain.GameSession.EventManager.CurrentIntensity * 100.0f : 0.0f;
if (suitableMusic.Count == 0)
{
targetMusic = null;
}
else if (!suitableMusic.Contains(currentMusic))
{
int index = Rand.Int(suitableMusic.Count);
IEnumerable<BackgroundMusic> suitableMusic = GetSuitableMusicClips(currentMusicType, currentIntensity);
if (currentMusic == null || suitableMusic[index].file != currentMusic.file)
if (suitableMusic.Count() == 0)
{
targetMusic[0] = null;
}
//switch the music if nothing playing atm or the currently playing clip is not suitable anymore
else if (targetMusic[0] == null || currentMusic[0] == null || !suitableMusic.Any(m => m.File == currentMusic[0].Filename))
{
targetMusic[0] = suitableMusic.GetRandom();
}
//get the appropriate intensity layers for current situation
IEnumerable<BackgroundMusic> suitableIntensityMusic = GetSuitableMusicClips("intensity", currentIntensity);
for (int i = 1; i < MaxMusicChannels; i++)
{
//disable targetmusics that aren't suitable anymore
if (targetMusic[i] != null && !suitableIntensityMusic.Any(m => m.File == targetMusic[i].File))
{
targetMusic = suitableMusic[index];
targetMusic[i] = null;
}
}
foreach (BackgroundMusic intensityMusic in suitableIntensityMusic)
{
//already playing, do nothing
if (targetMusic.Any(m => m != null && m.File == intensityMusic.File)) continue;
for (int i = 1; i < MaxMusicChannels; i++)
{
if (targetMusic[i] == null)
{
targetMusic[i] = intensityMusic;
break;
}
}
}
updateMusicTimer = UpdateMusicInterval;
}
if (targetMusic == null || currentMusic == null || targetMusic.file != currentMusic.file)
for (int i = 0; i < MaxMusicChannels; i++)
{
currMusicVolume = MathHelper.Lerp(currMusicVolume, 0.0f, MusicLerpSpeed * deltaTime);
if (currentMusic != null) Sound.StreamVolume(currMusicVolume);
if (currMusicVolume < 0.01f)
//nothing should be playing on this channel
if (targetMusic[i] == null)
{
Sound.StopStream();
try
if (musicChannel[i] != null && musicChannel[i].IsPlaying)
{
if (targetMusic != null) Sound.StartStream(targetMusic.file, currMusicVolume);
//mute the channel
musicChannel[i].Gain = MathHelper.Lerp(musicChannel[i].Gain, 0.0f, MusicLerpSpeed * deltaTime);
if (musicChannel[i].Gain < 0.01f) DisposeMusicChannel(i);
}
catch (FileNotFoundException e)
{
DebugConsole.ThrowError("Music clip " + targetMusic.file + " not found!", e);
}
currentMusic = targetMusic;
}
}
else
{
currMusicVolume = MathHelper.Lerp(currMusicVolume, MusicVolume, MusicLerpSpeed * deltaTime);
Sound.StreamVolume(currMusicVolume);
}
//something should be playing, but the channel is playing nothing or an incorrect clip
else if (currentMusic[i] == null || targetMusic[i].File != currentMusic[i].Filename)
{
//something playing -> mute it first
if (musicChannel[i] != null && musicChannel[i].IsPlaying)
{
musicChannel[i].Gain = MathHelper.Lerp(musicChannel[i].Gain, 0.0f, MusicLerpSpeed * deltaTime);
if (musicChannel[i].Gain < 0.01f) DisposeMusicChannel(i);
}
//channel free now, start playing the correct clip
if (currentMusic[i] == null || (musicChannel[i] == null || !musicChannel[i].IsPlaying))
{
DisposeMusicChannel(i);
currentMusic[i] = GameMain.SoundManager.LoadSound(targetMusic[i].File, true);
musicChannel[i] = currentMusic[i].Play(0.0f, "music");
musicChannel[i].Looping = true;
}
}
else
{
//playing something, lerp volume up
if (musicChannel[i] == null || !musicChannel[i].IsPlaying)
{
musicChannel[i]?.Dispose();
musicChannel[i] = currentMusic[i].Play(0.0f, "music");
musicChannel[i].Looping = true;
}
musicChannel[i].Gain = MathHelper.Lerp(musicChannel[i].Gain, 1.0f, MusicLerpSpeed * deltaTime);
}
}
}
public static void SwitchMusic()
private static void DisposeMusicChannel(int index)
{
var suitableMusic = GetSuitableMusicClips();
if (suitableMusic.Count > 1)
{
targetMusic = suitableMusic.Find(m => m != currentMusic);
}
musicChannel[index]?.Dispose(); musicChannel[index] = null;
currentMusic[index]?.Dispose(); currentMusic[index] = null;
}
private static List<BackgroundMusic> GetSuitableMusicClips()
private static IEnumerable<BackgroundMusic> GetSuitableMusicClips(string musicType, float currentIntensity)
{
string musicType = GetCurrentMusicType();
return musicClips.Where(music => music != null && music.type == musicType).ToList();
return musicClips.Where(music =>
music != null &&
music.Type == musicType &&
currentIntensity >= music.IntensityRange.X &&
currentIntensity <= music.IntensityRange.Y);
}
private static string GetCurrentMusicType()
@@ -375,20 +613,7 @@ namespace Barotrauma
}
if (targetSubmarine != null)
{
List<Reactor> reactors = new List<Reactor>();
foreach (Item item in Item.ItemList)
{
if (item.Submarine != targetSubmarine) continue;
var reactor = item.GetComponent<Reactor>();
if (reactor != null)
{
reactors.Add(reactor);
}
}
if (reactors.All(r => r.Temperature < 1.0f)) return "repair";
{
float floodedArea = 0.0f;
float totalArea = 0.0f;
foreach (Hull hull in Hull.hullList)
@@ -398,10 +623,9 @@ namespace Barotrauma
totalArea += hull.Volume;
}
if (totalArea > 0.0f && floodedArea / totalArea > 0.25f) return "repair";
if (totalArea > 0.0f && floodedArea / totalArea > 0.25f) return "flooded";
}
float enemyDistThreshold = 5000.0f;
if (targetSubmarine != null)
@@ -411,8 +635,9 @@ namespace Barotrauma
foreach (Character character in Character.CharacterList)
{
if (character.IsDead || !character.Enabled) continue;
EnemyAIController enemyAI = character.AIController as EnemyAIController;
if (enemyAI == null || (enemyAI.AttackHumans < 0.0f && enemyAI.AttackRooms < 0.0f)) continue;
if (enemyAI == null || (!enemyAI.AttackHumans && !enemyAI.AttackRooms)) continue;
if (targetSubmarine != null)
{
@@ -430,39 +655,64 @@ namespace Barotrauma
}
}
if (GameMain.GameSession != null && Timing.TotalTime < GameMain.GameSession.RoundStartTime + 120.0)
{
return "start";
}
return "default";
}
public static bool ShouldMuffleSound(Character listener, Vector2 soundWorldPos, float range, Hull hullGuess)
{
if (listener == null) return false;
float lowpassHFGain = 1.0f;
AnimController animController = listener.AnimController;
if (animController.HeadInWater)
{
lowpassHFGain = 0.2f;
}
lowpassHFGain *= Character.Controlled.LowPassMultiplier;
if (lowpassHFGain < 0.5f) return true;
Hull targetHull = Hull.FindHull(soundWorldPos, hullGuess, true);
if (listener.CurrentHull == null || targetHull == null)
{
return listener.CurrentHull != targetHull;
}
return listener.CurrentHull.GetApproximateDistance(targetHull, range) > range;
}
public static void PlaySplashSound(Vector2 worldPosition, float strength)
{
if (SplashSounds.Count == 0) { return; }
int splashIndex = MathHelper.Clamp((int)(strength + Rand.Range(-2, 2)), 0, SplashSounds.Count - 1);
SplashSounds[splashIndex].Play(1.0f, 800.0f, worldPosition);
float range = 800.0f;
var channel = SplashSounds[splashIndex].Play(1.0f, range, worldPosition, muffle: ShouldMuffleSound(Character.Controlled, worldPosition, range, null));
}
public static void PlayDamageSound(string damageType, float damage, PhysicsBody body)
{
Vector2 bodyPosition = body.DrawPosition;
PlayDamageSound(damageType, damage, bodyPosition, 800.0f);
}
public static void PlayDamageSound(string damageType, float damage, Vector2 position, float range = 2000.0f, List<string> tags = null)
public static void PlayDamageSound(string damageType, float damage, Vector2 position, float range = 2000.0f, IEnumerable<string> tags = null)
{
damage = MathHelper.Clamp(damage+Rand.Range(-10.0f, 10.0f), 0.0f, 100.0f);
var sounds = damageSounds.FindAll(s =>
damage = MathHelper.Clamp(damage + Rand.Range(-10.0f, 10.0f), 0.0f, 100.0f);
var sounds = damageSounds.FindAll(s =>
s.damageRange == null ||
(damage >= s.damageRange.X &&
damage <= s.damageRange.Y) &&
(damage >= s.damageRange.X &&
damage <= s.damageRange.Y) &&
s.damageType == damageType &&
(tags == null ? string.IsNullOrEmpty(s.requiredTag) : tags.Contains(s.requiredTag)));
if (!sounds.Any()) return;
int selectedSound = Rand.Int(sounds.Count);
sounds[selectedSound].sound.Play(1.0f, range, position);
Debug.WriteLine("playing: " + sounds[selectedSound].sound);
sounds[selectedSound].sound.Play(1.0f, range, position, muffle: ShouldMuffleSound(Character.Controlled, position, range, null));
}
}