(61d00a474) v0.9.7.1

This commit is contained in:
Regalis
2020-03-04 13:04:10 +01:00
parent 3c50efa5c9
commit 3c09ebe02f
5086 changed files with 786063 additions and 295871 deletions
@@ -0,0 +1,127 @@
using System;
using OpenAL;
using NVorbis;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Sounds
{
public class OggSound : Sound
{
private VorbisReader reader;
//key = sample rate, value = filter
private static Dictionary<int, BiQuad> muffleFilters = new Dictionary<int, BiQuad>();
private static List<float> playbackAmplitude;
private const int AMPLITUDE_SAMPLE_COUNT = 4410; //100ms in a 44100hz file
public OggSound(SoundManager owner, string filename, bool stream, XElement xElement) : base(owner, filename, stream, true, xElement)
{
filename = filename.CleanUpPath();
if (!ToolBox.IsProperFilenameCase(filename))
{
DebugConsole.ThrowError("Sound file \"" + filename + "\" has incorrect case!");
}
reader = new VorbisReader(filename);
ALFormat = reader.Channels == 1 ? Al.FormatMono16 : Al.FormatStereo16;
SampleRate = reader.SampleRate;
if (!stream)
{
int bufferSize = (int)reader.TotalSamples * reader.Channels;
float[] floatBuffer = new float[bufferSize];
short[] shortBuffer = new short[bufferSize];
int readSamples = reader.ReadSamples(floatBuffer, 0, bufferSize);
playbackAmplitude = new List<float>();
for (int i=0;i<bufferSize;i+=reader.Channels*AMPLITUDE_SAMPLE_COUNT)
{
float maxAmplitude = 0.0f;
for (int j=i;j<i+reader.Channels*AMPLITUDE_SAMPLE_COUNT;j++)
{
if (j >= bufferSize) { break; }
maxAmplitude = Math.Max(maxAmplitude, Math.Abs(floatBuffer[j]));
}
playbackAmplitude.Add(maxAmplitude);
}
CastBuffer(floatBuffer, shortBuffer, readSamples);
Al.BufferData(ALBuffer, ALFormat, shortBuffer,
readSamples * sizeof(short), SampleRate);
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to set buffer data for non-streamed audio! "+Al.GetErrorString(alError));
}
MuffleBuffer(floatBuffer, SampleRate, reader.Channels);
CastBuffer(floatBuffer, shortBuffer, readSamples);
Al.BufferData(ALMuffledBuffer, ALFormat, shortBuffer,
readSamples * sizeof(short), SampleRate);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to set buffer data for non-streamed audio! " + Al.GetErrorString(alError));
}
reader.Dispose();
}
}
public override float GetAmplitudeAtPlaybackPos(int playbackPos)
{
if (playbackAmplitude == null || playbackAmplitude.Count == 0) { return 0.0f; }
int index = playbackPos / AMPLITUDE_SAMPLE_COUNT;
if (index < 0) { return 0.0f; }
if (index >= playbackAmplitude.Count) { index = playbackAmplitude.Count - 1; }
return playbackAmplitude[index];
}
public override int FillStreamBuffer(int samplePos, short[] buffer)
{
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 sampleRate, int channelCount)
{
if (!muffleFilters.TryGetValue(sampleRate, out BiQuad filter))
{
filter = new LowpassFilter(sampleRate, 800);
muffleFilters.Add(sampleRate, filter);
}
filter.Process(buffer);
}
public override void Dispose()
{
if (Stream)
{
reader.Dispose();
}
base.Dispose();
}
}
}
@@ -0,0 +1,467 @@
/***
MIT License
Copyright (c) 2018 Nathan Glover
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
****************
Further modified for use in Barotrauma.
Original source code at https://github.com/NathanielGlover/OpenAL.NETCore/
***/
using System;
using System.Runtime.InteropServices;
namespace OpenAL
{
public class Al
{
#if OSX
public const string OpenAlDll = "/System/Library/Frameworks/OpenAL.framework/OpenAL";
#elif LINUX
public const string OpenAlDll = "libopenal.so.1";
#elif WINDOWS
#if X86
public const string OpenAlDll = "soft_oal_x86.dll";
#elif X64
public const string OpenAlDll = "soft_oal_x64.dll";
#endif
#endif
#region Enum
public const int None = 0;
public const int False = 0;
public const int True = 1;
public const int SourceRelative = 0x202;
public const int ConeInnerAngle = 0x1001;
public const int ConeOuterAngle = 0x1002;
public const int Pitch = 0x1003;
public const int Position = 0x1004;
public const int Direction = 0x1005;
public const int Velocity = 0x1006;
public const int Looping = 0x1007;
public const int Buffer = 0x1009;
public const int Gain = 0x100A;
public const int MinGain = 0x100D;
public const int MaxGain = 0x100E;
public const int Orientation = 0x100F;
public const int SourceState = 0x1010;
public const int Initial = 0x1011;
public const int Playing = 0x1012;
public const int Paused = 0x1013;
public const int Stopped = 0x1014;
public const int BuffersQueued = 0x1015;
public const int BuffersProcessed = 0x1016;
public const int SecOffset = 0x1024;
public const int SampleOffset = 0x1025;
public const int ByteOffset = 0x1026;
public const int SourceType = 0x1027;
public const int Static = 0x1028;
public const int Streaming = 0x1029;
public const int Undetermined = 0x1030;
public const int FormatMono8 = 0x1100;
public const int FormatMono16 = 0x1101;
public const int FormatStereo8 = 0x1102;
public const int FormatStereo16 = 0x1103;
public const int ReferenceDistance = 0x1020;
public const int RolloffFactor = 0x1021;
public const int ConeOuterGain = 0x1022;
public const int MaxDistance = 0x1023;
public const int Frequency = 0x2001;
public const int Bits = 0x2002;
public const int Channels = 0x2003;
public const int Size = 0x2004;
public const int Unused = 0x2010;
public const int Pending = 0x2011;
public const int Processed = 0x2012;
public const int NoError = False;
public const int InvalidName = 0xA001;
public const int InvalidEnum = 0xA002;
public const int InvalidValue = 0xA003;
public const int InvalidOperation = 0xA004;
public const int OutOfMemory = 0xA005;
public const int Vendor = 0xB001;
public const int Version = 0xB002;
public const int Renderer = 0xB003;
public const int Extensions = 0xB004;
public const int EnumDopplerFactor = 0xC000;
public const int EnumDopplerVelocity = 0xC001;
public const int EnumSpeedOfSound = 0xC003;
public const int EnumDistanceModel = 0xD000;
public const int InverseDistance = 0xD001;
public const int InverseDistanceClamped = 0xD002;
public const int LinearDistance = 0xD003;
public const int LinearDistanceClamped = 0xD004;
public const int ExponentDistance = 0xD005;
public const int ExponentDistanceClamped = 0xD006;
#endregion
#region Functions
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alEnable")]
public static extern void Enable(int capability);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alDisable")]
public static extern void Disable(int capability);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alIsEnabled")]
public static extern bool IsEnabled(int capability);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetString")]
private static extern IntPtr _GetString(int param);
public static string GetString(int param) => Marshal.PtrToStringAnsi(_GetString(param));
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetBooleanv")]
public static extern void GetBooleanv(int param, out bool data);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetIntegerv")]
public static extern void GetIntegerv(int param, out int data);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetFloatv")]
public static extern void GetFloatv(int param, out float data);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetDoublev")]
public static extern void GetDoublev(int param, out double data);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetBoolean")]
public static extern bool GetBoolean(int param);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetInteger")]
public static extern int GetInteger(int param);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetFloat")]
public static extern float GetFloat(int param);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetDouble")]
public static extern double GetDouble(int param);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetError")]
public static extern int GetError();
public static string GetErrorString(int error)
{
switch (error)
{
case NoError:
return "No error";
case InvalidName:
return "Invalid name";
case InvalidEnum:
return "Invalid enum";
case InvalidValue:
return "Invalid value";
case InvalidOperation:
return "Invalid operation";
case OutOfMemory:
return "Out of memory";
default:
return "Unknown error";
}
}
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alIsExtensionPresent")]
public static extern bool IsExtensionPresent(string extname);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetProcAddress")]
public static extern IntPtr GetProcAddress(string fname);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetEnumValue")]
public static extern int GetEnumValue(string ename);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alListenerf")]
public static extern void Listenerf(int param, float value);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alListener3f")]
public static extern void Listener3f(int param, float value1, float value2, float value3);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alListenerfv")]
public static extern void Listenerfv(int param, float[] values);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetListenerf")]
public static extern void GetListenerf(int param, out float value);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetListener3f")]
public static extern void GetListener3f(int param, out float value1, out float value2, out float value3);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetListenerfv")]
private static extern void _GetListenerfv(int param, IntPtr values);
public static void GetListenerfv(int param, out float[] values)
{
int len;
switch(param)
{
case Gain:
len = 1;
break;
case Position:
case Velocity:
len = 3;
break;
case Orientation:
len = 6;
break;
default:
len = 0;
break;
}
values = new float[len];
GCHandle arrayHandle = GCHandle.Alloc(values, GCHandleType.Pinned);
_GetListenerfv(param, arrayHandle.AddrOfPinnedObject());
arrayHandle.Free();
}
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGenSources")]
private static extern void _GenSources(int n, IntPtr sources);
public static void GenSources(int n, out uint[] sources)
{
sources = new uint[n];
GCHandle arrayHandle = GCHandle.Alloc(sources, GCHandleType.Pinned);
_GenSources(n, arrayHandle.AddrOfPinnedObject());
arrayHandle.Free();
}
public static void GenSource(out uint source)
{
GenSources(1, out var sources);
source = sources[0];
}
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alDeleteSources")]
public static extern void DeleteSources(int n, uint[] sources);
public static void DeleteSource(uint source) => DeleteSources(1, new[] {source});
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alIsSource")]
public static extern bool IsSource(uint sid);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSourcef")]
public static extern void Sourcef(uint sid, int param, float value);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSource3f")]
public static extern void Source3f(uint sid, int param, float value1, float value2, float value3);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSourcefv")]
public static extern void Sourcefv(uint sid, int param, float[] values);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSourcei")]
public static extern void Sourcei(uint sid, int param, int value);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSource3i")]
public static extern void Source3i(uint sid, int param, int value1, int value2, int value3);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSourceiv")]
public static extern void Sourceiv(uint sid, int param, int[] values);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetSourcef")]
public static extern void GetSourcef(uint sid, int param, out float value);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetSource3f")]
public static extern void GetSource3f(uint sid, int param, out float value1, out float value2, out float value3);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetSourcefv")]
private static extern void _GetSourcefv(uint sid, int param, IntPtr values);
public static void GetSourcefv(uint sid, int param, out float[] values)
{
int len;
switch(param)
{
case Pitch:
case Gain:
case MaxDistance:
case RolloffFactor:
case ReferenceDistance:
case MinGain:
case MaxGain:
case ConeOuterGain:
case ConeInnerAngle:
case ConeOuterAngle:
case SecOffset:
case SampleOffset:
case ByteOffset:
len = 1;
break;
case Position:
case Velocity:
case Direction:
len = 3;
break;
default:
len = 0;
break;
}
values = new float[len];
GCHandle arrayHandle = GCHandle.Alloc(values, GCHandleType.Pinned);
_GetSourcefv(sid, param, arrayHandle.AddrOfPinnedObject());
arrayHandle.Free();
}
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetSourcei")]
public static extern void GetSourcei(uint sid, int param, out int value);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetSource3i")]
public static extern void GetSource3i(uint sid, int param, out int value1, out int value2, out int value3);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetSourceiv")]
private static extern void _GetSourceiv(uint sid, int param, IntPtr values);
public static void GetSourceiv(uint sid, int param, out int[] values)
{
int len;
switch(param)
{
case MaxDistance:
case RolloffFactor:
case ReferenceDistance:
case ConeInnerAngle:
case ConeOuterAngle:
case SourceRelative:
case SourceType:
case Looping:
case Buffer:
case SourceState:
case BuffersQueued:
case BuffersProcessed:
case SecOffset:
case SampleOffset:
case ByteOffset:
len = 1;
break;
case Direction:
len = 3;
break;
default:
len = 0;
break;
}
values = new int[len];
GCHandle arrayHandle = GCHandle.Alloc(values, GCHandleType.Pinned);
_GetSourceiv(sid, param, arrayHandle.AddrOfPinnedObject());
arrayHandle.Free();
}
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSourcePlayv")]
public static extern void SourcePlayv(int ns, uint[] sids);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSourceStopv")]
public static extern void SourceStopv(int ns, uint[] sids);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSourceRewindv")]
public static extern void SourceRewindv(int ns, uint[] sids);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSourcePausev")]
public static extern void SourcePausev(int ns, uint[] sids);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSourcePlay")]
public static extern void SourcePlay(uint sid);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSourceStop")]
public static extern void SourceStop(uint sid);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSourceRewind")]
public static extern void SourceRewind(uint sid);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSourcePause")]
public static extern void SourcePause(uint sid);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSourceQueueBuffers")]
public static extern void SourceQueueBuffers(uint sid, int numEntries, uint[] bids);
public static void SourceQueueBuffer(uint sid, uint bid)
{
uint[] bids = new uint[1]; bids[0] = bid;
SourceQueueBuffers(sid, 1, bids);
}
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSourceUnqueueBuffers")]
public static extern void SourceUnqueueBuffers(uint sid, int numEntries, uint[] bids);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGenBuffers")]
private static extern void _GenBuffers(int n, IntPtr buffers);
public static void GenBuffers(int n, out uint[] buffers)
{
buffers = new uint[n];
GCHandle arrayHandle = GCHandle.Alloc(buffers, GCHandleType.Pinned);
_GenBuffers(n, arrayHandle.AddrOfPinnedObject());
arrayHandle.Free();
}
public static void GenBuffer(out uint buffer)
{
GenBuffers(1, out var buffers);
buffer = buffers[0];
}
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alDeleteBuffers")]
public static extern void DeleteBuffers(int n, uint[] buffers);
public static void DeleteBuffer(uint buffer) => DeleteBuffers(1, new[] {buffer});
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alIsBuffer")]
public static extern bool IsBuffer(uint bid);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alBufferData")]
public static extern void BufferData(uint bid, int format, IntPtr data, int size, int freq);
public static void BufferData<T>(uint bid, int format, T[] data, int len, int freq)
{
GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
BufferData(bid, format, handle.AddrOfPinnedObject(), len, freq);
handle.Free();
}
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alBufferi")]
public static extern void Bufferi(uint bid, int param, int value);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetBufferi")]
public static extern void GetBufferi(uint bid, int param, out int value);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alDopplerFactor")]
public static extern void DopplerFactor(float value);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alDopplerVelocity")]
public static extern void DopplerVelocity(float value);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSpeedOfSound")]
public static extern void SpeedOfSound(float value);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alDistanceModel")]
public static extern void DistanceModel(int distanceModel);
#endregion
}
}
@@ -0,0 +1,241 @@
/***
MIT License
Copyright (c) 2018 Nathan Glover
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
****************
Further modified for use in Barotrauma.
Original source code at https://github.com/NathanielGlover/OpenAL.NETCore/
***/
using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Text;
namespace OpenAL
{
public class Alc
{
#if OSX
public const string OpenAlDll = "/System/Library/Frameworks/OpenAL.framework/OpenAL";
#elif LINUX
public const string OpenAlDll = "libopenal.so.1";
#elif WINDOWS
#if X86
public const string OpenAlDll = "soft_oal_x86.dll";
#elif X64
public const string OpenAlDll = "soft_oal_x64.dll";
#endif
#endif
#region Enum
public const int False = 0;
public const int True = 1;
public const int Frequency = 0x1007;
public const int Refresh = 0x1008;
public const int Sync = 0x1009;
public const int MonoSources = 0x1010;
public const int StereoSources = 0x1011;
public const int NoError = False;
public const int InvalidDevice = 0xA001;
public const int InvalidContext = 0xA002;
public const int InvalidEnum = 0xA003;
public const int InvalidValue = 0xA004;
public const int OutOfMemory = 0xA005;
public const int DefaultDeviceSpecifier = 0x1004;
public const int DeviceSpecifier = 0x1005;
public const int Extensions = 0x1006;
public const int MajorVersion = 0x1000;
public const int MinorVersion = 0x1001;
public const int AttributesSize = 0x1002;
public const int AllAttributes = 0x1003;
public const int DefaultAllDevicesSpecifier = 0x1012;
public const int AllDevicesSpecifier = 0x1013;
public const int CaptureDeviceSpecifier = 0x310;
public const int CaptureDefaultDeviceSpecifier = 0x311;
public const int EnumCaptureSamples = 0x312;
#endregion
#region Context Management Functions
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcCreateContext")]
private static extern IntPtr _CreateContext(IntPtr device, IntPtr attrlist);
public static IntPtr CreateContext(IntPtr device, int[] attrList)
{
GCHandle handle = GCHandle.Alloc(attrList, GCHandleType.Pinned);
IntPtr retVal = _CreateContext(device, handle.AddrOfPinnedObject());
handle.Free();
return retVal;
}
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcMakeContextCurrent")]
public static extern bool MakeContextCurrent(IntPtr context);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcProcessContext")]
public static extern void ProcessContext(IntPtr context);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcSuspendContext")]
public static extern void SuspendContext(IntPtr context);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcDestroyContext")]
public static extern void DestroyContext(IntPtr context);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcGetCurrentContext")]
public static extern IntPtr GetCurrentContext();
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcGetContextsDevice")]
public static extern IntPtr GetContextsDevice(IntPtr context);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcOpenDevice")]
public static extern IntPtr OpenDevice(string deviceName);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcCloseDevice")]
public static extern bool CloseDevice(IntPtr device);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcGetError")]
public static extern int GetError(IntPtr device);
public static string GetErrorString(int errorCode)
{
switch (errorCode)
{
case NoError:
return "No error";
case InvalidContext:
return "Invalid context";
case InvalidDevice:
return "Invalid device";
case InvalidEnum:
return "Invalid enum";
case InvalidValue:
return "Invalid value";
case OutOfMemory:
return "Out of memory";
default:
return "Unknown error";
}
}
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcIsExtensionPresent")]
public static extern bool IsExtensionPresent(IntPtr device, string extname);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcGetProcAddress")]
public static extern IntPtr GetProcAddress(IntPtr device, string funcname);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcGetEnumValue")]
public static extern int GetEnumValue(IntPtr device, string enumname);
#endregion
#region Query Functions
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcGetString")]
private static extern IntPtr _GetString(IntPtr device, int param);
public static string GetString(IntPtr device, int param)
{
IntPtr strPtr = _GetString(device, param);
int strLen = 0;
while (Marshal.ReadByte(strPtr,strLen)!='\0') { strLen++; }
byte[] bytes = new byte[strLen];
Marshal.Copy(strPtr, bytes, 0, strLen);
return Encoding.UTF8.GetString(bytes);
}
public static IList<string> GetStringList(IntPtr device, int param)
{
List<string> retVal = new List<string>();
IntPtr strPtr = _GetString(device, param);
int strStart = 0;
int strEnd = 0;
byte currChar = Marshal.ReadByte(strPtr, strEnd);
if (currChar == '\0') { return retVal; }
byte prevChar = 255;
while (true) {
strEnd++;
prevChar = currChar;
currChar = Marshal.ReadByte(strPtr, strEnd);
if (currChar == '\0')
{
if (prevChar == '\0')
{
break;
}
byte[] bytes = new byte[strEnd-strStart];
Marshal.Copy(strPtr+strStart, bytes, 0, strEnd - strStart);
retVal.Add(Encoding.UTF8.GetString(bytes));
strStart = strEnd+1;
}
}
return retVal;
}
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcGetIntegerv")]
public static extern void GetIntegerv(IntPtr device, int param, int size, IntPtr data);
public static void GetInteger(IntPtr device, int param, out int data)
{
int[] dataArr = new int[1];
GCHandle handle = GCHandle.Alloc(dataArr,GCHandleType.Pinned);
GetIntegerv(device, param, 1, handle.AddrOfPinnedObject());
handle.Free();
data = dataArr[0];
}
#endregion
#region Capture Functions
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcCaptureOpenDevice")]
private static extern IntPtr CaptureOpenDevice(IntPtr devicename, uint frequency, int format, int buffersize);
public static IntPtr CaptureOpenDevice(string devicename, uint frequency, int format, int buffersize)
{
byte[] devicenameBytes = Encoding.UTF8.GetBytes(devicename + "\0");
GCHandle devicenameHandle = GCHandle.Alloc(devicenameBytes, GCHandleType.Pinned);
IntPtr retVal = CaptureOpenDevice(devicenameHandle.AddrOfPinnedObject(), frequency, format, buffersize);
devicenameHandle.Free();
return retVal;
}
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcCaptureCloseDevice")]
public static extern bool CaptureCloseDevice(IntPtr device);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcCaptureStart")]
public static extern void CaptureStart(IntPtr device);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcCaptureStop")]
public static extern void CaptureStop(IntPtr device);
[DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcCaptureSamples")]
public static extern void CaptureSamples(IntPtr device, IntPtr buffer, int samples);
#endregion
}
}
@@ -0,0 +1,233 @@
using System;
using OpenAL;
using Microsoft.Xna.Framework;
using System.IO;
using System.Xml.Linq;
namespace Barotrauma.Sounds
{
public abstract class Sound : IDisposable
{
protected bool disposed;
public bool Disposed
{
get { return disposed; }
}
public SoundManager Owner
{
get;
protected set;
}
public string Filename
{
get;
protected set;
}
public XElement XElement
{
get;
protected set;
}
public bool Stream
{
get;
protected set;
}
public bool StreamsReliably
{
get;
protected set;
}
public virtual SoundManager.SourcePoolIndex SourcePoolIndex
{
get
{
return SoundManager.SourcePoolIndex.Default;
}
}
private uint alBuffer;
public uint ALBuffer
{
get { return !Stream ? alBuffer : 0; }
}
private uint alMuffledBuffer;
public uint ALMuffledBuffer
{
get { return !Stream ? alMuffledBuffer : 0; }
}
public int ALFormat
{
get;
protected set;
}
public int SampleRate
{
get;
protected set;
}
/// <summary>
/// How many instances of the same sound clip can be playing at the same time
/// </summary>
public int MaxSimultaneousInstances = 5;
public float BaseGain;
public float BaseNear;
public float BaseFar;
public Sound(SoundManager owner, string filename, bool stream, bool streamsReliably, XElement xElement=null)
{
Owner = owner;
Filename = Path.GetFullPath(filename.CleanUpPath()).CleanUpPath();
Stream = stream;
StreamsReliably = streamsReliably;
XElement = xElement;
BaseGain = 1.0f;
BaseNear = 100.0f;
BaseFar = 200.0f;
if (!stream)
{
Al.GenBuffer(out alBuffer);
int alError = Al.GetError();
if (alError != Al.NoError)
{
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 != Al.NoError)
{
throw new Exception("Failed to create OpenAL buffer for non-streamed sound: " + Al.GetErrorString(alError));
}
if (!Al.IsBuffer(alMuffledBuffer))
{
throw new Exception("Generated OpenAL buffer is invalid!");
}
}
else
{
alBuffer = 0;
}
}
public override string ToString()
{
return GetType().ToString() + " (" + Filename + ")";
}
public virtual bool IsPlaying()
{
return Owner.IsPlaying(this);
}
public virtual 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);
}
public virtual SoundChannel Play(Vector3? position, float gain, bool muffle = false)
{
return new SoundChannel(this, gain, position, BaseNear, BaseFar, "default", muffle);
}
public virtual SoundChannel Play(float gain)
{
return Play(null, gain);
}
public virtual SoundChannel Play()
{
return Play(BaseGain);
}
public virtual SoundChannel Play(float? gain, string category)
{
if (Owner.CountPlayingInstances(this) >= MaxSimultaneousInstances) { return null; }
return new SoundChannel(this, gain ?? BaseGain, null, BaseNear, BaseFar, category);
}
static protected void CastBuffer(float[] inBuffer, short[] outBuffer, int length)
{
for (int i = 0; i < length; i++)
{
outBuffer[i] = FloatToShort(inBuffer[i]);
}
}
static protected short FloatToShort(float fVal)
{
int temp = (int)(32767 * fVal);
if (temp > short.MaxValue) temp = short.MaxValue;
else if (temp < short.MinValue) temp = short.MinValue;
return (short)temp;
}
static protected float ShortToFloat(short shortVal)
{
return shortVal / 32767f;
}
public abstract int FillStreamBuffer(int samplePos, short[] buffer);
public abstract float GetAmplitudeAtPlaybackPos(int playbackPos);
public virtual void Dispose()
{
if (disposed) { return; }
Owner.KillChannels(this);
if (alBuffer != 0)
{
if (!Al.IsBuffer(alBuffer))
{
throw new Exception("Buffer to delete is invalid!");
}
Al.DeleteBuffer(alBuffer); alBuffer = 0;
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to delete OpenAL buffer for non-streamed sound: " + Al.GetErrorString(alError));
}
}
if (alMuffledBuffer != 0)
{
if (!Al.IsBuffer(alMuffledBuffer))
{
throw new Exception("Buffer to delete is invalid!");
}
Al.DeleteBuffer(alMuffledBuffer); alMuffledBuffer = 0;
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to delete OpenAL buffer for non-streamed sound: " + Al.GetErrorString(alError));
}
}
Owner.RemoveSound(this);
disposed = true;
}
}
}
@@ -0,0 +1,788 @@
using System;
using OpenAL;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Threading;
namespace Barotrauma.Sounds
{
public class SoundSourcePool : IDisposable
{
public uint[] ALSources
{
get;
private set;
}
public SoundSourcePool(int sourceCount = SoundManager.SOURCE_COUNT)
{
int alError = Al.NoError;
ALSources = new uint[sourceCount];
for (int i = 0; i < sourceCount; i++)
{
Al.GenSource(out ALSources[i]);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Error generating alSource[" + i.ToString() + "]: " + Al.GetErrorString(alError));
}
if (!Al.IsSource(ALSources[i]))
{
throw new Exception("Generated alSource[" + i.ToString() + "] is invalid!");
}
Al.SourceStop(ALSources[i]);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Error stopping newly generated alSource[" + i.ToString() + "]: " + Al.GetErrorString(alError));
}
Al.Sourcef(ALSources[i], Al.MinGain, 0.0f);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Error setting min gain: " + Al.GetErrorString(alError));
}
Al.Sourcef(ALSources[i], Al.MaxGain, 1.0f);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Error setting max gain: " + Al.GetErrorString(alError));
}
Al.Sourcef(ALSources[i], Al.RolloffFactor, 1.0f);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Error setting rolloff factor: " + Al.GetErrorString(alError));
}
}
}
public void Dispose()
{
for (int i = 0; i < ALSources.Length; i++)
{
Al.DeleteSource(ALSources[i]);
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to delete ALSources[" + i.ToString() + "]: " + Al.GetErrorString(alError));
}
}
ALSources = null;
}
}
public class SoundChannel : IDisposable
{
private const int STREAM_BUFFER_SIZE = 8820;
private short[] streamShortBuffer;
private string debugName = "SoundChannel";
private Vector3? position;
public Vector3? Position
{
get { return position; }
set
{
position = value;
if (ALSourceIndex < 0) { return; }
if (position != null)
{
if (float.IsNaN(position.Value.X))
{
throw new Exception("Failed to set source's position: " + debugName + ", position.X is NaN");
}
if (float.IsNaN(position.Value.Y))
{
throw new Exception("Failed to set source's position: " + debugName + ", position.Y is NaN");
}
if (float.IsNaN(position.Value.Z))
{
throw new Exception("Failed to set source's position: " + debugName + ", position.Z is NaN");
}
if (float.IsInfinity(position.Value.X))
{
throw new Exception("Failed to set source's position: " + debugName + ", position.X is Infinity");
}
if (float.IsInfinity(position.Value.Y))
{
throw new Exception("Failed to set source's position: " + debugName + ", position.Y is Infinity");
}
if (float.IsInfinity(position.Value.Z))
{
throw new Exception("Failed to set source's position: " + debugName + ", position.Z is Infinity");
}
uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex);
Al.Sourcei(alSource, Al.SourceRelative, Al.False);
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to enable source's relative flag: " + debugName + ", " + Al.GetErrorString(alError));
}
Al.Source3f(alSource, Al.Position, position.Value.X, position.Value.Y, position.Value.Z);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to set source's position: " + debugName + ", " + Al.GetErrorString(alError));
}
}
else
{
uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex);
Al.Sourcei(alSource, Al.SourceRelative, Al.True);
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to disable source's relative flag: " + debugName + ", " + Al.GetErrorString(alError));
}
Al.Source3f(alSource, Al.Position, 0.0f, 0.0f, 0.0f);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to reset source's position: " + debugName + ", " + Al.GetErrorString(alError));
}
}
}
}
private float near;
public float Near
{
get { return near; }
set
{
near = value;
if (ALSourceIndex < 0) { return; }
uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex);
Al.Sourcef(alSource, Al.ReferenceDistance, near);
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to set source's reference distance: " + debugName + ", " + Al.GetErrorString(alError));
}
}
}
private float far;
public float Far
{
get { return far; }
set
{
far = value;
if (ALSourceIndex < 0) { return; }
uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex);
Al.Sourcef(alSource, Al.MaxDistance, far);
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to set source's max distance: " + debugName + ", " + Al.GetErrorString(alError));
}
}
}
private float gain;
public float Gain
{
get { return gain; }
set
{
gain = Math.Max(Math.Min(value, 1.0f), 0.0f);
if (ALSourceIndex < 0) { return; }
uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex);
float effectiveGain = gain;
if (category != null) effectiveGain *= Sound.Owner.GetCategoryGainMultiplier(category);
Al.Sourcef(alSource, Al.Gain, effectiveGain);
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to set source's gain: " + debugName + ", " + Al.GetErrorString(alError));
}
}
}
private bool looping;
public bool Looping
{
get { return looping; }
set
{
looping = value;
if (ALSourceIndex < 0) { return; }
if (!IsStream)
{
uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex);
Al.Sourcei(alSource, Al.Looping, looping ? Al.True : Al.False);
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to set source's looping state: " + debugName + ", " + Al.GetErrorString(alError));
}
}
}
}
public bool FilledByNetwork
{
get;
private set;
}
private int decayTimer;
private bool muffled;
public bool Muffled
{
get { return muffled; }
set
{
if (muffled == value) { return; }
muffled = value;
if (ALSourceIndex < 0) { return; }
if (!IsPlaying) return;
if (!IsStream)
{
uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex);
int playbackPos; Al.GetSourcei(alSource, Al.SampleOffset, out playbackPos);
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to get source's playback position: " + debugName + ", " + Al.GetErrorString(alError));
}
Al.SourceStop(alSource);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to stop source: " + debugName + ", " + Al.GetErrorString(alError));
}
Al.Sourcei(alSource, Al.Buffer, muffled ? (int)Sound.ALMuffledBuffer : (int)Sound.ALBuffer);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to bind buffer to source: " + debugName + ", " + Al.GetErrorString(alError));
}
Al.SourcePlay(alSource);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to replay source: " + debugName + ", " + Al.GetErrorString(alError));
}
Al.Sourcei(alSource, Al.SampleOffset, playbackPos);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to reset playback position: " + debugName + ", " + Al.GetErrorString(alError));
}
}
}
}
private float streamAmplitude;
public float CurrentAmplitude
{
get
{
if (!IsPlaying) { return 0.0f; }
uint alSource = Sound?.Owner?.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex) ?? 0;
if (alSource == 0) { return 0.0f; }
if (!IsStream)
{
int playbackPos; Al.GetSourcei(alSource, Al.SampleOffset, out playbackPos);
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to get source's playback position: " + debugName + ", " + Al.GetErrorString(alError));
}
return Sound.GetAmplitudeAtPlaybackPos(playbackPos);
}
else
{
float retVal = -1.0f;
Monitor.Enter(mutex);
retVal = streamAmplitude;
Monitor.Exit(mutex);
return retVal;
}
}
}
private string category;
public string Category
{
get { return category; }
set
{
category = value;
Gain = gain;
}
}
public Sound Sound
{
get;
private set;
}
public int ALSourceIndex
{
get;
private set;
} = -1;
public bool IsStream
{
get;
private set;
}
private int streamSeekPos;
private int buffersToRequeue;
private bool reachedEndSample;
private int queueStartIndex;
private readonly uint[] streamBuffers;
private uint[] unqueuedBuffers;
private float[] streamBufferAmplitudes;
public int StreamSeekPos
{
get { return streamSeekPos; }
set
{
if (!IsStream)
{
throw new InvalidOperationException("Cannot set StreamSeekPos on a non-streaming sound channel.");
}
streamSeekPos = Math.Max(value, 0);
}
}
private object mutex;
public bool IsPlaying
{
get
{
if (ALSourceIndex < 0) return false;
if (IsStream && !reachedEndSample) return true;
int state;
uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex);
if (!Al.IsSource(alSource)) return false;
Al.GetSourcei(alSource, Al.SourceState, out state);
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to determine playing state from source: " + debugName + ", " + Al.GetErrorString(alError));
}
bool playing = state == Al.Playing;
return playing;
}
}
public SoundChannel(Sound sound, float gain, Vector3? position, float near, float far, string category, bool muffle = false)
{
Sound = sound;
debugName = sound == null ?
"SoundChannel (null)" :
$"SoundChannel ({(string.IsNullOrEmpty(sound.Filename) ? "filename empty" : sound.Filename) })";
IsStream = sound.Stream;
FilledByNetwork = sound is VoipSound;
decayTimer = 0;
streamSeekPos = 0; reachedEndSample = false;
buffersToRequeue = 4;
muffled = muffle;
if (IsStream)
{
mutex = new object();
}
try
{
if (mutex != null) { Monitor.Enter(mutex); }
if (sound.Owner.CountPlayingInstances(sound) < sound.MaxSimultaneousInstances)
{
ALSourceIndex = sound.Owner.AssignFreeSourceToChannel(this);
}
if (ALSourceIndex >= 0)
{
if (!IsStream)
{
Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Buffer, 0);
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to reset source buffer: " + debugName + ", " + 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.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Buffer, (int)alBuffer);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to bind buffer to source (" + ALSourceIndex.ToString() + ":" + sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex) + "," + sound.ALBuffer.ToString() + "): " + debugName + ", " + Al.GetErrorString(alError));
}
Al.SourcePlay(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex));
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to play source: " + debugName + ", " + Al.GetErrorString(alError));
}
}
else
{
uint alBuffer = sound.Owner.GetCategoryMuffle(category) || muffle ? sound.ALMuffledBuffer : sound.ALBuffer;
Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Buffer, (int)alBuffer);
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to reset source buffer: " + debugName + ", " + Al.GetErrorString(alError));
}
Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Looping, Al.False);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to set stream looping state: " + debugName + ", " + Al.GetErrorString(alError));
}
streamShortBuffer = new short[STREAM_BUFFER_SIZE];
streamBuffers = new uint[4];
unqueuedBuffers = new uint[4];
streamBufferAmplitudes = new float[4];
for (int i = 0; i < 4; i++)
{
Al.GenBuffer(out streamBuffers[i]);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to generate stream buffers: " + debugName + ", " + Al.GetErrorString(alError));
}
if (!Al.IsBuffer(streamBuffers[i]))
{
throw new Exception("Generated streamBuffer[" + i.ToString() + "] is invalid! " + debugName);
}
}
Sound.Owner.InitStreamThread();
}
}
this.Position = position;
this.Gain = gain;
this.Looping = false;
this.Near = near;
this.Far = far;
this.Category = category;
}
catch
{
throw;
}
finally
{
if (mutex != null) { Monitor.Exit(mutex); }
}
Sound.Owner.Update();
}
public override string ToString()
{
return debugName;
}
public bool FadingOutAndDisposing
{
get;
private set;
}
public void FadeOutAndDispose()
{
FadingOutAndDisposing = true;
}
public void Dispose()
{
try
{
if (mutex != null) { Monitor.Enter(mutex); }
if (ALSourceIndex >= 0)
{
Al.SourceStop(Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex));
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to stop source: " + debugName + ", " + Al.GetErrorString(alError));
}
if (IsStream)
{
uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex);
Al.SourceStop(alSource);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to stop streamed source: " + debugName + ", " + Al.GetErrorString(alError));
}
int buffersToRequeue = 0;
buffersToRequeue = 0;
Al.GetSourcei(alSource, Al.BuffersProcessed, out buffersToRequeue);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to determine processed buffers from streamed source: " + debugName + ", " + Al.GetErrorString(alError));
}
Al.SourceUnqueueBuffers(alSource, buffersToRequeue, unqueuedBuffers);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to unqueue buffers from streamed source: " + debugName + ", " + Al.GetErrorString(alError));
}
Al.Sourcei(alSource, Al.Buffer, 0);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to reset buffer for streamed source: " + debugName + ", " + Al.GetErrorString(alError));
}
for (int i = 0; i < 4; i++)
{
Al.DeleteBuffer(streamBuffers[i]);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to delete streamBuffers[" + i.ToString() + "] (" + streamBuffers[i].ToString() + "): " + debugName + ", " + Al.GetErrorString(alError));
}
}
reachedEndSample = true;
}
else
{
Al.Sourcei(Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Buffer, 0);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to unbind buffer to non-streamed source: " + debugName + ", " + Al.GetErrorString(alError));
}
}
ALSourceIndex = -1;
debugName += " [DISPOSED]";
}
}
finally
{
if (mutex != null) { Monitor.Exit(mutex); }
}
}
public void UpdateStream()
{
if (!IsStream) { throw new Exception("Called UpdateStream on a non-streamed sound channel!"); }
try
{
Monitor.Enter(mutex);
if (!reachedEndSample)
{
uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex);
int state;
Al.GetSourcei(alSource, Al.SourceState, out state);
bool playing = state == Al.Playing;
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to determine playing state from streamed source: " + debugName + ", " + Al.GetErrorString(alError));
}
int unqueuedBufferCount;
Al.GetSourcei(alSource, Al.BuffersProcessed, out unqueuedBufferCount);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to determine processed buffers from streamed source: " + debugName + ", " + Al.GetErrorString(alError));
}
Al.SourceUnqueueBuffers(alSource, unqueuedBufferCount, unqueuedBuffers);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to unqueue buffers from streamed source: " + debugName + ", " + Al.GetErrorString(alError));
}
buffersToRequeue += unqueuedBufferCount;
int iterCount = buffersToRequeue;
for (int k = 0; k < iterCount; k++)
{
int index = queueStartIndex;
short[] buffer = streamShortBuffer;
int readSamples = Sound.FillStreamBuffer(streamSeekPos, buffer);
float readAmplitude = 0.0f;
for (int i = 0; i < Math.Min(readSamples, buffer.Length); i++)
{
float sampleF = ((float)buffer[i]) / ((float)short.MaxValue);
readAmplitude = Math.Max(readAmplitude, Math.Abs(sampleF));
}
if (FilledByNetwork)
{
if (Sound is VoipSound voipSound)
{
voipSound.ApplyFilters(buffer, readSamples);
}
if (readSamples <= 0)
{
streamAmplitude *= 0.5f;
decayTimer++;
if (decayTimer > 120) //TODO: replace magic number
{
reachedEndSample = true;
}
}
else
{
decayTimer = 0;
}
}
else if (Sound.StreamsReliably)
{
streamSeekPos += readSamples;
if (readSamples < STREAM_BUFFER_SIZE)
{
if (looping)
{
streamSeekPos = 0;
}
else
{
reachedEndSample = true;
}
}
}
if (readSamples > 0)
{
streamBufferAmplitudes[index] = readAmplitude;
Al.BufferData<short>(streamBuffers[index], Sound.ALFormat, buffer, readSamples, Sound.SampleRate);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to assign data to stream buffer: " +
Al.GetErrorString(alError) + ": " + streamBuffers[index].ToString() + "/" + streamBuffers.Length + ", readSamples: " + readSamples + ", " + debugName);
}
Al.SourceQueueBuffer(alSource, streamBuffers[index]);
queueStartIndex = (queueStartIndex + 1) % 4;
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to queue streamBuffer[" + index.ToString() + "] to stream: " + debugName + ", " + Al.GetErrorString(alError));
}
}
else
{
if (readSamples < 0)
{
reachedEndSample = true;
}
break;
}
buffersToRequeue--;
}
streamAmplitude = streamBufferAmplitudes[queueStartIndex];
Al.GetSourcei(alSource, Al.SourceState, out state);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to retrieve stream source state: " + debugName + ", " + Al.GetErrorString(alError));
}
if (state != Al.Playing)
{
Al.SourcePlay(alSource);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to start stream playback: " + debugName + ", " + Al.GetErrorString(alError));
}
}
}
if (reachedEndSample)
{
streamAmplitude = 0.0f;
}
}
catch (Exception e)
{
DebugConsole.ThrowError($"An exception was thrown when updating a sound stream ({debugName})", e);
}
finally
{
Monitor.Exit(mutex);
}
}
}
}
@@ -0,0 +1,492 @@
using System;
namespace Barotrauma.Sounds
{
/* This code is adapted from CSCore (.NET Audio Library) which is under a Microsoft Public License (Ms-PL) license.*/
/*
Microsoft Public License (Ms-PL)
This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software.
1. Definitions
The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
A "contribution" is the original software, or any additions or changes to the software.
A "contributor" is any person that distributes its contribution under this license.
"Licensed patents" are a contributor's patent claims that read directly on its contribution.
2. Grant of Rights
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
3. Conditions and Limitations
(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
(D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
*/
/*
* These implementations are based on http://www.earlevel.com/main/2011/01/02/biquad-formulas/
*/
/// <summary>
/// Represents a biquad-filter.
/// </summary>
public abstract class BiQuad
{
/// <summary>
/// The a0 value.
/// </summary>
protected double A0;
/// <summary>
/// The a1 value.
/// </summary>
protected double A1;
/// <summary>
/// The a2 value.
/// </summary>
protected double A2;
/// <summary>
/// The b1 value.
/// </summary>
protected double B1;
/// <summary>
/// The b2 value.
/// </summary>
protected double B2;
/// <summary>
/// The q value.
/// </summary>
private double _q;
/// <summary>
/// The gain value in dB.
/// </summary>
private double _gainDB;
/// <summary>
/// The z1 value.
/// </summary>
protected double Z1;
/// <summary>
/// The z2 value.
/// </summary>
protected double Z2;
private double _frequency;
/// <summary>
/// Gets or sets the frequency.
/// </summary>
/// <exception cref="System.ArgumentOutOfRangeException">value;The samplerate has to be bigger than 2 * frequency.</exception>
public double Frequency
{
get { return _frequency; }
set
{
if (SampleRate < value * 2)
{
throw new ArgumentOutOfRangeException("value", "The samplerate has to be bigger than 2 * frequency.");
}
_frequency = value;
CalculateBiQuadCoefficients();
}
}
/// <summary>
/// Gets the sample rate.
/// </summary>
public int SampleRate { get; private set; }
/// <summary>
/// The q value.
/// </summary>
public double Q
{
get { return _q; }
set
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException("value");
}
_q = value;
CalculateBiQuadCoefficients();
}
}
/// <summary>
/// Gets or sets the gain value in dB.
/// </summary>
public double GainDB
{
get { return _gainDB; }
set
{
_gainDB = value;
CalculateBiQuadCoefficients();
}
}
/// <summary>
/// Initializes a new instance of the <see cref="BiQuad"/> class.
/// </summary>
/// <param name="sampleRate">The sample rate.</param>
/// <param name="frequency">The frequency.</param>
/// <exception cref="System.ArgumentOutOfRangeException">
/// sampleRate
/// or
/// frequency
/// or
/// q
/// </exception>
protected BiQuad(int sampleRate, double frequency)
: this(sampleRate, frequency, 1.0 / Math.Sqrt(2))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BiQuad"/> class.
/// </summary>
/// <param name="sampleRate">The sample rate.</param>
/// <param name="frequency">The frequency.</param>
/// <param name="q">The q.</param>
/// <exception cref="System.ArgumentOutOfRangeException">
/// sampleRate
/// or
/// frequency
/// or
/// q
/// </exception>
protected BiQuad(int sampleRate, double frequency, double q)
{
if (sampleRate <= 0)
throw new ArgumentOutOfRangeException("sampleRate");
if (frequency <= 0)
throw new ArgumentOutOfRangeException("frequency");
if (q <= 0)
throw new ArgumentOutOfRangeException("q");
SampleRate = sampleRate;
Frequency = frequency;
Q = q;
GainDB = 6;
}
/// <summary>
/// Processes a single <paramref name="input"/> sample and returns the result.
/// </summary>
/// <param name="input">The input sample to process.</param>
/// <returns>The result of the processed <paramref name="input"/> sample.</returns>
public float Process(float input)
{
double o = input * A0 + Z1;
Z1 = input * A1 + Z2 - B1 * o;
Z2 = input * A2 - B2 * o;
return (float)o;
}
/// <summary>
/// Processes multiple <paramref name="input"/> samples.
/// </summary>
/// <param name="input">The input samples to process.</param>
/// <remarks>The result of the calculation gets stored within the <paramref name="input"/> array.</remarks>
public void Process(float[] input)
{
for (int i = 0; i < input.Length; i++)
{
input[i] = Process(input[i]);
}
}
/// <summary>
/// Calculates all coefficients.
/// </summary>
protected abstract void CalculateBiQuadCoefficients();
}
/// <summary>
/// Used to apply a lowpass-filter to a signal.
/// </summary>
public class LowpassFilter : BiQuad
{
/// <summary>
/// Initializes a new instance of the <see cref="LowpassFilter"/> class.
/// </summary>
/// <param name="sampleRate">The sample rate.</param>
/// <param name="frequency">The filter's corner frequency.</param>
public LowpassFilter(int sampleRate, double frequency)
: base(sampleRate, frequency)
{
}
/// <summary>
/// Calculates all coefficients.
/// </summary>
protected override void CalculateBiQuadCoefficients()
{
double k = Math.Tan(Math.PI * Frequency / SampleRate);
var norm = 1 / (1 + k / Q + k * k);
A0 = k * k * norm;
A1 = 2 * A0;
A2 = A0;
B1 = 2 * (k * k - 1) * norm;
B2 = (1 - k / Q + k * k) * norm;
}
}
/// <summary>
/// Used to apply a highpass-filter to a signal.
/// </summary>
public class HighpassFilter : BiQuad
{
/// <summary>
/// Initializes a new instance of the <see cref="HighpassFilter"/> class.
/// </summary>
/// <param name="sampleRate">The sample rate.</param>
/// <param name="frequency">The filter's corner frequency.</param>
public HighpassFilter(int sampleRate, double frequency)
: base(sampleRate, frequency)
{
}
/// <summary>
/// Calculates all coefficients.
/// </summary>
protected override void CalculateBiQuadCoefficients()
{
double k = Math.Tan(Math.PI * Frequency / SampleRate);
var norm = 1 / (1 + k / Q + k * k);
A0 = 1 * norm;
A1 = -2 * A0;
A2 = A0;
B1 = 2 * (k * k - 1) * norm;
B2 = (1 - k / Q + k * k) * norm;
}
}
/// <summary>
/// Used to apply a bandpass-filter to a signal.
/// </summary>
public class BandpassFilter : BiQuad
{
/// <summary>
/// Initializes a new instance of the <see cref="BandpassFilter"/> class.
/// </summary>
/// <param name="sampleRate">The sample rate.</param>
/// <param name="frequency">The filter's corner frequency.</param>
public BandpassFilter(int sampleRate, double frequency)
: base(sampleRate, frequency)
{
}
/// <summary>
/// Calculates all coefficients.
/// </summary>
protected override void CalculateBiQuadCoefficients()
{
double k = Math.Tan(Math.PI * Frequency / SampleRate);
double norm = 1 / (1 + k / Q + k * k);
A0 = k / Q * norm;
A1 = 0;
A2 = -A0;
B1 = 2 * (k * k - 1) * norm;
B2 = (1 - k / Q + k * k) * norm;
}
}
/// <summary>
/// Used to apply a notch-filter to a signal.
/// </summary>
public class NotchFilter : BiQuad
{
/// <summary>
/// Initializes a new instance of the <see cref="NotchFilter"/> class.
/// </summary>
/// <param name="sampleRate">The sample rate.</param>
/// <param name="frequency">The filter's corner frequency.</param>
public NotchFilter(int sampleRate, double frequency)
: base(sampleRate, frequency)
{
}
/// <summary>
/// Calculates all coefficients.
/// </summary>
protected override void CalculateBiQuadCoefficients()
{
double k = Math.Tan(Math.PI * Frequency / SampleRate);
double norm = 1 / (1 + k / Q + k * k);
A0 = (1 + k * k) * norm;
A1 = 2 * (k * k - 1) * norm;
A2 = A0;
B1 = A1;
B2 = (1 - k / Q + k * k) * norm;
}
}
/// <summary>
/// Used to apply a lowshelf-filter to a signal.
/// </summary>
public class LowShelfFilter : BiQuad
{
/// <summary>
/// Initializes a new instance of the <see cref="LowShelfFilter"/> class.
/// </summary>
/// <param name="sampleRate">The sample rate.</param>
/// <param name="frequency">The filter's corner frequency.</param>
/// <param name="gainDB">Gain value in dB.</param>
public LowShelfFilter(int sampleRate, double frequency, double gainDB)
: base(sampleRate, frequency)
{
GainDB = gainDB;
}
/// <summary>
/// Calculates all coefficients.
/// </summary>
protected override void CalculateBiQuadCoefficients()
{
const double sqrt2 = 1.4142135623730951;
double k = Math.Tan(Math.PI * Frequency / SampleRate);
double v = Math.Pow(10, Math.Abs(GainDB) / 20.0);
double norm;
if (GainDB >= 0)
{ // boost
norm = 1 / (1 + sqrt2 * k + k * k);
A0 = (1 + Math.Sqrt(2 * v) * k + v * k * k) * norm;
A1 = 2 * (v * k * k - 1) * norm;
A2 = (1 - Math.Sqrt(2 * v) * k + v * k * k) * norm;
B1 = 2 * (k * k - 1) * norm;
B2 = (1 - sqrt2 * k + k * k) * norm;
}
else
{ // cut
norm = 1 / (1 + Math.Sqrt(2 * v) * k + v * k * k);
A0 = (1 + sqrt2 * k + k * k) * norm;
A1 = 2 * (k * k - 1) * norm;
A2 = (1 - sqrt2 * k + k * k) * norm;
B1 = 2 * (v * k * k - 1) * norm;
B2 = (1 - Math.Sqrt(2 * v) * k + v * k * k) * norm;
}
}
}
/// <summary>
/// Used to apply a highshelf-filter to a signal.
/// </summary>
public class HighShelfFilter : BiQuad
{
/// <summary>
/// Initializes a new instance of the <see cref="HighShelfFilter"/> class.
/// </summary>
/// <param name="sampleRate">The sample rate.</param>
/// <param name="frequency">The filter's corner frequency.</param>
/// <param name="gainDB">Gain value in dB.</param>
public HighShelfFilter(int sampleRate, double frequency, double gainDB)
: base(sampleRate, frequency)
{
GainDB = gainDB;
}
/// <summary>
/// Calculates all coefficients.
/// </summary>
protected override void CalculateBiQuadCoefficients()
{
const double sqrt2 = 1.4142135623730951;
double k = Math.Tan(Math.PI * Frequency / SampleRate);
double v = Math.Pow(10, Math.Abs(GainDB) / 20.0);
double norm;
if (GainDB >= 0)
{ // boost
norm = 1 / (1 + sqrt2 * k + k * k);
A0 = (v + Math.Sqrt(2 * v) * k + k * k) * norm;
A1 = 2 * (k * k - v) * norm;
A2 = (v - Math.Sqrt(2 * v) * k + k * k) * norm;
B1 = 2 * (k * k - 1) * norm;
B2 = (1 - sqrt2 * k + k * k) * norm;
}
else
{ // cut
norm = 1 / (v + Math.Sqrt(2 * v) * k + k * k);
A0 = (1 + sqrt2 * k + k * k) * norm;
A1 = 2 * (k * k - 1) * norm;
A2 = (1 - sqrt2 * k + k * k) * norm;
B1 = 2 * (k * k - v) * norm;
B2 = (v - Math.Sqrt(2 * v) * k + k * k) * norm;
}
}
}
/// <summary>
/// Used to apply an peak-filter to a signal.
/// </summary>
public class PeakFilter : BiQuad
{
/// <summary>
/// Gets or sets the bandwidth.
/// </summary>
public double BandWidth
{
get { return Q; }
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException("value");
Q = value;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="PeakFilter"/> class.
/// </summary>
/// <param name="sampleRate">The sampleRate of the audio data to process.</param>
/// <param name="frequency">The center frequency to adjust.</param>
/// <param name="bandWidth">The bandWidth.</param>
/// <param name="peakGainDB">The gain value in dB.</param>
public PeakFilter(int sampleRate, double frequency, double bandWidth, double peakGainDB)
: base(sampleRate, frequency, bandWidth)
{
GainDB = peakGainDB;
}
/// <summary>
/// Calculates all coefficients.
/// </summary>
protected override void CalculateBiQuadCoefficients()
{
double norm;
double v = Math.Pow(10, Math.Abs(GainDB) / 20.0);
double k = Math.Tan(Math.PI * Frequency / SampleRate);
double q = Q;
if (GainDB >= 0) //boost
{
norm = 1 / (1 + 1 / q * k + k * k);
A0 = (1 + v / q * k + k * k) * norm;
A1 = 2 * (k * k - 1) * norm;
A2 = (1 - v / q * k + k * k) * norm;
B1 = A1;
B2 = (1 - 1 / q * k + k * k) * norm;
}
else //cut
{
norm = 1 / (1 + v / q * k + k * k);
A0 = (1 + 1 / q * k + k * k) * norm;
A1 = 2 * (k * k - 1) * norm;
A2 = (1 - 1 / q * k + k * k) * norm;
B1 = A1;
B2 = (1 - v / q * k + k * k) * norm;
}
}
}
}
@@ -0,0 +1,723 @@
using System;
using System.Threading;
using System.Collections.Generic;
using System.Xml.Linq;
using OpenAL;
using Microsoft.Xna.Framework;
using System.Linq;
using System.IO;
namespace Barotrauma.Sounds
{
public class SoundManager : IDisposable
{
public const int SOURCE_COUNT = 32;
public bool Disabled
{
get;
private set;
}
private readonly IntPtr alcDevice;
private readonly IntPtr alcContext;
public enum SourcePoolIndex
{
Default = 0,
Voice = 1
}
private readonly SoundSourcePool[] sourcePools;
private readonly List<Sound> loadedSounds;
private readonly SoundChannel[][] playingChannels = new SoundChannel[2][];
private readonly object threadDeathMutex = new object();
private Thread streamingThread;
private Vector3 listenerPosition;
public Vector3 ListenerPosition
{
get { return listenerPosition; }
set
{
if (Disabled) { return; }
listenerPosition = value;
Al.Listener3f(Al.Position,value.X,value.Y,value.Z);
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to set listener position: " + Al.GetErrorString(alError));
}
}
}
private readonly float[] listenerOrientation = new float[6];
public Vector3 ListenerTargetVector
{
get { return new Vector3(listenerOrientation[0], listenerOrientation[1], listenerOrientation[2]); }
set
{
if (Disabled) { return; }
listenerOrientation[0] = value.X; listenerOrientation[1] = value.Y; listenerOrientation[2] = value.Z;
Al.Listenerfv(Al.Orientation, listenerOrientation);
int alError = Al.GetError();
if (alError != Al.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
{
if (Disabled) { return; }
listenerOrientation[3] = value.X; listenerOrientation[4] = value.Y; listenerOrientation[5] = value.Z;
Al.Listenerfv(Al.Orientation, listenerOrientation);
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to set listener up vector: " + Al.GetErrorString(alError));
}
}
}
private float listenerGain;
public float ListenerGain
{
get { return listenerGain; }
set
{
if (Disabled) { return; }
if (Math.Abs(ListenerGain - value) < 0.001f) { return; }
listenerGain = value;
Al.Listenerf(Al.Gain, listenerGain);
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to set listener gain: " + Al.GetErrorString(alError));
}
}
}
public float PlaybackAmplitude
{
get
{
if (Disabled) { return 0.0f; }
float aggregateAmplitude = 0.0f;
//NOTE: this is obviously not entirely accurate;
//It assumes a linear falloff model, and assumes that audio
//is simply added together to produce the final result.
//Adjustments may be needed under certain scenarios.
for (int i = 0; i < 2; i++)
{
foreach (SoundChannel soundChannel in playingChannels[i].Where(ch => ch != null))
{
float amplitude = soundChannel.CurrentAmplitude;
amplitude *= soundChannel.Gain;
float dist = Vector3.Distance(ListenerPosition, soundChannel.Position ?? ListenerPosition);
if (dist > soundChannel.Near)
{
amplitude *= 1.0f - Math.Min(1.0f, (dist - soundChannel.Near) / (soundChannel.Far - soundChannel.Near));
}
aggregateAmplitude += amplitude;
}
}
return aggregateAmplitude;
}
}
public float CompressionDynamicRangeGain { get; private set; }
private float voipAttenuatedGain;
private double lastAttenuationTime;
public float VoipAttenuatedGain
{
get { return voipAttenuatedGain; }
set
{
lastAttenuationTime = Timing.TotalTime;
voipAttenuatedGain = value;
}
}
public int LoadedSoundCount
{
get { return loadedSounds.Count; }
}
public int UniqueLoadedSoundCount
{
get { return loadedSounds.Select(s => s.Filename).Distinct().Count(); }
}
private class CategoryModifier
{
public float[] GainMultipliers;
public bool Muffle;
public CategoryModifier(int gainMultiplierIndex, float gain, bool muffle)
{
Muffle = muffle;
GainMultipliers = new float[gainMultiplierIndex+1];
for (int i=0;i<GainMultipliers.Length;i++)
{
if (i==gainMultiplierIndex)
{
GainMultipliers[i] = gain;
}
else
{
GainMultipliers[i] = 1.0f;
}
}
}
public void SetGainMultiplier(int index, float gain)
{
if (GainMultipliers.Length < index+1)
{
int oldLength = GainMultipliers.Length;
Array.Resize(ref GainMultipliers, index + 1);
for (int i=oldLength;i<GainMultipliers.Length;i++)
{
GainMultipliers[i] = 1.0f;
}
}
GainMultipliers[index] = gain;
}
}
private Dictionary<string, CategoryModifier> categoryModifiers;
public SoundManager()
{
loadedSounds = new List<Sound>();
streamingThread = null;
categoryModifiers = null;
int alcError = Alc.NoError;
string deviceName = Alc.GetString(IntPtr.Zero, Alc.DefaultDeviceSpecifier);
DebugConsole.NewMessage($"Attempting to open ALC device \"{deviceName}\"");
alcDevice = IntPtr.Zero;
for (int i = 0; i < 3; i++)
{
alcDevice = Alc.OpenDevice(deviceName);
if (alcDevice == IntPtr.Zero)
{
DebugConsole.NewMessage($"ALC device initialization attempt #{i + 1} failed: device is null");
}
else
{
alcError = Alc.GetError(alcDevice);
if (alcError != Alc.NoError)
{
DebugConsole.NewMessage($"ALC device initialization attempt #{i + 1} failed: error code {Alc.GetErrorString(alcError)}");
bool closed = Alc.CloseDevice(alcDevice);
if (!closed)
{
DebugConsole.NewMessage($"Failed to close ALC device");
}
alcDevice = IntPtr.Zero;
}
}
}
if (alcDevice == IntPtr.Zero)
{
DebugConsole.ThrowError("ALC device creation failed too many times!");
Disabled = true;
return;
}
int[] alcContextAttrs = new int[] { };
alcContext = Alc.CreateContext(alcDevice, alcContextAttrs);
if (alcContext == null)
{
DebugConsole.ThrowError("Failed to create an ALC context! (error code: " + Alc.GetError(alcDevice).ToString() + "). Disabling audio playback...");
Disabled = true;
return;
}
if (!Alc.MakeContextCurrent(alcContext))
{
DebugConsole.ThrowError("Failed to assign the current ALC context! (error code: " + Alc.GetError(alcDevice).ToString() + "). Disabling audio playback...");
Disabled = true;
return;
}
alcError = Alc.GetError(alcDevice);
if (alcError != Alc.NoError)
{
DebugConsole.ThrowError("Error after assigning ALC context: " + Alc.GetErrorString(alcError) + ". Disabling audio playback...");
Disabled = true;
return;
}
sourcePools = new SoundSourcePool[2];
sourcePools[(int)SourcePoolIndex.Default] = new SoundSourcePool(SOURCE_COUNT);
playingChannels[(int)SourcePoolIndex.Default] = new SoundChannel[SOURCE_COUNT];
sourcePools[(int)SourcePoolIndex.Voice] = new SoundSourcePool(16);
playingChannels[(int)SourcePoolIndex.Voice] = new SoundChannel[16];
Al.DistanceModel(Al.LinearDistanceClamped);
int alError = Al.GetError();
if (alError != Al.NoError)
{
DebugConsole.ThrowError("Error setting distance model: " + Al.GetErrorString(alError) + ". Disabling audio playback...");
Disabled = true;
return;
}
ListenerPosition = Vector3.Zero;
ListenerTargetVector = new Vector3(0.0f, 0.0f, 1.0f);
ListenerUpVector = new Vector3(0.0f, -1.0f, 0.0f);
CompressionDynamicRangeGain = 1.0f;
}
public Sound LoadSound(string filename, bool stream = false)
{
if (Disabled) { return null; }
if (!File.Exists(filename))
{
throw new FileNotFoundException("Sound file \"" + filename + "\" doesn't exist!");
}
Sound newSound = new OggSound(this, filename, stream, null);
lock (loadedSounds)
{
loadedSounds.Add(newSound);
}
return newSound;
}
public Sound LoadSound(XElement element, bool stream = false)
{
if (Disabled) { return null; }
string filePath = element.GetAttributeString("file", "");
if (!File.Exists(filePath))
{
throw new FileNotFoundException("Sound file \"" + filePath + "\" doesn't exist!");
}
var newSound = new OggSound(this, filePath, stream, xElement: element);
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;
}
lock (loadedSounds)
{
loadedSounds.Add(newSound);
}
return newSound;
}
public SoundChannel GetSoundChannelFromIndex(SourcePoolIndex poolIndex, int ind)
{
if (Disabled || ind < 0 || ind >= playingChannels[(int)poolIndex].Length) return null;
return playingChannels[(int)poolIndex][ind];
}
public uint GetSourceFromIndex(SourcePoolIndex poolIndex, int srcInd)
{
if (Disabled || srcInd < 0 || srcInd >= sourcePools[(int)poolIndex].ALSources.Length) return 0;
if (!Al.IsSource(sourcePools[(int)poolIndex].ALSources[srcInd]))
{
throw new Exception("alSources[" + srcInd.ToString() + "] is invalid!");
}
return sourcePools[(int)poolIndex].ALSources[srcInd];
}
public int AssignFreeSourceToChannel(SoundChannel newChannel)
{
if (Disabled) { return -1; }
//remove a channel that has stopped
//or hasn't even been assigned
int poolIndex = (int)newChannel.Sound.SourcePoolIndex;
lock (playingChannels[poolIndex])
{
for (int i = 0; i < playingChannels[poolIndex].Length; i++)
{
if (playingChannels[poolIndex][i] == null || !playingChannels[poolIndex][i].IsPlaying)
{
if (playingChannels[poolIndex][i] != null) { playingChannels[poolIndex][i].Dispose(); }
playingChannels[poolIndex][i] = newChannel;
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 < sourcePools[0].ALSources.Length; i++)
{
Al.Sourcef(sourcePools[0].ALSources[i], Al.MaxGain, i == ind ? 1.0f : 0.0f);
Al.Sourcef(sourcePools[0].ALSources[i], Al.MinGain, 0.0f);
}
}
#endif
public bool IsPlaying(Sound sound)
{
if (Disabled) { return false; }
lock (playingChannels[(int)sound.SourcePoolIndex])
{
for (int i = 0; i < playingChannels[(int)sound.SourcePoolIndex].Length; i++)
{
if (playingChannels[(int)sound.SourcePoolIndex][i] != null &&
playingChannels[(int)sound.SourcePoolIndex][i].Sound == sound)
{
if (playingChannels[(int)sound.SourcePoolIndex][i].IsPlaying) return true;
}
}
}
return false;
}
public int CountPlayingInstances(Sound sound)
{
if (Disabled) { return 0; }
int count = 0;
lock (playingChannels[(int)sound.SourcePoolIndex])
{
for (int i = 0; i < playingChannels[(int)sound.SourcePoolIndex].Length; i++)
{
if (playingChannels[(int)sound.SourcePoolIndex][i] != null &&
playingChannels[(int)sound.SourcePoolIndex][i].Sound.Filename == sound.Filename)
{
if (playingChannels[(int)sound.SourcePoolIndex][i].IsPlaying) { count++; };
}
}
}
return count;
}
public SoundChannel GetChannelFromSound(Sound sound)
{
if (Disabled) { return null; }
lock (playingChannels[(int)sound.SourcePoolIndex])
{
for (int i = 0; i < playingChannels[(int)sound.SourcePoolIndex].Length; i++)
{
if (playingChannels[(int)sound.SourcePoolIndex][i] != null &&
playingChannels[(int)sound.SourcePoolIndex][i].Sound == sound)
{
if (playingChannels[(int)sound.SourcePoolIndex][i].IsPlaying) return playingChannels[(int)sound.SourcePoolIndex][i];
}
}
}
return null;
}
public void KillChannels(Sound sound)
{
if (Disabled) { return; }
lock (playingChannels[(int)sound.SourcePoolIndex])
{
for (int i = 0; i < playingChannels[(int)sound.SourcePoolIndex].Length; i++)
{
if (playingChannels[(int)sound.SourcePoolIndex][i]!=null && playingChannels[(int)sound.SourcePoolIndex][i].Sound == sound)
{
playingChannels[(int)sound.SourcePoolIndex][i]?.Dispose();
playingChannels[(int)sound.SourcePoolIndex][i] = null;
}
}
}
}
public void RemoveSound(Sound sound)
{
lock (loadedSounds)
{
for (int i = 0; i < loadedSounds.Count; i++)
{
if (loadedSounds[i] == sound)
{
loadedSounds.RemoveAt(i);
return;
}
}
}
}
public void SetCategoryGainMultiplier(string category, float gain, int index=0)
{
if (Disabled) { return; }
category = category.ToLower();
if (categoryModifiers == null) categoryModifiers = new Dictionary<string, CategoryModifier>();
if (!categoryModifiers.ContainsKey(category))
{
categoryModifiers.Add(category, new CategoryModifier(index, gain, false));
}
else
{
categoryModifiers[category].SetGainMultiplier(index, gain);
}
for (int i = 0; i < playingChannels.Length; i++)
{
lock (playingChannels[i])
{
for (int j = 0; j < playingChannels[i].Length; j++)
{
if (playingChannels[i][j] != null && playingChannels[i][j].IsPlaying)
{
playingChannels[i][j].Gain = playingChannels[i][j].Gain; //force all channels to recalculate their gain
}
}
}
}
}
public float GetCategoryGainMultiplier(string category, int index=-1)
{
if (Disabled) { return 0.0f; }
category = category.ToLower();
if (categoryModifiers == null || !categoryModifiers.ContainsKey(category)) return 1.0f;
if (index < 0)
{
float accumulatedMultipliers = 1.0f;
for (int i = 0; i < categoryModifiers[category].GainMultipliers.Length; i++)
{
accumulatedMultipliers *= categoryModifiers[category].GainMultipliers[i];
}
return accumulatedMultipliers;
}
else
{
return categoryModifiers[category].GainMultipliers[index];
}
}
public void SetCategoryMuffle(string category,bool muffle)
{
if (Disabled) { return; }
category = category.ToLower();
if (categoryModifiers == null) categoryModifiers = new Dictionary<string, CategoryModifier>();
if (!categoryModifiers.ContainsKey(category))
{
categoryModifiers.Add(category, new CategoryModifier(0, 1.0f, muffle));
}
else
{
categoryModifiers[category].Muffle = muffle;
}
for (int i = 0; i < playingChannels.Length; i++)
{
lock (playingChannels[i])
{
for (int j = 0; j < playingChannels[i].Length; j++)
{
if (playingChannels[i][j] != null && playingChannels[i][j].IsPlaying)
{
if (playingChannels[i][j].Category.ToLower() == category) playingChannels[i][j].Muffled = muffle;
}
}
}
}
}
public bool GetCategoryMuffle(string category)
{
if (Disabled) { return false; }
category = category.ToLower();
if (categoryModifiers == null || !categoryModifiers.ContainsKey(category)) return false;
return categoryModifiers[category].Muffle;
}
public void Update()
{
if (Disabled) { return; }
if (GameMain.Client != null && GameMain.Config.VoipAttenuationEnabled)
{
if (Timing.TotalTime > lastAttenuationTime+0.2)
{
voipAttenuatedGain = voipAttenuatedGain * 0.9f + 0.1f;
}
}
else
{
voipAttenuatedGain = 1.0f;
}
SetCategoryGainMultiplier("default", VoipAttenuatedGain, 1);
SetCategoryGainMultiplier("ui", VoipAttenuatedGain, 1);
SetCategoryGainMultiplier("waterambience", VoipAttenuatedGain, 1);
SetCategoryGainMultiplier("music", VoipAttenuatedGain, 1);
if (GameMain.Config.DynamicRangeCompressionEnabled)
{
float targetGain = (Math.Min(1.0f, 1.0f / PlaybackAmplitude) - 1.0f) * 0.5f + 1.0f;
if (targetGain < CompressionDynamicRangeGain)
{
//if the target gain is lower than the current gain, lower the current gain immediately to prevent clipping
CompressionDynamicRangeGain = targetGain;
}
else
{
//otherwise, let it rise back smoothly
CompressionDynamicRangeGain = (targetGain) * 0.05f + CompressionDynamicRangeGain * 0.95f;
}
}
else
{
CompressionDynamicRangeGain = 1.0f;
}
if (streamingThread == null || streamingThread.ThreadState.HasFlag(ThreadState.Stopped))
{
bool startedStreamThread = false;
for (int i = 0; i < playingChannels.Length; i++)
{
lock (playingChannels[i])
{
for (int j = 0; j < playingChannels[i].Length; j++)
{
if (playingChannels[i][j] == null) { continue; }
if (playingChannels[i][j].IsStream && playingChannels[i][j].IsPlaying)
{
InitStreamThread();
startedStreamThread = true;
}
if (startedStreamThread) { break; }
}
}
if (startedStreamThread) { break; }
}
}
}
public void InitStreamThread()
{
if (Disabled) { return; }
bool isStreamThreadDying;
lock (threadDeathMutex)
{
isStreamThreadDying = !areStreamsPlaying;
}
if (streamingThread == null || streamingThread.ThreadState.HasFlag(ThreadState.Stopped) || isStreamThreadDying)
{
streamingThread?.Join();
areStreamsPlaying = true;
streamingThread = new Thread(UpdateStreaming)
{
Name = "SoundManager Streaming Thread",
IsBackground = true //this should kill the thread if the game crashes
};
streamingThread.Start();
}
}
bool areStreamsPlaying = false;
void UpdateStreaming()
{
bool killThread = false;
while (!killThread)
{
lock (threadDeathMutex)
{
areStreamsPlaying = false;
}
for (int i = 0; i < playingChannels.Length; i++)
{
lock (playingChannels[i])
{
for (int j = 0; j < playingChannels[i].Length; j++)
{
if (playingChannels[i][j] == null) { continue; }
if (playingChannels[i][j].IsStream)
{
if (playingChannels[i][j].IsPlaying)
{
lock (threadDeathMutex)
{
areStreamsPlaying = true;
}
playingChannels[i][j].UpdateStream();
}
else
{
playingChannels[i][j].Dispose();
}
}
else if (playingChannels[i][j].FadingOutAndDisposing)
{
playingChannels[i][j].Gain -= 0.1f;
if (playingChannels[i][j].Gain <= 0.0f)
{
playingChannels[i][j].Dispose();
}
}
}
}
}
lock (threadDeathMutex)
{
killThread = !areStreamsPlaying;
}
Thread.Sleep(10); //TODO: use a separate thread for network audio?
}
}
public void Dispose()
{
if (Disabled) { return; }
for (int i = 0; i < playingChannels.Length; i++)
{
lock (playingChannels[i])
{
for (int j = 0; j < playingChannels[i].Length; j++)
{
if (playingChannels[i][j] != null) playingChannels[i][j].Dispose();
}
}
}
streamingThread?.Join();
for (int i = loadedSounds.Count - 1; i >= 0; i--)
{
loadedSounds[i].Dispose();
}
sourcePools[(int)SourcePoolIndex.Default]?.Dispose();
sourcePools[(int)SourcePoolIndex.Voice]?.Dispose();
if (!Alc.MakeContextCurrent(IntPtr.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!");
}
}
}
}
@@ -0,0 +1,880 @@
using Barotrauma.Extensions;
using Barotrauma.Sounds;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
public struct DamageSound
{
//the range of inflicted damage where the sound can be played
//(10.0f, 30.0f) would be played when the inflicted damage is between 10 and 30
public readonly Vector2 damageRange;
public readonly string damageType;
public readonly Sound sound;
public readonly string requiredTag;
public DamageSound(Sound sound, Vector2 damageRange, string damageType, string requiredTag = "")
{
this.sound = sound;
this.damageRange = damageRange;
this.damageType = damageType;
this.requiredTag = requiredTag;
}
}
public class BackgroundMusic
{
public readonly string File;
public readonly string Type;
public readonly bool DuckVolume;
public readonly Vector2 IntensityRange;
public readonly bool ContinueFromPreviousTime;
public int PreviousTime;
public readonly XElement Element;
public BackgroundMusic(XElement element)
{
this.File = Path.GetFullPath(element.GetAttributeString("file", "")).CleanUpPath();
this.Type = element.GetAttributeString("type", "").ToLowerInvariant();
this.IntensityRange = element.GetAttributeVector2("intensityrange", new Vector2(0.0f, 100.0f));
this.DuckVolume = element.GetAttributeBool("duckvolume", false);
this.ContinueFromPreviousTime = element.GetAttributeBool("continuefromprevioustime", false);
this.Element = element;
}
}
static class SoundPlayer
{
private static ILookup<string, Sound> miscSounds;
//music
private const float MusicLerpSpeed = 1.0f;
private const float UpdateMusicInterval = 5.0f;
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 updateMusicTimer;
//ambience
private static List<Sound> waterAmbiences = new List<Sound>();
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
//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
// TODO: could use a dictionary to split up the list into smaller lists of same type?
private static List<DamageSound> damageSounds;
private static Sound startUpSound;
public static bool Initialized;
public static string OverrideMusicType
{
get;
set;
}
public static float? OverrideMusicDuration;
public static int SoundCount;
private static List<XElement> loadedSoundElements;
private static bool SoundElementsEquivalent(XElement a, XElement b)
{
string filePathA = a.GetAttributeString("file", "").CleanUpPath();
float baseGainA = a.GetAttributeFloat("volume", 1.0f);
float rangeA = a.GetAttributeFloat("range", 1000.0f);
string filePathB = b.GetAttributeString("file", "").CleanUpPath();
float baseGainB = b.GetAttributeFloat("volume", 1.0f);
float rangeB = b.GetAttributeFloat("range", 1000.0f);
return a.Name.ToString().Equals(b.Name.ToString(), StringComparison.OrdinalIgnoreCase) &&
filePathA == filePathB && MathUtils.NearlyEqual(baseGainA, baseGainB) &&
MathUtils.NearlyEqual(rangeA, rangeB);
}
public static IEnumerable<object> Init()
{
OverrideMusicType = null;
var soundFiles = GameMain.Instance.GetFilesOfType(ContentType.Sounds);
List<XElement> soundElements = new List<XElement>();
foreach (ContentFile soundFile in soundFiles)
{
XDocument doc = XMLExtensions.TryLoadXml(soundFile.Path);
if (doc == null) { continue; }
var mainElement = doc.Root;
if (doc.Root.IsOverride())
{
mainElement = doc.Root.FirstElement();
DebugConsole.NewMessage($"Overriding all sounds with {soundFile.Path}", Color.Yellow);
soundElements.Clear();
}
soundElements.AddRange(mainElement.Elements());
}
SoundCount = 1 + soundElements.Count();
var startUpSoundElement = soundElements.Find(e => e.Name.ToString().Equals("startupsound", StringComparison.OrdinalIgnoreCase));
if (startUpSoundElement != null)
{
startUpSound = GameMain.SoundManager.LoadSound(startUpSoundElement, false);
startUpSound?.Play();
}
yield return CoroutineStatus.Running;
List<KeyValuePair<string, Sound>> miscSoundList = new List<KeyValuePair<string, Sound>>();
damageSounds = damageSounds ?? new List<DamageSound>();
musicClips = musicClips ?? new List<BackgroundMusic>();
foreach (XElement soundElement in soundElements)
{
yield return CoroutineStatus.Running;
if (loadedSoundElements != null && loadedSoundElements.Any(e => SoundElementsEquivalent(e, soundElement)))
{
continue;
}
try
{
switch (soundElement.Name.ToString().ToLowerInvariant())
{
case "music":
var newMusicClip = new BackgroundMusic(soundElement);
musicClips.AddIfNotNull(newMusicClip);
if (loadedSoundElements != null)
{
if (newMusicClip.Type.Equals("menu", StringComparison.OrdinalIgnoreCase))
{
targetMusic[0] = newMusicClip;
}
}
break;
case "splash":
SplashSounds.AddIfNotNull(GameMain.SoundManager.LoadSound(soundElement, false));
break;
case "flow":
FlowSounds.AddIfNotNull(GameMain.SoundManager.LoadSound(soundElement, false));
break;
case "waterambience":
waterAmbiences.AddIfNotNull(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");
damageSounds.Add(new DamageSound(
damageSound,
soundElement.GetAttributeVector2("damagerange", Vector2.Zero),
damageSoundType,
soundElement.GetAttributeString("requiredtag", "")));
break;
default:
Sound sound = GameMain.SoundManager.LoadSound(soundElement, false);
if (sound != null)
{
miscSoundList.Add(new KeyValuePair<string, Sound>(soundElement.Name.ToString().ToLowerInvariant(), sound));
}
break;
}
}
catch (FileNotFoundException e)
{
DebugConsole.ThrowError("Error while initializing SoundPlayer.", e);
}
}
musicClips.RemoveAll(mc => !soundElements.Any(e => SoundElementsEquivalent(mc.Element, e)));
for (int i=0;i<currentMusic.Length;i++)
{
if (currentMusic[i] != null && !musicClips.Any(mc => mc.File == currentMusic[i].Filename))
{
DisposeMusicChannel(i);
}
}
SplashSounds.ForEach(s =>
{
if (!soundElements.Any(e => SoundElementsEquivalent(s.XElement, e))) { s.Dispose(); }
});
SplashSounds.RemoveAll(s => s.Disposed);
FlowSounds.ForEach(s =>
{
if (!soundElements.Any(e => SoundElementsEquivalent(s.XElement, e))) { s.Dispose(); }
});
FlowSounds.RemoveAll(s => s.Disposed);
waterAmbiences.ForEach(s =>
{
if (!soundElements.Any(e => SoundElementsEquivalent(s.XElement, e))) { s.Dispose(); }
});
waterAmbiences.RemoveAll(s => s.Disposed);
damageSounds.ForEach(s =>
{
if (!soundElements.Any(e => SoundElementsEquivalent(s.sound.XElement, e))) { s.sound.Dispose(); }
});
damageSounds.RemoveAll(s => s.sound.Disposed);
miscSounds?.ForEach(g => g.ForEach(s =>
{
if (!soundElements.Any(e => SoundElementsEquivalent(s.XElement, e))) { s.Dispose(); }
else { miscSoundList.Add(new KeyValuePair<string, Sound>(g.Key, s)); }
}));
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;
loadedSoundElements = soundElements;
yield return CoroutineStatus.Success;
}
public static void Update(float deltaTime)
{
if (!Initialized) { return; }
UpdateMusic(deltaTime);
if (startUpSound != null && !GameMain.SoundManager.IsPlaying(startUpSound))
{
startUpSound.Dispose();
startUpSound = null;
}
//stop water sounds if no sub is loaded
if (Submarine.MainSub == null || Screen.Selected != GameMain.GameScreen)
{
for (int i = 0; i < waterAmbienceChannels.Length; i++)
{
if (waterAmbienceChannels[i] == null) continue;
waterAmbienceChannels[i].FadeOutAndDispose();
waterAmbienceChannels[i] = null;
}
for (int i = 0; i < FlowSounds.Count; i++)
{
if (flowSoundChannels[i] == null) continue;
flowSoundChannels[i].FadeOutAndDispose();
flowSoundChannels[i] = null;
}
for (int i = 0; i < fireSoundChannels.Length; i++)
{
if (fireSoundChannels[i] == null) continue;
fireSoundChannels[i].FadeOutAndDispose();
fireSoundChannels[i] = null;
}
fireVolumeLeft[0] = 0.0f; fireVolumeLeft[1] = 0.0f;
fireVolumeRight[0] = 0.0f; fireVolumeRight[1] = 0.0f;
return;
}
float ambienceVolume = 0.8f;
if (Character.Controlled != null && !Character.Controlled.Removed)
{
AnimController animController = Character.Controlled.AnimController;
if (animController.HeadInWater)
{
ambienceVolume = 1.0f;
ambienceVolume += animController.Limbs[0].LinearVelocity.Length();
}
}
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;
foreach (Submarine sub in Submarine.Loaded)
{
float movementFactor = (sub.Velocity == Vector2.Zero) ? 0.0f : sub.Velocity.Length() / 10.0f;
movementFactor = MathHelper.Clamp(movementFactor, 0.0f, 1.0f);
if (Character.Controlled == null || Character.Controlled.Submarine != sub)
{
float dist = Vector2.Distance(GameMain.GameScreen.Cam.WorldViewCenter, sub.WorldPosition);
movementFactor = movementFactor / Math.Max(dist / 1000.0f, 1.0f);
}
movementSoundVolume = Math.Max(movementSoundVolume, movementFactor);
if (!MathUtils.IsValid(movementSoundVolume))
{
string errorMsg = "Failed to update water ambience volume - submarine's movement value invalid (" + movementSoundVolume + ", sub velocity: " + sub.Velocity + ")";
DebugConsole.Log(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("SoundPlayer.UpdateWaterAmbience:InvalidVolume", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
movementSoundVolume = 0.0f;
}
}
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 (Math.Abs(diff.X) < fs.Size.X / 2.0f) { diffLeft.X = 0.0f; }
if (diffLeft.X <= 0)
{
float distFallOffLeft = diffLeft.Length() / FireSoundRange;
if (distFallOffLeft < 0.99f)
{
fireVolumeLeft[0] += (1.0f - distFallOffLeft);
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 (Math.Abs(diff.X) < fs.Size.X / 2.0f) { diffRight.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].FadeOutAndDispose();
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 -= deltaTime;
}
else
{
PlaySound(
"ambient",
new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y) + Rand.Vector(100.0f),
Rand.Range(0.5f, 1.0f),
1000.0f);
ambientSoundTimer = Rand.Range(ambientSoundInterval.X, ambientSoundInterval.Y);
}
}
public static Sound GetSound(string soundTag)
{
var matchingSounds = miscSounds[soundTag].ToList();
if (matchingSounds.Count == 0) return null;
return matchingSounds[Rand.Int(matchingSounds.Count)];
}
/// <summary>
/// Play a sound defined in a sound xml file without any positional effects.
/// </summary>
public static SoundChannel PlaySound(string soundTag, float volume = 1.0f)
{
var sound = GetSound(soundTag);
return sound?.Play(volume);
}
/// <summary>
/// Play a sound defined in a sound xml file. If the volume or range parameters are omitted, the volume and range defined in the sound xml are used.
/// </summary>
public static SoundChannel PlaySound(string soundTag, Vector2 position, float? volume = null, float? range = null, Hull hullGuess = null)
{
var sound = GetSound(soundTag);
if (sound == null) { return null; }
return PlaySound(sound, position, volume ?? sound.BaseGain, range ?? sound.BaseFar, hullGuess);
}
public static SoundChannel PlaySound(Sound sound, Vector2 position, float? volume = null, float? range = null, Hull hullGuess = null)
{
if (sound == null)
{
string errorMsg = "Error in SoundPlayer.PlaySound (sound was null)\n" + Environment.StackTrace;
GameAnalyticsManager.AddErrorEventOnce("SoundPlayer.PlaySound:SoundNull" + Environment.StackTrace, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return null;
}
float far = range ?? sound.BaseFar;
if (Vector2.DistanceSquared(new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y), position) > far * far) return null;
return sound.Play(volume ?? sound.BaseGain, far, position, muffle: ShouldMuffleSound(Character.Controlled, position, far, hullGuess));
}
private static void UpdateMusic(float deltaTime)
{
if (musicClips == null || GameMain.SoundManager.Disabled) { return; }
if (OverrideMusicType != null && OverrideMusicDuration.HasValue)
{
OverrideMusicDuration -= deltaTime;
if (OverrideMusicDuration <= 0.0f)
{
OverrideMusicType = null;
OverrideMusicDuration = null;
}
}
updateMusicTimer -= deltaTime;
if (updateMusicTimer <= 0.0f)
{
//find appropriate music for the current situation
string currentMusicType = GetCurrentMusicType();
float currentIntensity = GameMain.GameSession?.EventManager != null ?
GameMain.GameSession.EventManager.CurrentIntensity * 100.0f : 0.0f;
IEnumerable<BackgroundMusic> suitableMusic = GetSuitableMusicClips(currentMusicType, currentIntensity);
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 = Screen.Selected == GameMain.GameScreen ?
GetSuitableMusicClips("intensity", currentIntensity) :
Enumerable.Empty<BackgroundMusic>();
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[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;
}
int activeTrackCount = targetMusic.Count(m => m != null);
for (int i = 0; i < MaxMusicChannels; i++)
{
//nothing should be playing on this channel
if (targetMusic[i] == null)
{
if (musicChannel[i] != null && musicChannel[i].IsPlaying)
{
//mute the channel
musicChannel[i].Gain = MathHelper.Lerp(musicChannel[i].Gain, 0.0f, MusicLerpSpeed * deltaTime);
if (musicChannel[i].Gain < 0.01f) DisposeMusicChannel(i);
}
}
//something should be playing, but the targetMusic is invalid
else if (!musicClips.Any(mc => mc.File == targetMusic[i].File))
{
targetMusic[i] = GetSuitableMusicClips(targetMusic[i].Type, 0.0f).GetRandom();
}
//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");
if (targetMusic[i].ContinueFromPreviousTime)
{
musicChannel[i].StreamSeekPos = targetMusic[i].PreviousTime;
}
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;
}
float targetGain = 1.0f;
if (targetMusic[i].DuckVolume)
{
targetGain = (float)Math.Sqrt(1.0f / activeTrackCount);
}
musicChannel[i].Gain = MathHelper.Lerp(musicChannel[i].Gain, targetGain, MusicLerpSpeed * deltaTime);
}
}
}
private static void DisposeMusicChannel(int index)
{
var clip = musicClips.Find(m => m.File == musicChannel[index]?.Sound?.Filename);
if (clip != null)
{
if (clip.ContinueFromPreviousTime) { clip.PreviousTime = musicChannel[index].StreamSeekPos; }
}
musicChannel[index]?.Dispose(); musicChannel[index] = null;
currentMusic[index]?.Dispose(); currentMusic[index] = null;
}
private static IEnumerable<BackgroundMusic> GetSuitableMusicClips(string musicType, float currentIntensity)
{
return musicClips.Where(music =>
music != null &&
music.Type == musicType &&
currentIntensity >= music.IntensityRange.X &&
currentIntensity <= music.IntensityRange.Y);
}
private static string GetCurrentMusicType()
{
if (OverrideMusicType != null) { return OverrideMusicType; }
if (Screen.Selected == null) { return "menu"; }
if (Screen.Selected == GameMain.CharacterEditorScreen ||
Screen.Selected == GameMain.LevelEditorScreen ||
Screen.Selected == GameMain.ParticleEditorScreen ||
Screen.Selected == GameMain.SpriteEditorScreen ||
Screen.Selected == GameMain.SubEditorScreen)
{
return "editor";
}
if (Screen.Selected != GameMain.GameScreen) { return "menu"; }
if (Character.Controlled != null &&
Level.Loaded != null && Level.Loaded.Ruins != null &&
Level.Loaded.Ruins.Any(r => r.Area.Contains(Character.Controlled.WorldPosition)))
{
return "ruins";
}
Submarine targetSubmarine = Character.Controlled?.Submarine;
if ((targetSubmarine != null && targetSubmarine.AtDamageDepth) ||
(GameMain.GameScreen != null && Screen.Selected == GameMain.GameScreen && GameMain.GameScreen.Cam.Position.Y < SubmarineBody.DamageDepth))
{
return "deep";
}
if (targetSubmarine != null)
{
float floodedArea = 0.0f;
float totalArea = 0.0f;
foreach (Hull hull in Hull.hullList)
{
if (hull.Submarine != targetSubmarine) continue;
floodedArea += hull.WaterVolume;
totalArea += hull.Volume;
}
if (totalArea > 0.0f && floodedArea / totalArea > 0.25f) return "flooded";
}
float enemyDistThreshold = 5000.0f;
if (targetSubmarine != null)
{
enemyDistThreshold = Math.Max(enemyDistThreshold, Math.Max(targetSubmarine.Borders.Width, targetSubmarine.Borders.Height) * 2.0f);
}
foreach (Character character in Character.CharacterList)
{
if (character.IsDead || !character.Enabled) continue;
if (!(character.AIController is EnemyAIController enemyAI) || (!enemyAI.AttackHumans && !enemyAI.AttackRooms)) continue;
if (targetSubmarine != null)
{
if (Vector2.DistanceSquared(character.WorldPosition, targetSubmarine.WorldPosition) < enemyDistThreshold * enemyDistThreshold)
{
return "monster";
}
}
else if (Character.Controlled != null)
{
if (Vector2.DistanceSquared(character.WorldPosition, Character.Controlled.WorldPosition) < enemyDistThreshold * enemyDistThreshold)
{
return "monster";
}
}
}
if (GameMain.GameSession != null)
{
if (Submarine.Loaded != null && Level.Loaded != null && Submarine.MainSub.AtEndPosition)
{
return "levelend";
}
if (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;
}
Vector2 soundPos = soundWorldPos;
if (targetHull.Submarine != null)
{
soundPos += -targetHull.Submarine.WorldPosition + targetHull.Submarine.HiddenSubPosition;
}
return listener.CurrentHull.GetApproximateDistance(listener.Position, soundPos, 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);
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, IEnumerable<string> tags = null)
{
damage = MathHelper.Clamp(damage + Rand.Range(-10.0f, 10.0f), 0.0f, 100.0f);
var sounds = damageSounds.FindAll(s =>
(s.damageRange == Vector2.Zero ||
(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, muffle: ShouldMuffleSound(Character.Controlled, position, range, null));
}
}
}
@@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenAL;
using Microsoft.Xna.Framework;
using System.Runtime.InteropServices;
using System.Threading;
using Barotrauma.Media;
namespace Barotrauma.Sounds
{
public class VideoSound : Sound
{
private readonly object mutex;
private Queue<short[]> sampleQueue;
private SoundChannel soundChannel;
private Video video;
public VideoSound(SoundManager owner, string filename, int sampleRate, int channelCount, Video vid) : base(owner, filename, true, false)
{
ALFormat = channelCount == 2 ? Al.FormatStereo16 : Al.FormatMono16;
SampleRate = sampleRate;
sampleQueue = new Queue<short[]>();
mutex = new object();
soundChannel = null;
video = vid;
}
public override float GetAmplitudeAtPlaybackPos(int playbackPos)
{
throw new NotImplementedException();
}
public override bool IsPlaying()
{
bool retVal = false;
lock (mutex)
{
retVal = soundChannel != null && soundChannel.IsPlaying;
}
return retVal;
}
public void Enqueue(short[] buf)
{
lock (mutex)
{
sampleQueue.Enqueue(buf);
}
}
public override SoundChannel Play(float gain, float range, Vector2 position, bool muffle = false)
{
throw new InvalidOperationException();
}
public override SoundChannel Play(Vector3? position, float gain, bool muffle = false)
{
throw new InvalidOperationException();
}
public override SoundChannel Play(float gain)
{
SoundChannel chn = null;
lock (mutex)
{
if (soundChannel != null)
{
soundChannel.Dispose();
soundChannel = null;
}
}
chn = new SoundChannel(this, gain, null, 1.0f, 3.0f, "video", false);
lock (mutex)
{
soundChannel = chn;
}
return chn;
}
public override SoundChannel Play()
{
return Play(BaseGain);
}
public override int FillStreamBuffer(int samplePos, short[] buffer)
{
if (!video.IsPlaying) return -1;
short[] buf;
int readAmount = 0;
lock (mutex)
{
while (readAmount<buffer.Length)
{
if (sampleQueue.Count == 0) break;
buf = sampleQueue.Peek();
if (readAmount + buf.Length >= buffer.Length) break;
buf = sampleQueue.Dequeue();
buf.CopyTo(buffer, readAmount);
readAmount += buf.Length;
}
}
return readAmount*2;
}
public override void Dispose()
{
lock (mutex)
{
if (soundChannel != null)
{
soundChannel.Dispose();
}
base.Dispose();
}
}
}
}
@@ -0,0 +1,176 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using OpenAL;
using System;
using System.Collections.Generic;
namespace Barotrauma.Sounds
{
public class VoipSound : Sound
{
public override SoundManager.SourcePoolIndex SourcePoolIndex
{
get
{
return SoundManager.SourcePoolIndex.Voice;
}
}
public new bool IsPlaying
{
get
{
return soundChannel != null && soundChannel.IsPlaying;
}
}
private VoipQueue queue;
private int bufferID = 0;
private SoundChannel soundChannel;
public bool UseRadioFilter;
public bool UseMuffleFilter;
public float Near { get; private set; }
public float Far { get; private set; }
private static BiQuad[] muffleFilters = new BiQuad[]
{
new LowpassFilter(VoipConfig.FREQUENCY, 800)
};
private static BiQuad[] radioFilters = new BiQuad[]
{
new BandpassFilter(VoipConfig.FREQUENCY, 2000)
};
public float Gain
{
get { return soundChannel == null ? 0.0f : soundChannel.Gain; }
set
{
if (soundChannel == null) { return; }
soundChannel.Gain = value;
}
}
public float CurrentAmplitude
{
get { return soundChannel?.CurrentAmplitude ?? 0.0f; }
}
public VoipSound(string name, SoundManager owner, VoipQueue q) : base(owner, "voip", true, true)
{
Filename = $"VoIP ({name})";
VoipConfig.SetupEncoding();
ALFormat = Al.FormatMono16;
SampleRate = VoipConfig.FREQUENCY;
queue = q;
bufferID = queue.LatestBufferID;
soundChannel = null;
SoundChannel chn = new SoundChannel(this, 1.0f, null, 0.4f, 1.0f, "voip", false);
soundChannel = chn;
}
public override float GetAmplitudeAtPlaybackPos(int playbackPos)
{
throw new NotImplementedException(); //TODO: implement?
}
public void SetPosition(Vector3? pos)
{
soundChannel.Position = pos;
}
public void SetRange(float near, float far)
{
soundChannel.Near = Near = near;
soundChannel.Far = Far = far;
}
public void ApplyFilters(short[] buffer, int readSamples)
{
for (int i = 0; i < readSamples; i++)
{
float fVal = ShortToFloat(buffer[i]);
if (UseMuffleFilter)
{
foreach (var filter in muffleFilters)
{
fVal = filter.Process(fVal);
}
}
if (UseRadioFilter)
{
foreach (var filter in radioFilters)
{
fVal = filter.Process(fVal);
}
}
buffer[i] = FloatToShort(fVal);
}
if (UseMuffleFilter)
{
ApplyFilters(muffleFilters, buffer, readSamples);
}
if (UseRadioFilter)
{
ApplyFilters(radioFilters, buffer, readSamples);
}
}
private void ApplyFilters(IEnumerable<BiQuad> filters, short[] buffer, int readSamples)
{
}
public override SoundChannel Play(float gain, float range, Vector2 position, bool muffle = false)
{
throw new InvalidOperationException();
}
public override SoundChannel Play(Vector3? position, float gain, bool muffle = false)
{
throw new InvalidOperationException();
}
public override SoundChannel Play(float gain)
{
throw new InvalidOperationException();
}
public override SoundChannel Play()
{
throw new InvalidOperationException();
}
public override int FillStreamBuffer(int samplePos, short[] buffer)
{
queue.RetrieveBuffer(bufferID, out int compressedSize, out byte[] compressedBuffer);
if (compressedSize > 0)
{
VoipConfig.Decoder.Decode(compressedBuffer, 0, compressedSize, buffer, 0, VoipConfig.BUFFER_SIZE);
bufferID++;
return VoipConfig.BUFFER_SIZE * 2;
}
if (bufferID < queue.LatestBufferID - (VoipQueue.BUFFER_COUNT - 1)) bufferID = queue.LatestBufferID - (VoipQueue.BUFFER_COUNT - 1);
return 0;
}
public override void Dispose()
{
if (soundChannel != null)
{
soundChannel.Dispose();
soundChannel = null;
}
base.Dispose();
}
}
}