Client LuaCsForBarotrauma
SoundManager.cs
1 using System;
2 using System.Threading;
3 using System.Collections.Generic;
4 using System.Xml.Linq;
5 using OpenAL;
6 using Microsoft.Xna.Framework;
7 using System.Linq;
8 using Barotrauma.IO;
9 
10 namespace Barotrauma.Sounds
11 {
12  class SoundManager : IDisposable
13  {
14  public const int SourceCount = 32;
15  public const string SoundCategoryDefault = "default";
16  public const string SoundCategoryUi = "ui";
17  public const string SoundCategoryWaterAmbience = "waterambience";
18  public const string SoundCategoryMusic = "music";
19  public const string SoundCategoryVoip = "voip";
20 
21  public bool Disabled
22  {
23  get;
24  private set;
25  }
26 
27  private IntPtr alcDevice;
28  private IntPtr alcContext;
29 
30  public enum SourcePoolIndex
31  {
32  Default = 0,
33  Voice = 1
34  }
35  private readonly SoundSourcePool[] sourcePools;
36 
37  private readonly List<Sound> loadedSounds;
38  public IReadOnlyList<Sound> LoadedSounds => loadedSounds;
39 
40  private readonly SoundChannel[][] playingChannels = new SoundChannel[2][];
41  private readonly object threadDeathMutex = new object();
42 
43  public bool CanDetectDisconnect { get; private set; }
44 
45  public bool Disconnected { get; private set; }
46 
47  private Thread updateChannelsThread;
48 
49  private Vector3 listenerPosition;
50  public Vector3 ListenerPosition
51  {
52  get { return listenerPosition; }
53  set
54  {
55  if (Disabled) { return; }
56  listenerPosition = value;
57  Al.Listener3f(Al.Position,value.X,value.Y,value.Z);
58  int alError = Al.GetError();
59  if (alError != Al.NoError && !GameMain.IsExiting)
60  {
61  throw new Exception("Failed to set listener position: " + Al.GetErrorString(alError));
62  }
63  }
64  }
65 
66  private readonly float[] listenerOrientation = new float[6];
67  public Vector3 ListenerTargetVector
68  {
69  get { return new Vector3(listenerOrientation[0], listenerOrientation[1], listenerOrientation[2]); }
70  set
71  {
72  if (Disabled) { return; }
73  listenerOrientation[0] = value.X; listenerOrientation[1] = value.Y; listenerOrientation[2] = value.Z;
74  Al.Listenerfv(Al.Orientation, listenerOrientation);
75  int alError = Al.GetError();
76  if (alError != Al.NoError && !GameMain.IsExiting)
77  {
78  throw new Exception("Failed to set listener target vector: " + Al.GetErrorString(alError));
79  }
80  }
81  }
82  public Vector3 ListenerUpVector
83  {
84  get { return new Vector3(listenerOrientation[3], listenerOrientation[4], listenerOrientation[5]); }
85  set
86  {
87  if (Disabled) { return; }
88  listenerOrientation[3] = value.X; listenerOrientation[4] = value.Y; listenerOrientation[5] = value.Z;
89  Al.Listenerfv(Al.Orientation, listenerOrientation);
90  int alError = Al.GetError();
91  if (alError != Al.NoError && !GameMain.IsExiting)
92  {
93  throw new Exception("Failed to set listener up vector: " + Al.GetErrorString(alError));
94  }
95  }
96  }
97 
98  private float listenerGain;
99  public float ListenerGain
100  {
101  get { return listenerGain; }
102  set
103  {
104  if (Disabled) { return; }
105  if (Math.Abs(ListenerGain - value) < 0.001f) { return; }
106  listenerGain = value;
107  Al.Listenerf(Al.Gain, listenerGain);
108  int alError = Al.GetError();
109  if (alError != Al.NoError && !GameMain.IsExiting)
110  {
111  throw new Exception("Failed to set listener gain: " + Al.GetErrorString(alError));
112  }
113  }
114  }
115 
116  public float PlaybackAmplitude
117  {
118  get
119  {
120  if (Disabled) { return 0.0f; }
121  float aggregateAmplitude = 0.0f;
122  //NOTE: this is obviously not entirely accurate;
123  //It assumes a linear falloff model, and assumes that audio
124  //is simply added together to produce the final result.
125  //Adjustments may be needed under certain scenarios.
126  for (int i = 0; i < 2; i++)
127  {
128  foreach (SoundChannel soundChannel in playingChannels[i].Where(ch => ch != null))
129  {
130  float amplitude = soundChannel.CurrentAmplitude;
131  amplitude *= soundChannel.Gain;
132  float dist = Vector3.Distance(ListenerPosition, soundChannel.Position ?? ListenerPosition);
133  if (dist > soundChannel.Near)
134  {
135  amplitude *= 1.0f - Math.Min(1.0f, (dist - soundChannel.Near) / (soundChannel.Far - soundChannel.Near));
136  }
137  aggregateAmplitude += amplitude;
138  }
139  }
140  return aggregateAmplitude;
141  }
142  }
143 
144  public float CompressionDynamicRangeGain { get; private set; }
145 
146  private float voipAttenuatedGain;
147  private double lastAttenuationTime;
148  public float VoipAttenuatedGain
149  {
150  get { return voipAttenuatedGain; }
151  set
152  {
153  lastAttenuationTime = Timing.TotalTime;
154  voipAttenuatedGain = value;
155  }
156  }
157 
158  public int LoadedSoundCount
159  {
160  get { return loadedSounds.Count; }
161  }
163  {
164  get { return loadedSounds.Select(s => s.Filename).Distinct().Count(); }
165  }
166 
167  private class CategoryModifier
168  {
169  public float[] GainMultipliers;
170  public bool Muffle;
171 
172  public CategoryModifier(int gainMultiplierIndex, float gain, bool muffle)
173  {
174  Muffle = muffle;
175  GainMultipliers = new float[gainMultiplierIndex + 1];
176  for (int i = 0; i < GainMultipliers.Length; i++)
177  {
178  if (i == gainMultiplierIndex)
179  {
180  GainMultipliers[i] = gain;
181  }
182  else
183  {
184  GainMultipliers[i] = 1.0f;
185  }
186  }
187  }
188 
189  public void SetGainMultiplier(int index, float gain)
190  {
191  if (GainMultipliers.Length < index + 1)
192  {
193  int oldLength = GainMultipliers.Length;
194  Array.Resize(ref GainMultipliers, index + 1);
195  for (int i = oldLength; i < GainMultipliers.Length; i++)
196  {
197  GainMultipliers[i] = 1.0f;
198  }
199  }
200  GainMultipliers[index] = gain;
201  }
202  }
203 
204  private readonly Dictionary<string, CategoryModifier> categoryModifiers = new Dictionary<string, CategoryModifier>();
205 
206  public SoundManager()
207  {
208  loadedSounds = new List<Sound>();
209  updateChannelsThread = null;
210 
211  sourcePools = new SoundSourcePool[2];
212  playingChannels[(int)SourcePoolIndex.Default] = new SoundChannel[SourceCount];
213  playingChannels[(int)SourcePoolIndex.Voice] = new SoundChannel[16];
214 
215  string deviceName = GameSettings.CurrentConfig.Audio.AudioOutputDevice;
216 
217  if (string.IsNullOrEmpty(deviceName))
218  {
219  deviceName = Alc.GetString((IntPtr)null, Alc.DefaultDeviceSpecifier);
220  }
221 
222 #if (!OSX)
223  var audioDeviceNames = Alc.GetStringList((IntPtr)null, Alc.AllDevicesSpecifier);
224  if (audioDeviceNames.Any() && !audioDeviceNames.Any(n => n.Equals(deviceName, StringComparison.OrdinalIgnoreCase)))
225  {
226  deviceName = audioDeviceNames[0];
227  }
228 #endif
229  if (GameSettings.CurrentConfig.Audio.AudioOutputDevice != deviceName)
230  {
231  SetAudioOutputDevice(deviceName);
232  }
233 
234  InitializeAlcDevice(deviceName);
235 
236  ListenerPosition = Vector3.Zero;
237  ListenerTargetVector = new Vector3(0.0f, 0.0f, 1.0f);
238  ListenerUpVector = new Vector3(0.0f, -1.0f, 0.0f);
239 
241  }
242 
243  private static void SetAudioOutputDevice(string deviceName)
244  {
245  var config = GameSettings.CurrentConfig;
246  config.Audio.AudioOutputDevice = deviceName;
247  GameSettings.SetCurrentConfig(config);
248  }
249 
250  public bool InitializeAlcDevice(string deviceName)
251  {
252  ReleaseResources(true);
253 
254  DebugConsole.NewMessage($"Attempting to open ALC device \"{deviceName}\"");
255 
256  alcDevice = IntPtr.Zero;
257  int alcError;
258  for (int i = 0; i < 3; i++)
259  {
260  alcDevice = Alc.OpenDevice(deviceName);
261  if (alcDevice == IntPtr.Zero)
262  {
263  alcError = Alc.GetError(IntPtr.Zero);
264  DebugConsole.NewMessage($"ALC device initialization attempt #{i + 1} failed: device is null (error code {Alc.GetErrorString(alcError)})");
265  if (!string.IsNullOrEmpty(deviceName))
266  {
267  deviceName = null;
268  DebugConsole.NewMessage($"Switching to default device...");
269  }
270  }
271  else
272  {
273  alcError = Alc.GetError(alcDevice);
274  if (alcError != Alc.NoError)
275  {
276  DebugConsole.NewMessage($"ALC device initialization attempt #{i + 1} failed: error code {Alc.GetErrorString(alcError)}");
277  bool closed = Alc.CloseDevice(alcDevice);
278  if (!closed)
279  {
280  DebugConsole.NewMessage($"Failed to close ALC device");
281  }
282  alcDevice = IntPtr.Zero;
283  }
284  else
285  {
286  break;
287  }
288  }
289  }
290  if (alcDevice == IntPtr.Zero)
291  {
292  DebugConsole.ThrowError("ALC device creation failed too many times!");
293  Disabled = true;
294  return false;
295  }
296 
297  CanDetectDisconnect = Alc.IsExtensionPresent(alcDevice, "ALC_EXT_disconnect");
298  alcError = Alc.GetError(alcDevice);
299  if (alcError != Alc.NoError)
300  {
301  DebugConsole.ThrowError("Error determining if disconnect can be detected: " + alcError.ToString() + ". Disabling audio playback...");
302  Disabled = true;
303  return false;
304  }
305 
306  Disconnected = false;
307 
308  int[] alcContextAttrs = new int[] { };
309  alcContext = Alc.CreateContext(alcDevice, alcContextAttrs);
310  if (alcContext == null)
311  {
312  DebugConsole.ThrowError("Failed to create an ALC context! (error code: " + Alc.GetError(alcDevice).ToString() + "). Disabling audio playback...");
313  Disabled = true;
314  return false;
315  }
316 
317  if (!Alc.MakeContextCurrent(alcContext))
318  {
319  DebugConsole.ThrowError("Failed to assign the current ALC context! (error code: " + Alc.GetError(alcDevice).ToString() + "). Disabling audio playback...");
320  Disabled = true;
321  return false;
322  }
323 
324  alcError = Alc.GetError(alcDevice);
325  if (alcError != Alc.NoError)
326  {
327  DebugConsole.ThrowError("Error after assigning ALC context: " + Alc.GetErrorString(alcError) + ". Disabling audio playback...");
328  Disabled = true;
329  return false;
330  }
331 
333 
334  int alError = Al.GetError();
335  if (alError != Al.NoError)
336  {
337  DebugConsole.ThrowError("Error setting distance model: " + Al.GetErrorString(alError) + ". Disabling audio playback...");
338  Disabled = true;
339  return false;
340  }
341 
342  sourcePools[(int)SourcePoolIndex.Default] = new SoundSourcePool(SourceCount);
343  sourcePools[(int)SourcePoolIndex.Voice] = new SoundSourcePool(16);
344 
345  ReloadSounds();
346 
347  Disabled = false;
348 
349  return true;
350  }
351 
352  public Sound LoadSound(string filename, bool stream = false)
353  {
354  if (Disabled) { return null; }
355 
356  if (!File.Exists(filename))
357  {
358  throw new System.IO.FileNotFoundException("Sound file \"" + filename + "\" doesn't exist!");
359  }
360 
361 #if DEBUG
362  System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
363  sw.Start();
364 #endif
365  Sound newSound = new OggSound(this, filename, stream, null);
366  lock (loadedSounds)
367  {
368  loadedSounds.Add(newSound);
369  }
370 #if DEBUG
371  sw.Stop();
372  System.Diagnostics.Debug.WriteLine($"Loaded sound \"{filename}\" ({sw.ElapsedMilliseconds} ms).");
373 #endif
374  return newSound;
375  }
376 
377  public Sound LoadSound(ContentXElement element, bool stream = false, string overrideFilePath = null)
378  {
379  if (Disabled) { return null; }
380 
381  string filePath = overrideFilePath ?? element.GetAttributeContentPath("file")?.Value ?? "";
382  if (!File.Exists(filePath))
383  {
384  throw new System.IO.FileNotFoundException($"Sound file \"{filePath}\" doesn't exist! Content package \"{(element.ContentPackage?.Name ?? "Unknown")}\".");
385  }
386 
387  var newSound = new OggSound(this, filePath, stream, xElement: element)
388  {
389  BaseGain = element.GetAttributeFloat("volume", 1.0f)
390  };
391  float range = element.GetAttributeFloat("range", 1000.0f);
392  newSound.BaseNear = range * 0.4f;
393  newSound.BaseFar = range;
394 
395  lock (loadedSounds)
396  {
397  loadedSounds.Add(newSound);
398  }
399  return newSound;
400  }
401 
403  {
404  if (Disabled || ind < 0 || ind >= playingChannels[(int)poolIndex].Length) return null;
405  return playingChannels[(int)poolIndex][ind];
406  }
407 
408  public uint GetSourceFromIndex(SourcePoolIndex poolIndex, int srcInd)
409  {
410  if (Disabled || srcInd < 0 || srcInd >= sourcePools[(int)poolIndex].ALSources.Length) return 0;
411 
412  if (!Al.IsSource(sourcePools[(int)poolIndex].ALSources[srcInd]))
413  {
414  throw new Exception("alSources[" + srcInd.ToString() + "] is invalid!");
415  }
416 
417  return sourcePools[(int)poolIndex].ALSources[srcInd];
418  }
419 
421  {
422  if (Disabled) { return -1; }
423 
424  //remove a channel that has stopped
425  //or hasn't even been assigned
426  int poolIndex = (int)newChannel.Sound.SourcePoolIndex;
427 
428  lock (playingChannels[poolIndex])
429  {
430  for (int i = 0; i < playingChannels[poolIndex].Length; i++)
431  {
432  if (playingChannels[poolIndex][i] == null || !playingChannels[poolIndex][i].IsPlaying)
433  {
434  if (playingChannels[poolIndex][i] != null) { playingChannels[poolIndex][i].Dispose(); }
435  playingChannels[poolIndex][i] = newChannel;
436  return i;
437  }
438  }
439  }
440 
441  //we couldn't get a free source to assign to this channel!
442  return -1;
443  }
444 
445 #if DEBUG
446  public void DebugSource(int ind)
447  {
448  for (int i = 0; i < sourcePools[0].ALSources.Length; i++)
449  {
450  Al.Sourcef(sourcePools[0].ALSources[i], Al.MaxGain, i == ind ? 1.0f : 0.0f);
451  Al.Sourcef(sourcePools[0].ALSources[i], Al.MinGain, 0.0f);
452  }
453  }
454 #endif
455 
456  public bool IsPlaying(Sound sound)
457  {
458  if (Disabled) { return false; }
459  lock (playingChannels[(int)sound.SourcePoolIndex])
460  {
461  for (int i = 0; i < playingChannels[(int)sound.SourcePoolIndex].Length; i++)
462  {
463  if (playingChannels[(int)sound.SourcePoolIndex][i] != null &&
464  playingChannels[(int)sound.SourcePoolIndex][i].Sound == sound)
465  {
466  if (playingChannels[(int)sound.SourcePoolIndex][i].IsPlaying) return true;
467  }
468  }
469  }
470  return false;
471  }
472 
473  public int CountPlayingInstances(Sound sound)
474  {
475  if (Disabled) { return 0; }
476  int count = 0;
477  lock (playingChannels[(int)sound.SourcePoolIndex])
478  {
479  for (int i = 0; i < playingChannels[(int)sound.SourcePoolIndex].Length; i++)
480  {
481  if (playingChannels[(int)sound.SourcePoolIndex][i] != null &&
482  playingChannels[(int)sound.SourcePoolIndex][i].Sound.Filename == sound.Filename)
483  {
484  if (playingChannels[(int)sound.SourcePoolIndex][i].IsPlaying) { count++; };
485  }
486  }
487  }
488  return count;
489  }
490 
492  {
493  if (Disabled) { return null; }
494  lock (playingChannels[(int)sound.SourcePoolIndex])
495  {
496  for (int i = 0; i < playingChannels[(int)sound.SourcePoolIndex].Length; i++)
497  {
498  if (playingChannels[(int)sound.SourcePoolIndex][i] != null &&
499  playingChannels[(int)sound.SourcePoolIndex][i].Sound == sound)
500  {
501  if (playingChannels[(int)sound.SourcePoolIndex][i].IsPlaying) return playingChannels[(int)sound.SourcePoolIndex][i];
502  }
503  }
504  }
505  return null;
506  }
507 
508  public void KillChannels(Sound sound)
509  {
510  if (Disabled) { return; }
511  lock (playingChannels[(int)sound.SourcePoolIndex])
512  {
513  for (int i = 0; i < playingChannels[(int)sound.SourcePoolIndex].Length; i++)
514  {
515  if (playingChannels[(int)sound.SourcePoolIndex][i]!=null && playingChannels[(int)sound.SourcePoolIndex][i].Sound == sound)
516  {
517  playingChannels[(int)sound.SourcePoolIndex][i]?.Dispose();
518  playingChannels[(int)sound.SourcePoolIndex][i] = null;
519  }
520  }
521  }
522  }
523 
524  public void RemoveSound(Sound sound)
525  {
526  lock (loadedSounds)
527  {
528  for (int i = 0; i < loadedSounds.Count; i++)
529  {
530  if (loadedSounds[i] == sound)
531  {
532  loadedSounds.RemoveAt(i);
533  return;
534  }
535  }
536  }
537  }
538 
539  public void MoveSoundToPosition(Sound sound, int pos)
540  {
541  lock (loadedSounds)
542  {
543  int index = loadedSounds.IndexOf(sound);
544  if (index >= 0)
545  {
546  loadedSounds.SiftElement(index, pos);
547  }
548  }
549  }
550 
551  public void SetCategoryGainMultiplier(string category, float gain, int index=0)
552  {
553  if (Disabled) { return; }
554  category = category.ToLower();
555  lock (categoryModifiers)
556  {
557  if (!categoryModifiers.ContainsKey(category))
558  {
559  categoryModifiers.Add(category, new CategoryModifier(index, gain, false));
560  }
561  else
562  {
563  categoryModifiers[category].SetGainMultiplier(index, gain);
564  }
565  }
566 
567  for (int i = 0; i < playingChannels.Length; i++)
568  {
569  lock (playingChannels[i])
570  {
571  for (int j = 0; j < playingChannels[i].Length; j++)
572  {
573  if (playingChannels[i][j] != null && playingChannels[i][j].IsPlaying)
574  {
575  playingChannels[i][j].Gain = playingChannels[i][j].Gain; //force all channels to recalculate their gain
576  }
577  }
578  }
579  }
580  }
581 
582  public float GetCategoryGainMultiplier(string category, int index = -1)
583  {
584  if (Disabled) { return 0.0f; }
585  category = category.ToLower();
586  lock (categoryModifiers)
587  {
588  if (categoryModifiers == null || !categoryModifiers.TryGetValue(category, out CategoryModifier categoryModifier)) { return 1.0f; }
589  if (index < 0)
590  {
591  float accumulatedMultipliers = 1.0f;
592  for (int i = 0; i < categoryModifier.GainMultipliers.Length; i++)
593  {
594  accumulatedMultipliers *= categoryModifier.GainMultipliers[i];
595  }
596  return accumulatedMultipliers;
597  }
598  else
599  {
600  return categoryModifier.GainMultipliers[index];
601  }
602  }
603  }
604 
605  public void SetCategoryMuffle(string category, bool muffle)
606  {
607  if (Disabled) { return; }
608 
609  category = category.ToLower();
610  lock (categoryModifiers)
611  {
612  if (!categoryModifiers.ContainsKey(category))
613  {
614  categoryModifiers.Add(category, new CategoryModifier(0, 1.0f, muffle));
615  }
616  else
617  {
618  categoryModifiers[category].Muffle = muffle;
619  }
620  }
621 
622  for (int i = 0; i < playingChannels.Length; i++)
623  {
624  lock (playingChannels[i])
625  {
626  for (int j = 0; j < playingChannels[i].Length; j++)
627  {
628  if (playingChannels[i][j] != null && playingChannels[i][j].IsPlaying)
629  {
630  if (playingChannels[i][j]?.Category.ToLower() == category) { playingChannels[i][j].Muffled = muffle; }
631  }
632  }
633  }
634  }
635  }
636 
637  public bool GetCategoryMuffle(string category)
638  {
639  if (Disabled) { return false; }
640 
641  category = category.ToLower();
642  lock (categoryModifiers)
643  {
644  if (categoryModifiers == null || !categoryModifiers.TryGetValue(category, out CategoryModifier categoryModifier)) { return false; }
645  return categoryModifier.Muffle;
646  }
647  }
648 
649  public void Update()
650  {
651  if (Disconnected || Disabled) { return; }
652 
654  {
655  Alc.GetInteger(alcDevice, Alc.EnumConnected, out int isConnected);
656  int alcError = Alc.GetError(alcDevice);
657  if (alcError != Alc.NoError)
658  {
659  throw new Exception("Failed to determine if device is connected: " + alcError.ToString());
660  }
661 
662  if (isConnected == 0)
663  {
665  {
666  //wait for loading to finish so we don't start releasing and reloading sounds when they're being loaded,
667  //or throw an error mid-loading that'd prevent the content package from being enabled
668  return;
669  }
670  DebugConsole.ThrowError("Playback device has been disconnected. You can select another available device in the settings.");
671  SetAudioOutputDevice("<disconnected>");
672  Disconnected = true;
673  return;
674  }
675  }
676 
677  if (GameMain.Client != null && GameSettings.CurrentConfig.Audio.VoipAttenuationEnabled)
678  {
679  if (Timing.TotalTime > lastAttenuationTime+0.2)
680  {
681  voipAttenuatedGain = voipAttenuatedGain * 0.9f + 0.1f;
682  }
683  }
684  else
685  {
686  voipAttenuatedGain = 1.0f;
687  }
692 
693  if (GameSettings.CurrentConfig.Audio.DynamicRangeCompressionEnabled)
694  {
695  float targetGain = (Math.Min(1.0f, 1.0f / PlaybackAmplitude) - 1.0f) * 0.5f + 1.0f;
696  if (targetGain < CompressionDynamicRangeGain)
697  {
698  //if the target gain is lower than the current gain, lower the current gain immediately to prevent clipping
699  CompressionDynamicRangeGain = targetGain;
700  }
701  else
702  {
703  //otherwise, let it rise back smoothly
704  CompressionDynamicRangeGain = (targetGain) * 0.05f + CompressionDynamicRangeGain * 0.95f;
705  }
706  }
707  else
708  {
710  }
711 
712  if (updateChannelsThread == null || updateChannelsThread.ThreadState.HasFlag(ThreadState.Stopped))
713  {
714  bool startedStreamThread = false;
715  for (int i = 0; i < playingChannels.Length; i++)
716  {
717  lock (playingChannels[i])
718  {
719  for (int j = 0; j < playingChannels[i].Length; j++)
720  {
721  if (playingChannels[i][j] == null) { continue; }
722  if (playingChannels[i][j].IsStream && playingChannels[i][j].IsPlaying)
723  {
725  startedStreamThread = true;
726  }
727  if (startedStreamThread) { break; }
728  }
729  }
730  if (startedStreamThread) { break; }
731  }
732  }
733  }
734 
735  public void ApplySettings()
736  {
737  SetCategoryGainMultiplier(SoundCategoryDefault, GameSettings.CurrentConfig.Audio.SoundVolume, 0);
738  SetCategoryGainMultiplier(SoundCategoryUi, GameSettings.CurrentConfig.Audio.UiVolume, 0);
739  SetCategoryGainMultiplier(SoundCategoryWaterAmbience, GameSettings.CurrentConfig.Audio.SoundVolume, 0);
740  SetCategoryGainMultiplier(SoundCategoryMusic, GameSettings.CurrentConfig.Audio.MusicVolume, 0);
741  SetCategoryGainMultiplier(SoundCategoryVoip, Math.Min(GameSettings.CurrentConfig.Audio.VoiceChatVolume, 1.0f), 0);
742  }
743 
748  {
749  if (Disabled) { return; }
750  bool isUpdateChannelsThreadDying;
751  lock (threadDeathMutex)
752  {
753  isUpdateChannelsThreadDying = !needsUpdateChannels;
754  }
755  if (updateChannelsThread == null || updateChannelsThread.ThreadState.HasFlag(ThreadState.Stopped) || isUpdateChannelsThreadDying)
756  {
757  if (updateChannelsThread != null && !updateChannelsThread.Join(1000))
758  {
759  DebugConsole.ThrowError("SoundManager.UpdateChannels thread join timed out!");
760  }
761  needsUpdateChannels = true;
762  updateChannelsThread = new Thread(UpdateChannels)
763  {
764  Name = "SoundManager.UpdateChannels Thread",
765  IsBackground = true //this should kill the thread if the game crashes
766  };
767  updateChannelsThread.Start();
768  }
769  }
770 
771  private bool needsUpdateChannels = false;
772  private ManualResetEvent updateChannelsMre = null;
773 
777  private void UpdateChannels()
778  {
779  updateChannelsMre = new ManualResetEvent(false);
780  bool killThread = false;
781  while (!killThread)
782  {
783  killThread = true;
784  for (int i = 0; i < playingChannels.Length; i++)
785  {
786  lock (playingChannels[i])
787  {
788  for (int j = 0; j < playingChannels[i].Length; j++)
789  {
790  if (playingChannels[i][j] == null) { continue; }
791  if (playingChannels[i][j].IsStream)
792  {
793  if (playingChannels[i][j].IsPlaying)
794  {
795  killThread = false;
796  playingChannels[i][j].UpdateStream();
797  }
798  else
799  {
800  playingChannels[i][j].Dispose();
801  playingChannels[i][j] = null;
802  }
803  }
804  else if (playingChannels[i][j].FadingOutAndDisposing)
805  {
806  killThread = false;
807  playingChannels[i][j].Gain -= 0.1f;
808  if (playingChannels[i][j].Gain <= 0.0f)
809  {
810  playingChannels[i][j].Dispose();
811  playingChannels[i][j] = null;
812  }
813  }
814  }
815  }
816  }
817  updateChannelsMre.WaitOne(10);
818  updateChannelsMre.Reset();
819  lock (threadDeathMutex)
820  {
821  needsUpdateChannels = !killThread;
822  }
823  }
824  }
825 
826  public void ForceStreamUpdate()
827  {
828  updateChannelsMre?.Set();
829  }
830 
831  private void ReloadSounds()
832  {
833  for (int i = loadedSounds.Count - 1; i >= 0; i--)
834  {
835  loadedSounds[i].InitializeAlBuffers();
836  }
837  }
838 
839  private void ReleaseResources(bool keepSounds)
840  {
841  for (int i = 0; i < playingChannels.Length; i++)
842  {
843  lock (playingChannels[i])
844  {
845  for (int j = 0; j < playingChannels[i].Length; j++)
846  {
847  playingChannels[i][j]?.Dispose();
848  }
849  }
850  }
851 
852  updateChannelsThread?.Join();
853  for (int i = loadedSounds.Count - 1; i >= 0; i--)
854  {
855  if (keepSounds)
856  {
857  loadedSounds[i].DeleteAlBuffers();
858  }
859  else
860  {
861  loadedSounds[i].Dispose();
862  }
863  }
864  sourcePools[(int)SourcePoolIndex.Default]?.Dispose();
865  sourcePools[(int)SourcePoolIndex.Voice]?.Dispose();
866 
867  SoundBuffers.ClearPool();
868  }
869 
870  public void Dispose()
871  {
872  if (Disabled) { return; }
873 
874  ReleaseResources(false);
875 
876  if (!Alc.MakeContextCurrent(IntPtr.Zero) && !GameMain.IsExiting)
877  {
878  throw new Exception("Failed to detach the current ALC context! (error code: " + Alc.GetError(alcDevice).ToString() + ")");
879  }
880 
881  Alc.DestroyContext(alcContext);
882 
883  if (!Alc.CloseDevice(alcDevice) && !GameMain.IsExiting)
884  {
885  throw new Exception("Failed to close ALC device!");
886  }
887  }
888  }
889 }
string???????????? Value
Definition: ContentPath.cs:27
float GetAttributeFloat(string key, float def)
ContentPath? GetAttributeContentPath(string key)
static bool IsExiting
Definition: GameMain.cs:135
static GameClient Client
Definition: GameMain.cs:188
static GameMain Instance
Definition: GameMain.cs:144
virtual SoundManager.SourcePoolIndex SourcePoolIndex
Definition: Sound.cs:31
readonly string Filename
Definition: Sound.cs:19
IReadOnlyList< Sound > LoadedSounds
Definition: SoundManager.cs:38
int CountPlayingInstances(Sound sound)
float GetCategoryGainMultiplier(string category, int index=-1)
bool GetCategoryMuffle(string category)
uint GetSourceFromIndex(SourcePoolIndex poolIndex, int srcInd)
void RemoveSound(Sound sound)
void MoveSoundToPosition(Sound sound, int pos)
void InitUpdateChannelThread()
Initializes the thread that handles streaming audio and fading out and disposing channels that are no...
SoundChannel GetChannelFromSound(Sound sound)
const string SoundCategoryWaterAmbience
Definition: SoundManager.cs:17
Sound LoadSound(ContentXElement element, bool stream=false, string overrideFilePath=null)
void SetCategoryGainMultiplier(string category, float gain, int index=0)
void SetCategoryMuffle(string category, bool muffle)
SoundChannel GetSoundChannelFromIndex(SourcePoolIndex poolIndex, int ind)
void KillChannels(Sound sound)
int AssignFreeSourceToChannel(SoundChannel newChannel)
bool InitializeAlcDevice(string deviceName)
Sound LoadSound(string filename, bool stream=false)
Definition: Al.cs:38
const int LinearDistanceClamped
Definition: Al.cs:115
static void Listener3f(int param, float value1, float value2, float value3)
static void Listenerfv(int param, float[] values)
static bool IsSource(uint sid)
static void Sourcef(uint sid, int param, float value)
const int Orientation
Definition: Al.cs:68
static void Listenerf(int param, float value)
const int MaxGain
Definition: Al.cs:67
static string GetErrorString(int error)
Definition: Al.cs:164
static int GetError()
static void DistanceModel(int distanceModel)
const int Gain
Definition: Al.cs:65
const int MinGain
Definition: Al.cs:66
const int Position
Definition: Al.cs:60
const int NoError
Definition: Al.cs:98
static bool MakeContextCurrent(IntPtr context)
const int EnumConnected
Definition: Alc.cs:111
static IReadOnlyList< string > GetStringList(IntPtr device, int param)
Definition: Alc.cs:225
static int GetError(IntPtr device)
static void DestroyContext(IntPtr context)
const int AllDevicesSpecifier
Definition: Alc.cs:107
static bool CloseDevice(IntPtr device)
static string GetErrorString(int errorCode)
Definition: Alc.cs:178
static void GetInteger(IntPtr device, int param, out int data)
Definition: Alc.cs:259
static IntPtr CreateContext(IntPtr device, int[] attrList)
Definition: Alc.cs:128
const int DefaultDeviceSpecifier
Definition: Alc.cs:99
const int NoError
Definition: Alc.cs:93
static bool IsExtensionPresent(IntPtr device, string extname)
static string GetString(IntPtr device, int param)
Definition: Alc.cs:215
Definition: Al.cs:36