Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaClient/Source/Sounds/SoundManager.cs
T
Joonas Rikkonen 80ab27df22 c2e39cc...409d4d9
commit 409d4d96ead69028a164274637d23e350acb73fb
Merge: 95169f539 26e89c63d
Author: EdusFF <pitkanen.eetu@gmail.com>
Date:   Mon Mar 11 15:13:27 2019 +0200

    Merge branch 'dev' of github.com:Regalis11/Barotrauma into dev

commit 95169f53937f9a7e168a884171eaa21ae7f08023
Author: EdusFF <pitkanen.eetu@gmail.com>
Date:   Mon Mar 11 15:13:11 2019 +0200

    Modified: ServerMessage structure to allow _ ; in player & submarine names

commit 26e89c63dc8da771aea9f09978a630a6cff60a6f
Merge: b7646d06d fb0b821bc
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 11 14:42:44 2019 +0200

    Merge branch 'kuraiookami-logicExpantion' into dev

    # Conflicts:
    #       Barotrauma/BarotraumaShared/SharedContent.projitems

commit fb0b821bc97891cdeec8f2c740a12119696393ea
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 11 14:41:21 2019 +0200

    Use invariant culture when parsing floats or converting them to strings in signal components

commit f0c8afba934b41358cf5d59a22b87caf33f98a61
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 11 14:00:44 2019 +0200

    Update new signal components to use identifiers & added names and descriptions to the text file, use invariant culture in equalscomponent, memorycomponent doesn't require the signals to be floats

commit 674d9ec804fc4770b602d4b09240b08cafc8ccec
Merge: 3ea33fb54 242e2429f
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 11 12:01:27 2019 +0200

    Merge branch 'logicExpantion' of https://github.com/kuraiookami/Barotrauma into kuraiookami-logicExpantion

    # Conflicts:
    #       Barotrauma/BarotraumaShared/BarotraumaShared.projitems
    #       Barotrauma/BarotraumaShared/Content/Items/Electricity/poweritems.xml
    #       Barotrauma/BarotraumaShared/Content/Items/Electricity/signalitems.xml
    #       Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs
    #       Barotrauma/BarotraumaShared/Source/Items/Components/Signal/AdderComponent.cs

commit b7646d06d53fb05227276e6286d0e15da5dc9080
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 11 11:37:33 2019 +0200

    Re-enabled multiplayer campaign

commit cf7258f6410a5995c881ec6e95eb9def5cd90ad4
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 11 11:28:48 2019 +0200

    Fixed item interfaces getting repositioned every frame when the editing HUD is open. Closes #1212

commit e8906239c779cf71de694bc65c81058e5cae16ef
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 11 11:12:05 2019 +0200

    Fixed VoipCapture creating new "could not start voice capture" popups constantly if there's no suitable capture device. Closes #1262

commit a30f47fbe47fde4fccb0453c1773a76d730d226b
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Sun Mar 10 19:04:59 2019 +0200

    Disable audio instead of crashing if no audio device is found. Closes #1214

commit 242e2429fd2c3ed199ac26b55e2cbdc8636e73f9
Author: Darkwolf <Darkwolf0101@gmail.com>
Date:   Mon Jan 21 21:26:57 2019 -0600

    Expansion of Barotrauma's logic system.

    Changed:
    - AdderComponent and children can clamp their output
    - Powercontainer signals for charge,charge% and charge rate

    Added:
    - ColorComponent: Dynamic signals for light set_color inputs
    - MemoryComponent: Stores and sends a signal that is edge latched
    - DivideComponent: Standard division
    - MultiplyComponent: Standard multiplication
    - SubtractComponent: Standard subtraction
    - XorComponent: Exclusive or
    - EqualsComponent: Equals comparison
    - GreaterComponent: Greater than comparison
2019-03-18 22:50:18 +02:00

523 lines
19 KiB
C#

