using System; using System.Collections; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; namespace MLEM.Sound { /// /// A simple class that handles automatically removing and disposing objects once they are done playing to free up the audio source for new sounds. /// Additionally, a callback can be registered that is invoked when the finishes playing. /// Note that an object of this class can be added to a using its . /// public class SoundEffectInstanceHandler : GameComponent, IEnumerable { private readonly List playingSounds = new List(); private AudioListener[] listeners; /// /// Creates a new sound effect instance handler with the given settings /// /// The game instance public SoundEffectInstanceHandler(Game game) : base(game) {} /// public override void Update(GameTime gameTime) { this.Update(); } /// /// Updates this sound effect handler and manages all of the objects in it. /// If has been called, all sounds will additionally be updated in 3D space. /// This should be called each update frame. /// public void Update() { for (var i = this.playingSounds.Count - 1; i >= 0; i--) { var entry = this.playingSounds[i]; if (entry.Instance.IsDisposed || entry.Instance.State == SoundState.Stopped) { entry.StopAndNotify(); this.playingSounds.RemoveAt(i); } else { entry.TryApply3D(this.listeners); } } } /// /// Sets the collection objects that should be listening to the sounds in this handler in 3D space. /// If there are one or more listeners, this handler applies 3d effects to all sound effect instances that have been added to this handler along with an in automatically. /// public void SetListeners(params AudioListener[] listeners) { this.listeners = listeners; } /// /// Pauses all of the sound effect instances that are currently playing /// public void Pause() { foreach (var entry in this.playingSounds) entry.Instance.Pause(); } /// /// Resumes all of the sound effect instances in this handler /// public void Resume() { foreach (var entry in this.playingSounds) entry.Instance.Resume(); } /// /// Stops all of the sound effect instances in this handler /// public void Stop() { this.playingSounds.RemoveAll(e => { e.StopAndNotify(); return true; }); } /// /// Adds a new to this handler. /// This also starts playing the instance. /// /// The instance to add /// The function that should be invoked when this instance stops playing, defaults to null /// An optional audio emitter with which 3d sound can be applied /// The passed instance, for chaining public SoundEffectInstance Add(SoundEffectInstance instance, Action onStopped = null, AudioEmitter emitter = null) { var entry = new Entry(instance, onStopped, emitter); this.playingSounds.Add(entry); instance.Play(); entry.TryApply3D(this.listeners); return instance; } /// /// Adds a new to this handler. /// This also starts playing the created instance. /// /// The info for which to add a /// The function that should be invoked when this instance stops playing, defaults to null /// An optional audio emitter with which 3d sound can be applied /// The newly created public SoundEffectInstance Add(SoundEffectInfo info, Action onStopped = null, AudioEmitter emitter = null) { return this.Add(info.CreateInstance(), onStopped, emitter); } /// /// Adds a new to this handler. /// This also starts playing the created instance. /// /// The sound for which to add a /// The function that should be invoked when this instance stops playing, defaults to null /// An optional audio emitter with which 3d sound can be applied /// The newly created public SoundEffectInstance Add(SoundEffect effect, Action onStopped = null, AudioEmitter emitter = null) { return this.Add(effect.CreateInstance(), onStopped, emitter); } /// Returns an enumerator that iterates through the collection. /// An enumerator that can be used to iterate through the collection. public IEnumerator GetEnumerator() { return this.playingSounds.GetEnumerator(); } /// Returns an enumerator that iterates through a collection. /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } /// /// An entry in a . /// Each entry stores the that is being played, as well as the additional data passed through . /// public readonly struct Entry { /// /// The sound effect instance that this entry is playing /// public readonly SoundEffectInstance Instance; /// /// An action that is invoked when this entry's is stopped or after it finishes naturally. /// This action is invoked in . /// public readonly Action OnStopped; /// /// The that this sound effect instance is linked to. /// If the underlying handler's method has been called, 3D sound will be applied. /// public readonly AudioEmitter Emitter; internal Entry(SoundEffectInstance instance, Action onStopped, AudioEmitter emitter) { this.Instance = instance; this.OnStopped = onStopped; this.Emitter = emitter; } internal void TryApply3D(AudioListener[] listeners) { if (listeners != null && listeners.Length > 0 && this.Emitter != null) this.Instance.Apply3D(listeners, this.Emitter); } internal void StopAndNotify() { this.Instance.Stop(true); this.OnStopped?.Invoke(this.Instance); } } } }