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