38f1ddb...178a853: v0.8.9.1, removed content folder
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user