using System;
using System.Threading;
using System.Collections.Generic;
using System.Xml.Linq;
using OpenTK.Audio.OpenAL;
using Microsoft.Xna.Framework;
using System.Linq;
using System.IO;
namespace Barotrauma.Sounds
{
public class SoundManager : IDisposable
{
public const int SOURCE_COUNT = 32;
public bool Disabled
{
Default = 0,
Voice = 1
}
private IntPtr alcDevice;
private OpenTK.ContextHandle alcContext;
public enum SourcePoolIndex
{
Default = 0,
Voice = 1
}
private readonly SoundSourcePool[] sourcePools;
private List<Sound> loadedSounds;
private readonly SoundChannel[][] playingChannels = new SoundChannel[2][];
private Thread streamingThread;
private Vector3 listenerPosition;
public Vector3 ListenerPosition
{
get { return listenerPosition; }
set
{
if (Disabled) { return; }
listenerPosition = value;
AL.Listener(ALListener3f.Position,value.X,value.Y,value.Z);
ALError alError = AL.GetError();
if (alError != ALError.NoError)
{
throw new Exception("Failed to set listener position: " + AL.GetErrorString(alError));
}
}
}
private 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.Listener(ALListenerfv.Orientation, ref listenerOrientation);
ALError alError = AL.GetError();
if (alError != ALError.NoError)
{
throw new Exception("Failed to set listener target vector: " + AL.GetErrorString(alError));
}
}
}
public Vector3 ListenerUpVector
{
get { return new Vector3(listenerOrientation[3], listenerOrientation[4], listenerOrientation[5]); }
set
{
if (Disabled) { return; }
listenerOrientation[3] = value.X; listenerOrientation[4] = value.Y; listenerOrientation[5] = value.Z;
AL.Listener(ALListenerfv.Orientation, ref listenerOrientation);
ALError alError = AL.GetError();
if (alError != ALError.NoError)
{
throw new Exception("Failed to set listener up vector: " + AL.GetErrorString(alError));
}
}
}
private float listenerGain;
public float ListenerGain
{
get { return listenerGain; }
set
{
if (Disabled) { return; }
if (Math.Abs(ListenerGain - value) < 0.001f) { return; }
listenerGain = value;
AL.Listener(ALListenerf.Gain, listenerGain);
ALError alError = AL.GetError();
if (alError != ALError.NoError)
{
throw new Exception("Failed to set listener gain: " + AL.GetErrorString(alError));
}
}
}
public int LoadedSoundCount
{
get { return loadedSounds.Count; }
}
public int UniqueLoadedSoundCount
{
get { return loadedSounds.Select(s => s.Filename).Distinct().Count(); }
}
private Dictionary<string, Pair<float, bool>> categoryModifiers;
public SoundManager()
{
loadedSounds = new List<Sound>();
streamingThread = null;
categoryModifiers = null;
alcDevice = Alc.OpenDevice(null);
if (alcDevice == null)
{
DebugConsole.ThrowError("Failed to open an ALC device! Disabling audio playback...");
Disabled = true;
return;
}
AlcError alcError = Alc.GetError(alcDevice);
if (alcError != AlcError.NoError)
{
//The audio device probably wasn't ready, this happens quite often
//Just wait a while and try again
Thread.Sleep(100);
alcDevice = Alc.OpenDevice(null);
alcError = Alc.GetError(alcDevice);
if (alcError != AlcError.NoError)
{
DebugConsole.ThrowError("Error initializing ALC device: " + alcError.ToString() + ". Disabling audio playback...");
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 != AlcError.NoError)
{
DebugConsole.ThrowError("Error after assigning ALC context: " + alcError.ToString() + ". Disabling audio playback...");
Disabled = true;
return;
}
ALError alError = ALError.NoError;
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(8);
playingChannels[(int)SourcePoolIndex.Voice] = new SoundChannel[8];
AL.DistanceModel(ALDistanceModel.LinearDistanceClamped);
alError = AL.GetError();
if (alError != ALError.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);
}
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);
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);
if (newSound != null)
{
newSound.BaseGain = element.GetAttributeFloat("volume", 1.0f);
float range = element.GetAttributeFloat("range", 1000.0f);
newSound.BaseNear = range * 0.4f;
newSound.BaseFar = range;
}
loadedSounds.Add(newSound);
return newSound;
}
public SoundChannel GetSoundChannelFromIndex(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; }
lock (playingChannels)
{
//remove a channel that has stopped
//or hasn't even been assigned
int poolIndex = (int)newChannel.Sound.SourcePoolIndex;
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.Source(sourcePools[0].ALSources[i], ALSourcef.MaxGain, i == ind ? 1.0f : 0.0f);
AL.Source(sourcePools[0].ALSources[i], ALSourcef.MinGain, 0.0f);
}
}
#endif
public bool IsPlaying(Sound sound)
{
if (Disabled) { return false; }
lock (playingChannels)
{
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)
{
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;
}
#endif
public SoundChannel GetChannelFromSound(Sound sound)
{
if (Disabled) { return null; }
lock (playingChannels)
{
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)
{
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)
{
for (int i = 0; i < loadedSounds.Count; i++)
{
if (loadedSounds[i] == sound)
{
loadedSounds.RemoveAt(i);
return;
}
}
}
public void SetCategoryGainMultiplier(string category, float gain)
{
if (Disabled) { return; }
category = category.ToLower();
if (categoryModifiers == null) categoryModifiers = new Dictionary<string, Pair<float, bool>>();
if (!categoryModifiers.ContainsKey(category))
{
categoryModifiers.Add(category, new Pair<float, bool>(gain, false));
}
else
{
categoryModifiers[category].First = gain;
}
lock (playingChannels)
{
for (int i = 0; i < playingChannels.Length; 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)
{
if (Disabled) { return 0.0f; }
category = category.ToLower();
if (categoryModifiers == null || !categoryModifiers.ContainsKey(category)) return 1.0f;
return categoryModifiers[category].First;
}
public void SetCategoryMuffle(string category,bool muffle)
{
if (Disabled) { return; }
category = category.ToLower();
if (categoryModifiers == null) categoryModifiers = new Dictionary<string, Pair<float, bool>>();
if (!categoryModifiers.ContainsKey(category))
{
categoryModifiers.Add(category, new Pair<float, bool>(1.0f, muffle));
}
else
{
categoryModifiers[category].Second = muffle;
}
}
for (int i = 0; i < playingChannels.Length; 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].Second;
}
public void InitStreamThread()
{
if (Disabled) { return; }
if (streamingThread == null || streamingThread.ThreadState.HasFlag(ThreadState.Stopped))
{
streamingThread = new Thread(UpdateStreaming)
{
Name = "SoundManager Streaming Thread",
IsBackground = true //this should kill the thread if the game crashes
};
streamingThread.Start();
}
}
void UpdateStreaming()
{
bool areStreamsPlaying = true;
while (areStreamsPlaying)
{
areStreamsPlaying = false;
lock (playingChannels)
{
for (int i = 0; i < playingChannels.Length; i++)
{
for (int j = 0; j < playingChannels[i].Length; j++)
{
if (playingChannels[i][j] != null && playingChannels[i][j].IsStream)
{
if (playingChannels[i][j].IsPlaying)
{
areStreamsPlaying = true;
playingChannels[i][j].UpdateStream();
}
else
{
playingChannels[i][j].Dispose();
}
}
}
}
}
Thread.Sleep(50); //TODO: use a separate thread for network audio?
}
}
public void Dispose()
{
if (Disabled) { return; }
lock (playingChannels)
{
for (int i = 0; i < playingChannels.Length; i++)
{
for (int j = 0; j < playingChannels[i].Length; j++)
{
if (playingChannels[i][j] != null) playingChannels[i][j].Dispose();
}
}
}
if (streamingThread != null && !streamingThread.ThreadState.HasFlag(ThreadState.Stopped))
{
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(OpenTK.ContextHandle.Zero))
{
throw new Exception("Failed to detach the current ALC context! (error code: " + Alc.GetError(alcDevice).ToString() + ")");
}
Alc.DestroyContext(alcContext);
if (!Alc.CloseDevice(alcDevice))
{
throw new Exception("Failed to close ALC device!");
}
}
}
}