Renamed project folders from Subsurface to Barotrauma
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using NVorbis;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
|
||||
namespace Barotrauma.Sounds
|
||||
{
|
||||
class OggSound : IDisposable
|
||||
{
|
||||
//internal VorbisReader Reader { get; private set; }
|
||||
|
||||
//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
|
||||
{
|
||||
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))
|
||||
{
|
||||
int bufferSize = (int)reader.TotalSamples;
|
||||
|
||||
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;
|
||||
|
||||
//alSourceId = AL.GenSource();
|
||||
AL.BufferData(sound.alBufferId, reader.Channels == 1 ? ALFormat.Mono16 : ALFormat.Stereo16, sound.castBuffer,
|
||||
readSamples * sizeof(short), reader.SampleRate);
|
||||
|
||||
ALHelper.Check();
|
||||
}
|
||||
|
||||
//AL.Source(alSourceId, ALSourcei.Buffer, alBufferId);
|
||||
|
||||
//if (ALHelper.XRam.IsInitialized)
|
||||
//{
|
||||
// ALHelper.XRam.SetBufferMode(bufferCount, ref alBufferId, XRamExtension.XRamStorage.Hardware);
|
||||
// ALHelper.Check();
|
||||
//}
|
||||
|
||||
//Volume = 1;
|
||||
|
||||
//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;
|
||||
|
||||
}
|
||||
|
||||
public void SetBufferData(int alBufferId)
|
||||
{
|
||||
AL.BufferData(alBufferId, format, castBuffer,
|
||||
castBuffer.Length * sizeof(short), sampleRate);
|
||||
}
|
||||
|
||||
static void CastBuffer(float[] inBuffer, short[] outBuffer, int length)
|
||||
{
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
int temp = (int)(32767f * inBuffer[i]);
|
||||
if (temp > short.MaxValue) temp = short.MaxValue;
|
||||
else if (temp < short.MinValue) temp = short.MinValue;
|
||||
outBuffer[i] = (short)temp;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
//var state = AL.GetSourceState(alSourceId);
|
||||
//if (state == ALSourceState.Playing || state == ALSourceState.Paused)
|
||||
// Stop();
|
||||
System.Diagnostics.Debug.WriteLine(alBufferId);
|
||||
//AL.DeleteSource(alSourceId);
|
||||
AL.DeleteBuffer(alBufferId);
|
||||
|
||||
//if (ALHelper.Efx.IsInitialized)
|
||||
// ALHelper.Efx.DeleteFilter(alFilterId);
|
||||
|
||||
ALHelper.Check();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,525 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using NVorbis;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
|
||||
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()
|
||||
{
|
||||
ALError error;
|
||||
if ((error = AL.GetError()) != ALError.NoError)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("OpenAL error: " + AL.GetErrorString(error));
|
||||
#else
|
||||
|
||||
DebugConsole.NewMessage("OpenAL error: " + AL.GetErrorString(error), Microsoft.Xna.Framework.Color.Red);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 OggStream(string filename, int bufferCount = DefaultBufferCount) : this(File.OpenRead(filename), bufferCount) { }
|
||||
public OggStream(Stream stream, int bufferCount = DefaultBufferCount)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public void Resume()
|
||||
{
|
||||
if (AL.GetSourceState(alSourceId) != ALSourceState.Paused)
|
||||
return;
|
||||
|
||||
OggStreamer.Instance.AddStream(this);
|
||||
AL.SourcePlay(alSourceId);
|
||||
ALHelper.Check();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
void StopPlayback()
|
||||
{
|
||||
AL.SourceStop(alSourceId);
|
||||
ALHelper.Check();
|
||||
}
|
||||
|
||||
void Empty()
|
||||
{
|
||||
int queued;
|
||||
AL.GetSource(alSourceId, ALGetSourcei.BuffersQueued, out queued);
|
||||
ALHelper.Check();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// Try turning it off again?
|
||||
AL.SourceStop(alSourceId);
|
||||
ALHelper.Check();
|
||||
|
||||
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();
|
||||
|
||||
// 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();
|
||||
|
||||
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();
|
||||
int processed;
|
||||
AL.GetSource(stream.alSourceId, ALGetSourcei.BuffersProcessed, out processed);
|
||||
ALHelper.Check();
|
||||
|
||||
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();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Sounds;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class Sound
|
||||
{
|
||||
public static Vector3 CameraPos;
|
||||
|
||||
private static List<Sound> loadedSounds = new List<Sound>();
|
||||
|
||||
private static OggStream stream;
|
||||
|
||||
private OggSound oggSound;
|
||||
|
||||
string filePath;
|
||||
|
||||
private int alSourceId;
|
||||
|
||||
private bool destroyOnGameEnd;
|
||||
|
||||
|
||||
//public float Volume
|
||||
//{
|
||||
// set { SoundManager.Volume(sourceIndex, value); }
|
||||
//}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return new Sound(file, destroyOnGameEnd);
|
||||
}
|
||||
|
||||
public int Play(float volume = 1.0f)
|
||||
{
|
||||
if (volume <= 0.0f) return -1;
|
||||
|
||||
alSourceId = SoundManager.Play(this, volume);
|
||||
return alSourceId;
|
||||
}
|
||||
|
||||
public int Play(float baseVolume, float range, Vector2 position)
|
||||
{
|
||||
//position = new Vector2(position.X - CameraPos.X, position.Y - CameraPos.Y);
|
||||
|
||||
//volume = (range == 0.0f) ? 0.0f : MathHelper.Clamp(volume * (range - position.Length())/range, 0.0f, 1.0f);
|
||||
|
||||
Vector2 relativePos = GetRelativePosition(position);
|
||||
float volume = GetVolume(relativePos, range, baseVolume);
|
||||
|
||||
if (volume <= 0.0f) return -1;
|
||||
|
||||
alSourceId = SoundManager.Play(this, relativePos/100.0f, volume);
|
||||
|
||||
return alSourceId;
|
||||
|
||||
//if (newIndex == -1) return -1;
|
||||
|
||||
//return UpdatePosition(newIndex, position, range, volume);
|
||||
}
|
||||
|
||||
//public int Play(float volume, float range, Body body)
|
||||
//{
|
||||
// //Vector2 bodyPosition = ConvertUnits.ToDisplayUnits(body.Position);
|
||||
// //bodyPosition.Y = -bodyPosition.Y;
|
||||
|
||||
|
||||
// alSourceId = Play(volume, range, ConvertUnits.ToDisplayUnits(body.Position));
|
||||
|
||||
// return alSourceId;
|
||||
//}
|
||||
|
||||
private float GetVolume(Vector2 relativePosition, float range, float baseVolume)
|
||||
{
|
||||
float volume = (range == 0.0f) ? 0.0f : MathHelper.Clamp(baseVolume * (range - relativePosition.Length()) / range, 0.0f, 1.0f);
|
||||
|
||||
return volume;
|
||||
}
|
||||
|
||||
private Vector2 GetRelativePosition(Vector2 position)
|
||||
{
|
||||
return new Vector2(position.X - CameraPos.X, position.Y - CameraPos.Y);
|
||||
}
|
||||
|
||||
//public static int UpdatePosition(int sourceIndex, Vector2 position, float range, float baseVolume = 1.0f)
|
||||
//{
|
||||
// position = new Vector2(position.X - CameraPos.X, position.Y - CameraPos.Y);
|
||||
// float volume = (range == 0.0f) ? 0.0f : MathHelper.Clamp(baseVolume * (range - position.Length())/range, 0.0f, 1.0f);
|
||||
|
||||
// if (volume <= 0.0f)
|
||||
// {
|
||||
// if (sourceIndex > 0)
|
||||
// {
|
||||
// SoundManager.Stop(sourceIndex);
|
||||
// sourceIndex = -1;
|
||||
// }
|
||||
|
||||
// return sourceIndex;
|
||||
// }
|
||||
|
||||
// SoundManager.UpdateSoundPosition(sourceIndex, position, volume, volume);
|
||||
|
||||
// return sourceIndex;
|
||||
//}
|
||||
|
||||
//public int UpdatePosition(int sourceIndex, Body body, float range, float baseVolume = 1.0f)
|
||||
//{
|
||||
// Vector2 bodyPosition = ConvertUnits.ToDisplayUnits(body.Position);
|
||||
// bodyPosition.Y = -bodyPosition.Y;
|
||||
// return UpdatePosition(sourceIndex, bodyPosition, range, baseVolume);
|
||||
//}
|
||||
|
||||
public int Loop(int sourceIndex, float volume)
|
||||
{
|
||||
if (volume <= 0.0f)
|
||||
{
|
||||
if (sourceIndex > 0)
|
||||
{
|
||||
SoundManager.Stop(sourceIndex);
|
||||
sourceIndex = -1;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
SoundManager.Stop(sourceIndex);
|
||||
sourceIndex = -1;
|
||||
}
|
||||
|
||||
return sourceIndex;
|
||||
}
|
||||
|
||||
alSourceId = SoundManager.Loop(this, sourceIndex, relativePos / 100.0f, volume);
|
||||
return alSourceId;
|
||||
}
|
||||
|
||||
public bool IsPlaying
|
||||
{
|
||||
get
|
||||
{
|
||||
return SoundManager.IsPlaying(alSourceId);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
foreach (Sound s in loadedSounds)
|
||||
{
|
||||
if (s.oggSound == oggSound) return;
|
||||
}
|
||||
|
||||
SoundManager.ClearAlSource(AlBufferId);
|
||||
ALHelper.Check();
|
||||
|
||||
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) return;
|
||||
stream.Volume = volume;
|
||||
}
|
||||
|
||||
public static void StopStream()
|
||||
{
|
||||
if (stream != null) SoundManager.StopStream();
|
||||
}
|
||||
|
||||
public static void Dispose()
|
||||
{
|
||||
SoundManager.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Xna.Framework;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using OpenTK.Audio;
|
||||
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Sounds
|
||||
{
|
||||
static class SoundManager
|
||||
{
|
||||
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];
|
||||
|
||||
private static AudioContext AC;
|
||||
|
||||
private static OggStreamer oggStreamer;
|
||||
private static OggStream oggStream;
|
||||
|
||||
public static float MasterVolume = 1.0f;
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
var availableDevices = AudioContext.AvailableDevices;
|
||||
if (availableDevices.Count == 0)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static int Play(Sound sound, float volume = 1.0f)
|
||||
{
|
||||
if (Disabled) return -1;
|
||||
return Play(sound, Vector2.Zero, volume, 0.0f);
|
||||
}
|
||||
|
||||
public static int Play(Sound sound, Vector2 position, float volume = 1.0f, float lowPassGain = 0.0f, bool loop=false)
|
||||
{
|
||||
if (Disabled || sound.AlBufferId == -1) return -1;
|
||||
|
||||
int sourceIndex = FindAudioSource(volume);
|
||||
if (sourceIndex > -1)
|
||||
{
|
||||
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);
|
||||
|
||||
UpdateSoundPosition(sourceIndex, position, volume);
|
||||
|
||||
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)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
//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++)
|
||||
{
|
||||
float vol;
|
||||
OpenTK.Audio.OpenAL.AL.GetSource(alSources[i], ALSourcef.Gain, out vol);
|
||||
if (vol < lowestVolume)
|
||||
{
|
||||
quietestSourceIndex = i;
|
||||
lowestVolume = vol;
|
||||
}
|
||||
}
|
||||
|
||||
if (quietestSourceIndex > -1)
|
||||
{
|
||||
Stop(quietestSourceIndex);
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
volume = 0.0f;
|
||||
}
|
||||
|
||||
if (sourceIndex<1 || soundsPlaying[sourceIndex] != sound)
|
||||
{
|
||||
sourceIndex = Play(sound, position, volume, 0.0f, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateSoundPosition(sourceIndex, position, volume);
|
||||
AL.Source(alSources[sourceIndex], ALSourceb.Looping, true);
|
||||
}
|
||||
|
||||
ALHelper.Check();
|
||||
return sourceIndex;
|
||||
}
|
||||
|
||||
public static void Pause(int sourceIndex)
|
||||
{
|
||||
if (Disabled) return;
|
||||
|
||||
if (AL.GetSourceState(alSources[sourceIndex]) != ALSourceState.Playing)
|
||||
return;
|
||||
|
||||
AL.SourcePause(alSources[sourceIndex]);
|
||||
ALHelper.Check();
|
||||
}
|
||||
|
||||
public static void Resume(int sourceIndex)
|
||||
{
|
||||
if (Disabled) return;
|
||||
|
||||
if (AL.GetSourceState(alSources[sourceIndex]) != ALSourceState.Paused)
|
||||
return;
|
||||
|
||||
AL.SourcePlay(alSources[sourceIndex]);
|
||||
ALHelper.Check();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
AL.SourceStop(alSources[sourceIndex]);
|
||||
AL.Source(alSources[sourceIndex], ALSourceb.Looping, false);
|
||||
|
||||
soundsPlaying[sourceIndex] = null;
|
||||
}
|
||||
}
|
||||
|
||||
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 IsPaused(int sourceIndex)
|
||||
{
|
||||
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
|
||||
{
|
||||
if (Disabled) return;
|
||||
if (ALHelper.Efx.IsInitialized)
|
||||
{
|
||||
lowPassHfGain = value;
|
||||
for (int i = 0; i < DefaultSourceCount; i++)
|
||||
{
|
||||
//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;
|
||||
|
||||
ALHelper.Efx.Filter(lowpassFilterId, OpenTK.Audio.OpenAL.EfxFilterf.LowpassGainHF, lowPassHfGain = value);
|
||||
ALHelper.Efx.BindFilterToSource(alSources[i], lowpassFilterId);
|
||||
ALHelper.Check();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void UpdateSoundPosition(int sourceIndex, Vector2 position, float baseVolume = 1.0f)
|
||||
{
|
||||
if (sourceIndex < 1 || Disabled) return;
|
||||
|
||||
if (!MathUtils.IsValid(position))
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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++)
|
||||
{
|
||||
var state = OpenTK.Audio.OpenAL.AL.GetSourceState(alSources[i]);
|
||||
if (state == OpenTK.Audio.OpenAL.ALSourceState.Playing || state == OpenTK.Audio.OpenAL.ALSourceState.Paused)
|
||||
{
|
||||
Stop(i);
|
||||
}
|
||||
|
||||
OpenTK.Audio.OpenAL.AL.DeleteSource(alSources[i]);
|
||||
|
||||
ALHelper.Check();
|
||||
}
|
||||
|
||||
if (oggStream != null)
|
||||
{
|
||||
oggStream.Stop();
|
||||
oggStream.Dispose();
|
||||
|
||||
oggStream = null;
|
||||
}
|
||||
|
||||
if (oggStreamer != null)
|
||||
{
|
||||
oggStreamer.Dispose();
|
||||
oggStreamer = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,428 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Sounds;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public enum DamageSoundType
|
||||
{
|
||||
None,
|
||||
StructureBlunt, StructureSlash,
|
||||
LimbBlunt, LimbSlash, LimbArmor
|
||||
}
|
||||
|
||||
public struct DamageSound
|
||||
{
|
||||
//the range of inflicted damage where the sound can be played
|
||||
//(10.0f, 30.0f) would be played when the inflicted damage is between 10 and 30
|
||||
public readonly Vector2 damageRange;
|
||||
|
||||
public readonly DamageSoundType damageType;
|
||||
|
||||
public readonly Sound sound;
|
||||
|
||||
public readonly string requiredTag;
|
||||
|
||||
public DamageSound(Sound sound, Vector2 damageRange, DamageSoundType damageType, string requiredTag = "")
|
||||
{
|
||||
this.sound = sound;
|
||||
this.damageRange = damageRange;
|
||||
this.damageType = damageType;
|
||||
|
||||
this.requiredTag = requiredTag;
|
||||
}
|
||||
}
|
||||
|
||||
public class BackgroundMusic
|
||||
{
|
||||
public readonly string file;
|
||||
public readonly string type;
|
||||
|
||||
public readonly Vector2 priorityRange;
|
||||
|
||||
public BackgroundMusic(string file, string type, Vector2 priorityRange)
|
||||
{
|
||||
this.file = file;
|
||||
this.type = type;
|
||||
this.priorityRange = priorityRange;
|
||||
}
|
||||
}
|
||||
|
||||
static class SoundPlayer
|
||||
{
|
||||
private static ILookup<string, Sound> miscSounds;
|
||||
|
||||
//music
|
||||
public static float MusicVolume = 1.0f;
|
||||
private const float MusicLerpSpeed = 0.1f;
|
||||
|
||||
private static BackgroundMusic currentMusic;
|
||||
private static BackgroundMusic targetMusic;
|
||||
private static BackgroundMusic[] musicClips;
|
||||
private static float currMusicVolume;
|
||||
|
||||
//ambience
|
||||
private static Sound[] waterAmbiences = new Sound[2];
|
||||
private static int[] waterAmbienceIndexes = new int[2];
|
||||
|
||||
private static float ambientSoundTimer;
|
||||
private static Vector2 ambientSoundInterval = new Vector2(20.0f, 40.0f); //x = min, y = max
|
||||
|
||||
//misc
|
||||
public static Sound[] flowSounds = new Sound[3];
|
||||
public static Sound[] SplashSounds = new Sound[10];
|
||||
|
||||
private static List<DamageSound> damageSounds;
|
||||
|
||||
private static Sound startDrone;
|
||||
|
||||
public static bool Initialized;
|
||||
|
||||
public static string OverrideMusicType
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public static int SoundCount;
|
||||
|
||||
public static IEnumerable<object> Init()
|
||||
{
|
||||
OverrideMusicType = null;
|
||||
|
||||
XDocument doc = ToolBox.TryLoadXml("Content/Sounds/sounds.xml");
|
||||
if (doc == null) yield return CoroutineStatus.Failure;
|
||||
|
||||
SoundCount = 16 + doc.Root.Elements().Count();
|
||||
|
||||
startDrone = Sound.Load("Content/Sounds/startDrone.ogg", false);
|
||||
startDrone.Play();
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
waterAmbiences[0] = Sound.Load("Content/Sounds/Water/WaterAmbience1.ogg", false);
|
||||
yield return CoroutineStatus.Running;
|
||||
waterAmbiences[1] = Sound.Load("Content/Sounds/Water/WaterAmbience2.ogg", false);
|
||||
yield return CoroutineStatus.Running;
|
||||
flowSounds[0] = Sound.Load("Content/Sounds/Water/FlowSmall.ogg", false);
|
||||
yield return CoroutineStatus.Running;
|
||||
flowSounds[1] = Sound.Load("Content/Sounds/Water/FlowMedium.ogg", false);
|
||||
yield return CoroutineStatus.Running;
|
||||
flowSounds[2] = Sound.Load("Content/Sounds/Water/FlowLarge.ogg", false);
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
for (int i = 0; i < 10; i++ )
|
||||
{
|
||||
SplashSounds[i] = Sound.Load("Content/Sounds/Water/Splash"+(i)+".ogg", false);
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
var xMusic = doc.Root.Elements("music").ToList();
|
||||
|
||||
if (xMusic.Any())
|
||||
{
|
||||
musicClips = new BackgroundMusic[xMusic.Count];
|
||||
int i = 0;
|
||||
foreach (XElement element in xMusic)
|
||||
{
|
||||
string file = ToolBox.GetAttributeString(element, "file", "");
|
||||
string type = ToolBox.GetAttributeString(element, "type", "").ToLowerInvariant();
|
||||
Vector2 priority = ToolBox.GetAttributeVector2(element, "priorityrange", new Vector2(0.0f, 100.0f));
|
||||
|
||||
musicClips[i] = new BackgroundMusic(file, type, priority);
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
List<KeyValuePair<string, Sound>> miscSoundList = new List<KeyValuePair<string, Sound>>();
|
||||
damageSounds = new List<DamageSound>();
|
||||
|
||||
foreach (XElement subElement in doc.Root.Elements())
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "music":
|
||||
continue;
|
||||
case "damagesound":
|
||||
Sound damageSound = Sound.Load(ToolBox.GetAttributeString(subElement, "file", ""), false);
|
||||
if (damageSound == null) continue;
|
||||
|
||||
DamageSoundType damageSoundType = DamageSoundType.None;
|
||||
Enum.TryParse<DamageSoundType>(ToolBox.GetAttributeString(subElement, "damagesoundtype", "None"), false, out damageSoundType);
|
||||
|
||||
damageSounds.Add(new DamageSound(
|
||||
damageSound,
|
||||
ToolBox.GetAttributeVector2(subElement, "damagerange", new Vector2(0.0f, 100.0f)),
|
||||
damageSoundType,
|
||||
ToolBox.GetAttributeString(subElement, "requiredtag", "")));
|
||||
|
||||
break;
|
||||
default:
|
||||
Sound sound = Sound.Load(ToolBox.GetAttributeString(subElement, "file", ""), false);
|
||||
if (sound != null)
|
||||
{
|
||||
miscSoundList.Add(new KeyValuePair<string, Sound>(subElement.Name.ToString().ToLowerInvariant(), sound));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
miscSounds = miscSoundList.ToLookup(kvp => kvp.Key, kvp => kvp.Value);
|
||||
|
||||
Initialized = true;
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static void Update()
|
||||
{
|
||||
UpdateMusic();
|
||||
|
||||
if (startDrone != null && !startDrone.IsPlaying)
|
||||
{
|
||||
startDrone.Remove();
|
||||
startDrone = null;
|
||||
}
|
||||
|
||||
//stop submarine ambient sounds if no sub is loaded
|
||||
if (Submarine.MainSub == null)
|
||||
{
|
||||
for (int i = 0; i < waterAmbienceIndexes.Length; i++)
|
||||
{
|
||||
if (waterAmbienceIndexes[i] <= 0) continue;
|
||||
|
||||
SoundManager.Stop(waterAmbienceIndexes[i]);
|
||||
waterAmbienceIndexes[i] = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
float ambienceVolume = 0.8f;
|
||||
float lowpassHFGain = 1.0f;
|
||||
if (Character.Controlled != null)
|
||||
{
|
||||
AnimController animController = Character.Controlled.AnimController;
|
||||
if (animController.HeadInWater)
|
||||
{
|
||||
ambienceVolume = 1.0f;
|
||||
ambienceVolume += animController.Limbs[0].LinearVelocity.Length();
|
||||
|
||||
lowpassHFGain = 0.2f;
|
||||
}
|
||||
|
||||
lowpassHFGain *= Character.Controlled.LowPassMultiplier;
|
||||
}
|
||||
|
||||
//how fast the sub is moving, scaled to 0.0 -> 1.0
|
||||
float movementSoundVolume = 0.0f;
|
||||
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
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)
|
||||
{
|
||||
float dist = Vector2.Distance(GameMain.GameScreen.Cam.WorldViewCenter, sub.WorldPosition);
|
||||
movementFactor = movementFactor / Math.Max(dist / 1000.0f, 1.0f);
|
||||
}
|
||||
|
||||
movementSoundVolume = Math.Max(movementSoundVolume, movementFactor);
|
||||
}
|
||||
|
||||
if (ambientSoundTimer > 0.0f)
|
||||
{
|
||||
ambientSoundTimer -= (float)Timing.Step;
|
||||
}
|
||||
else
|
||||
{
|
||||
PlaySound(
|
||||
"ambient",
|
||||
Rand.Range(0.5f, 1.0f),
|
||||
1000.0f,
|
||||
new Vector2(Sound.CameraPos.X, Sound.CameraPos.Y) + Rand.Vector(100.0f));
|
||||
|
||||
ambientSoundTimer = Rand.Range(ambientSoundInterval.X, ambientSoundInterval.Y);
|
||||
}
|
||||
|
||||
SoundManager.LowPassHFGain = lowpassHFGain;
|
||||
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)
|
||||
{
|
||||
var matchingSounds = miscSounds[soundTag].ToList();
|
||||
if (matchingSounds.Count == 0) return null;
|
||||
|
||||
return matchingSounds[Rand.Int(matchingSounds.Count)];
|
||||
}
|
||||
|
||||
public static void PlaySound(string soundTag, float volume = 1.0f)
|
||||
{
|
||||
var sound = GetSound(soundTag);
|
||||
if (sound != null) sound.Play(volume);
|
||||
}
|
||||
|
||||
public static void PlaySound(string soundTag, float volume, float range, Vector2 position)
|
||||
{
|
||||
var sound = GetSound(soundTag);
|
||||
if (sound != null) sound.Play(volume, range, position);
|
||||
}
|
||||
|
||||
private static void UpdateMusic()
|
||||
{
|
||||
if (musicClips == null) return;
|
||||
|
||||
List<BackgroundMusic> suitableMusic = GetSuitableMusicClips();
|
||||
|
||||
if (suitableMusic.Count == 0)
|
||||
{
|
||||
targetMusic = null;
|
||||
}
|
||||
else if (!suitableMusic.Contains(currentMusic))
|
||||
{
|
||||
int index = Rand.Int(suitableMusic.Count);
|
||||
|
||||
if (currentMusic == null || suitableMusic[index].file != currentMusic.file)
|
||||
{
|
||||
targetMusic = suitableMusic[index];
|
||||
}
|
||||
}
|
||||
|
||||
if (targetMusic == null || currentMusic == null || targetMusic.file != currentMusic.file)
|
||||
{
|
||||
currMusicVolume = MathHelper.Lerp(currMusicVolume, 0.0f, MusicLerpSpeed);
|
||||
if (currentMusic != null) Sound.StreamVolume(currMusicVolume);
|
||||
|
||||
if (currMusicVolume < 0.01f)
|
||||
{
|
||||
Sound.StopStream();
|
||||
|
||||
try
|
||||
{
|
||||
if (targetMusic != null) Sound.StartStream(targetMusic.file, currMusicVolume);
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
DebugConsole.ThrowError("Music clip " + targetMusic.file + " not found!", e);
|
||||
}
|
||||
|
||||
currentMusic = targetMusic;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
currMusicVolume = MathHelper.Lerp(currMusicVolume, MusicVolume, MusicLerpSpeed);
|
||||
Sound.StreamVolume(currMusicVolume);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SwitchMusic()
|
||||
{
|
||||
var suitableMusic = GetSuitableMusicClips();
|
||||
|
||||
if (suitableMusic.Count > 1)
|
||||
{
|
||||
targetMusic = suitableMusic.Find(m => m != currentMusic);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<BackgroundMusic> GetSuitableMusicClips()
|
||||
{
|
||||
string musicType = "default";
|
||||
if (OverrideMusicType != null)
|
||||
{
|
||||
musicType = OverrideMusicType;
|
||||
}
|
||||
else if (Character.Controlled != null &&
|
||||
Level.Loaded != null && Level.Loaded.Ruins != null &&
|
||||
Level.Loaded.Ruins.Any(r => r.Area.Contains(Character.Controlled.WorldPosition)))
|
||||
{
|
||||
musicType = "ruins";
|
||||
}
|
||||
else if ((Character.Controlled != null && Character.Controlled.Submarine != null && Character.Controlled.Submarine.AtDamageDepth) ||
|
||||
(Screen.Selected == GameMain.GameScreen && GameMain.GameScreen.Cam.Position.Y < SubmarineBody.DamageDepth))
|
||||
{
|
||||
musicType = "deep";
|
||||
}
|
||||
else
|
||||
{
|
||||
Task criticalTask = null;
|
||||
if (GameMain.GameSession != null && GameMain.GameSession.TaskManager != null)
|
||||
{
|
||||
foreach (Task task in GameMain.GameSession.TaskManager.Tasks)
|
||||
{
|
||||
if (!task.IsStarted) continue;
|
||||
if (criticalTask == null || task.Priority > criticalTask.Priority)
|
||||
{
|
||||
criticalTask = task;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (criticalTask != null)
|
||||
{
|
||||
var suitableClips =
|
||||
musicClips.Where(music =>
|
||||
music != null &&
|
||||
music.type == criticalTask.MusicType &&
|
||||
music.priorityRange.X < criticalTask.Priority &&
|
||||
music.priorityRange.Y > criticalTask.Priority).ToList();
|
||||
|
||||
if (suitableClips.Count > 0) return suitableClips;
|
||||
}
|
||||
}
|
||||
|
||||
return musicClips.Where(music => music != null && music.type == musicType).ToList();
|
||||
}
|
||||
|
||||
public static void PlaySplashSound(Vector2 worldPosition, float strength)
|
||||
{
|
||||
int splashIndex = MathHelper.Clamp((int)(strength + Rand.Range(-2,2)), 0, SplashSounds.Length-1);
|
||||
|
||||
SplashSounds[splashIndex].Play(1.0f, 800.0f, worldPosition);
|
||||
}
|
||||
|
||||
public static void PlayDamageSound(DamageSoundType damageType, float damage, PhysicsBody body)
|
||||
{
|
||||
Vector2 bodyPosition = body.DrawPosition;
|
||||
|
||||
PlayDamageSound(damageType, damage, bodyPosition, 800.0f);
|
||||
}
|
||||
|
||||
public static void PlayDamageSound(DamageSoundType damageType, float damage, Vector2 position, float range = 2000.0f, List<string> tags = null)
|
||||
{
|
||||
damage = MathHelper.Clamp(damage+Rand.Range(-10.0f, 10.0f), 0.0f, 100.0f);
|
||||
var sounds = damageSounds.FindAll(s =>
|
||||
damage >= s.damageRange.X &&
|
||||
damage <= s.damageRange.Y &&
|
||||
s.damageType == damageType &&
|
||||
(string.IsNullOrEmpty(s.requiredTag) || (tags != null && 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user