Refactored SoundChannel and SoundManager to use explicit locking for OpenAL operations and channel assignment, preventing race conditions during parallel sound playback. Added thread-local stacks in DynamicTree to ensure thread safety during parallel physics queries and raycasts. These changes address concurrency issues when sounds or physics queries are triggered from multiple threads.
878 lines
34 KiB
C#
878 lines
34 KiB
C#
using Microsoft.Xna.Framework;
|
|
using OpenAL;
|
|
using System;
|
|
using System.Threading;
|
|
|
|
namespace Barotrauma.Sounds
|
|
{
|
|
class SoundSourcePool : IDisposable
|
|
{
|
|
public uint[] ALSources
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public SoundSourcePool(int sourceCount = SoundManager.SourceCount)
|
|
{
|
|
int alError;
|
|
|
|
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()
|
|
{
|
|
if (ALSources == null) { return; }
|
|
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;
|
|
}
|
|
}
|
|
|
|
class SoundChannel : IDisposable
|
|
{
|
|
private const int STREAM_BUFFER_SIZE = 8820;
|
|
private readonly 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))
|
|
{
|
|
DebugConsole.ThrowError("Failed to set source's position: " + debugName + ", position.X is NaN", appendStackTrace: true);
|
|
return;
|
|
}
|
|
if (float.IsNaN(position.Value.Y))
|
|
{
|
|
DebugConsole.ThrowError("Failed to set source's position: " + debugName + ", position.Y is NaN", appendStackTrace: true);
|
|
return;
|
|
}
|
|
if (float.IsNaN(position.Value.Z))
|
|
{
|
|
DebugConsole.ThrowError("Failed to set source's position: " + debugName + ", position.Z is NaN", appendStackTrace: true);
|
|
return;
|
|
}
|
|
|
|
if (float.IsInfinity(position.Value.X))
|
|
{
|
|
DebugConsole.ThrowError("Failed to set source's position: " + debugName + ", position.X is Infinity", appendStackTrace: true);
|
|
return;
|
|
}
|
|
if (float.IsInfinity(position.Value.Y))
|
|
{
|
|
DebugConsole.ThrowError("Failed to set source's position: " + debugName + ", position.Y is Infinity", appendStackTrace: true);
|
|
return;
|
|
}
|
|
if (float.IsInfinity(position.Value.Z))
|
|
{
|
|
DebugConsole.ThrowError("Failed to set source's position: " + debugName + ", position.Z is Infinity", appendStackTrace: true);
|
|
return;
|
|
}
|
|
|
|
uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex);
|
|
Al.Sourcei(alSource, Al.SourceRelative, Al.False);
|
|
int alError = Al.GetError();
|
|
if (alError != Al.NoError)
|
|
{
|
|
DebugConsole.ThrowError("Failed to enable source's relative flag: " + debugName + ", " + Al.GetErrorString(alError), appendStackTrace: true);
|
|
return;
|
|
}
|
|
|
|
Al.Source3f(alSource, Al.Position, position.Value.X, position.Value.Y, position.Value.Z);
|
|
alError = Al.GetError();
|
|
if (alError != Al.NoError)
|
|
{
|
|
DebugConsole.ThrowError("Failed to set source's position: " + debugName + ", " + Al.GetErrorString(alError), appendStackTrace: true);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex);
|
|
Al.Sourcei(alSource, Al.SourceRelative, Al.True);
|
|
int alError = Al.GetError();
|
|
if (alError != Al.NoError)
|
|
{
|
|
DebugConsole.ThrowError("Failed to disable source's relative flag: " + debugName + ", " + Al.GetErrorString(alError), appendStackTrace: true);
|
|
return;
|
|
}
|
|
|
|
Al.Source3f(alSource, Al.Position, 0.0f, 0.0f, 0.0f);
|
|
alError = Al.GetError();
|
|
if (alError != Al.NoError)
|
|
{
|
|
DebugConsole.ThrowError("Failed to reset source's position: " + debugName + ", " + Al.GetErrorString(alError), appendStackTrace: true);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
DebugConsole.ThrowError("Failed to set source's reference distance: " + debugName + ", " + Al.GetErrorString(alError), appendStackTrace: true);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
DebugConsole.ThrowError("Failed to set source's max distance: " + debugName + ", " + Al.GetErrorString(alError), appendStackTrace: true);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
private float gain;
|
|
public float Gain
|
|
{
|
|
get { return gain; }
|
|
set
|
|
{
|
|
if (!MathUtils.IsValid(value)) { return; }
|
|
|
|
gain = Math.Max(value, 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)
|
|
{
|
|
DebugConsole.ThrowError($"Failed to set source's gain to {gain} (effective gain {effectiveGain}): {debugName}, {Al.GetErrorString(alError)}", appendStackTrace: true);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
DebugConsole.ThrowError("Failed to set source's looping state: " + debugName + ", " + Al.GetErrorString(alError), appendStackTrace: true);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public const float MinFrequencyMultiplier = 0.25f;
|
|
public const float MaxFrequencyMultiplier = 4.0f;
|
|
|
|
public float frequencyMultiplier;
|
|
public float FrequencyMultiplier
|
|
{
|
|
get
|
|
{
|
|
return frequencyMultiplier;
|
|
}
|
|
set
|
|
{
|
|
if (value is < MinFrequencyMultiplier or > MaxFrequencyMultiplier)
|
|
{
|
|
DebugConsole.ThrowError($"Frequency multiplier out of range: {value}" + Environment.StackTrace.CleanupStackTrace());
|
|
}
|
|
frequencyMultiplier = Math.Clamp(value, MinFrequencyMultiplier, MaxFrequencyMultiplier);
|
|
|
|
if (ALSourceIndex < 0) { return; }
|
|
|
|
uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex);
|
|
|
|
Al.Sourcef(alSource, Al.Pitch, frequencyMultiplier);
|
|
int alError = Al.GetError();
|
|
if (alError != Al.NoError)
|
|
{
|
|
throw new Exception("Failed to set source's frequency multiplier: " + 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) { return; }
|
|
|
|
uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex);
|
|
Al.GetSourcei(alSource, Al.SampleOffset, out int playbackPos);
|
|
int alError = Al.GetError();
|
|
if (alError != Al.NoError)
|
|
{
|
|
DebugConsole.ThrowError("Failed to get source's playback position: " + debugName + ", " + Al.GetErrorString(alError), appendStackTrace: true);
|
|
return;
|
|
}
|
|
|
|
Al.SourceStop(alSource);
|
|
|
|
alError = Al.GetError();
|
|
if (alError != Al.NoError)
|
|
{
|
|
DebugConsole.ThrowError("Failed to stop source: " + debugName + ", " + Al.GetErrorString(alError), appendStackTrace: true);
|
|
return;
|
|
}
|
|
|
|
Sound.FillAlBuffers();
|
|
if (Sound.Buffers is not { AlBuffer: not 0, AlMuffledBuffer: not 0 }) { return; }
|
|
|
|
Al.Sourcei(alSource, Al.Buffer, muffled ? (int)Sound.Buffers.AlMuffledBuffer : (int)Sound.Buffers.AlBuffer);
|
|
|
|
alError = Al.GetError();
|
|
if (alError != Al.NoError)
|
|
{
|
|
DebugConsole.ThrowError("Failed to bind buffer to source: " + debugName + ", " + Al.GetErrorString(alError), appendStackTrace: true);
|
|
return;
|
|
}
|
|
|
|
Al.SourcePlay(alSource);
|
|
alError = Al.GetError();
|
|
if (alError != Al.NoError)
|
|
{
|
|
DebugConsole.ThrowError("Failed to replay source: " + debugName + ", " + Al.GetErrorString(alError), appendStackTrace: true);
|
|
return;
|
|
}
|
|
|
|
Al.Sourcei(alSource, Al.SampleOffset, playbackPos);
|
|
alError = Al.GetError();
|
|
if (alError != Al.NoError)
|
|
{
|
|
DebugConsole.ThrowError("Failed to reset playback position: " + debugName + ", " + Al.GetErrorString(alError), appendStackTrace: true);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
Al.GetSourcei(alSource, Al.SampleOffset, out int playbackPos);
|
|
int alError = Al.GetError();
|
|
if (alError != Al.NoError)
|
|
{
|
|
DebugConsole.ThrowError("Failed to get source's playback position: " + debugName + ", " + Al.GetErrorString(alError), appendStackTrace: true);
|
|
return 0.0f;
|
|
}
|
|
return Sound.GetAmplitudeAtPlaybackPos(playbackPos);
|
|
}
|
|
else
|
|
{
|
|
float retVal;
|
|
Monitor.Enter(mutex);
|
|
retVal = streamAmplitude;
|
|
Monitor.Exit(mutex);
|
|
return retVal;
|
|
}
|
|
}
|
|
}
|
|
|
|
private Identifier category;
|
|
public Identifier 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 readonly uint[] unqueuedBuffers;
|
|
private readonly float[] streamBufferAmplitudes;
|
|
|
|
public bool MuteBackgroundMusic;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
public long MaxStreamSeekPos
|
|
{
|
|
get
|
|
{
|
|
if (!IsStream || Sound is not OggSound oggSound)
|
|
{
|
|
return 0;
|
|
}
|
|
return oggSound.MaxStreamSamplePos;
|
|
}
|
|
}
|
|
|
|
private readonly object mutex;
|
|
|
|
public bool IsPlaying
|
|
{
|
|
get
|
|
{
|
|
if (ALSourceIndex < 0) { return false; }
|
|
if (IsStream && !reachedEndSample) { return true; }
|
|
uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex);
|
|
if (!Al.IsSource(alSource)) { return false; }
|
|
Al.GetSourcei(alSource, Al.SourceState, out int state);
|
|
int alError = Al.GetError();
|
|
if (alError != Al.NoError)
|
|
{
|
|
DebugConsole.ThrowError("Failed to determine playing state from source: " + debugName + ", " + Al.GetErrorString(alError), appendStackTrace: true);
|
|
return false;
|
|
}
|
|
bool playing = state == Al.Playing;
|
|
return playing;
|
|
}
|
|
}
|
|
|
|
public SoundChannel(Sound sound, float gain, Vector3? position, float freqMult, float near, float far, Identifier 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();
|
|
}
|
|
|
|
// Use the playingChannels lock to protect both channel assignment AND OpenAL operations.
|
|
// This prevents race conditions when multiple threads try to play sounds simultaneously
|
|
// (e.g., during Parallel.ForEach in MapEntity.UpdateAll).
|
|
int poolIndex = (int)sound.SourcePoolIndex;
|
|
object channelsLock = sound.Owner.GetPlayingChannelsLock(sound.SourcePoolIndex);
|
|
|
|
#if !DEBUG
|
|
try
|
|
{
|
|
#endif
|
|
lock (channelsLock)
|
|
{
|
|
if (mutex != null) { Monitor.Enter(mutex); }
|
|
try
|
|
{
|
|
if (sound.Owner.CountPlayingInstancesUnsafe(sound, poolIndex) < sound.MaxSimultaneousInstances)
|
|
{
|
|
ALSourceIndex = sound.Owner.AssignFreeSourceToChannelUnsafe(this, poolIndex);
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
Sound.FillAlBuffers();
|
|
if (Sound.Buffers is not { AlBuffer: not 0, AlMuffledBuffer: not 0 }) { return; }
|
|
|
|
uint alBuffer = sound.Owner.GetCategoryMuffle(category) || muffled ? Sound.Buffers.AlMuffledBuffer : Sound.Buffers.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) + "," + alBuffer.ToString() + "): " + debugName + ", " + Al.GetErrorString(alError));
|
|
}
|
|
|
|
SetProperties();
|
|
|
|
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 = 0;
|
|
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.InitUpdateChannelThread();
|
|
SetProperties();
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (mutex != null) { Monitor.Exit(mutex); }
|
|
}
|
|
}
|
|
#if !DEBUG
|
|
}
|
|
catch
|
|
{
|
|
throw;
|
|
}
|
|
#endif
|
|
|
|
void SetProperties()
|
|
{
|
|
this.Position = position;
|
|
this.Gain = gain;
|
|
this.FrequencyMultiplier = freqMult;
|
|
this.Looping = false;
|
|
this.Near = near;
|
|
this.Far = far;
|
|
this.Category = category;
|
|
}
|
|
|
|
Sound.Owner.Update();
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return debugName;
|
|
}
|
|
|
|
public bool FadingOutAndDisposing
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
public void FadeOutAndDispose()
|
|
{
|
|
FadingOutAndDisposing = true;
|
|
Sound.Owner.InitUpdateChannelThread();
|
|
}
|
|
|
|
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()
|
|
{
|
|
try
|
|
{
|
|
if (!IsStream) { throw new Exception("Called UpdateStream on a non-streamed sound channel!"); }
|
|
|
|
Monitor.Enter(mutex);
|
|
if (!reachedEndSample)
|
|
{
|
|
uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex);
|
|
|
|
Al.GetSourcei(alSource, Al.SourceState, out int 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));
|
|
}
|
|
|
|
Al.GetSourcei(alSource, Al.BuffersProcessed, out int 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 (readSamples <= 0)
|
|
{
|
|
streamAmplitude *= 0.5f;
|
|
decayTimer++;
|
|
if (decayTimer > 120) //TODO: replace magic number
|
|
{
|
|
reachedEndSample = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Sound is VoipSound voipSound)
|
|
{
|
|
voipSound.ApplyFilters(buffer, readSamples);
|
|
}
|
|
|
|
decayTimer = 0;
|
|
}
|
|
}
|
|
else if (Sound.StreamsReliably)
|
|
{
|
|
streamSeekPos += readSamples * 2;
|
|
if (readSamples * 2 < 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 * 2, 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);
|
|
}
|
|
}
|
|
}
|
|
}
|