using SoundIOSharp; using System; using System.Collections.Concurrent; using System.Linq; namespace Ryujinx.Audio.SoundIo { /// <summary> /// An object pool containing a set of audio tracks /// </summary> internal class SoundIoAudioTrackPool : IDisposable { /// <summary> /// The current size of the <see cref="SoundIoAudioTrackPool"/> /// </summary> private int m_Size; /// <summary> /// The maximum size of the <see cref="SoundIoAudioTrackPool"/> /// </summary> private int m_MaxSize; /// <summary> /// The <see cref="SoundIO"/> audio context this track pool belongs to /// </summary> private SoundIO m_Context; /// <summary> /// The <see cref="SoundIODevice"/> audio device this track pool belongs to /// </summary> private SoundIODevice m_Device; /// <summary> /// The queue that keeps track of the available <see cref="SoundIoAudioTrack"/> in the pool. /// </summary> private ConcurrentQueue<SoundIoAudioTrack> m_Queue; /// <summary> /// The dictionary providing mapping between a TrackID and <see cref="SoundIoAudioTrack"/> /// </summary> private ConcurrentDictionary<int, SoundIoAudioTrack> m_TrackList; /// <summary> /// Gets the current size of the <see cref="SoundIoAudioTrackPool"/> /// </summary> public int Size { get => m_Size; } /// <summary> /// Gets the maximum size of the <see cref="SoundIoAudioTrackPool"/> /// </summary> public int MaxSize { get => m_MaxSize; } /// <summary> /// Gets a value that indicates whether the <see cref="SoundIoAudioTrackPool"/> is empty /// </summary> public bool IsEmpty { get => m_Queue.IsEmpty; } /// <summary> /// Constructs a new instance of a <see cref="SoundIoAudioTrackPool"/> that is empty /// </summary> /// <param name="maxSize">The maximum amount of tracks that can be created</param> public SoundIoAudioTrackPool(SoundIO context, SoundIODevice device, int maxSize) { m_Size = 0; m_Context = context; m_Device = device; m_MaxSize = maxSize; m_Queue = new ConcurrentQueue<SoundIoAudioTrack>(); m_TrackList = new ConcurrentDictionary<int, SoundIoAudioTrack>(); } /// <summary> /// Constructs a new instance of a <see cref="SoundIoAudioTrackPool"/> that contains /// the specified amount of <see cref="SoundIoAudioTrack"/> /// </summary> /// <param name="maxSize">The maximum amount of tracks that can be created</param> /// <param name="initialCapacity">The initial number of tracks that the pool contains</param> public SoundIoAudioTrackPool(SoundIO context, SoundIODevice device, int maxSize, int initialCapacity) : this(context, device, maxSize) { var trackCollection = Enumerable.Range(0, initialCapacity) .Select(TrackFactory); m_Size = initialCapacity; m_Queue = new ConcurrentQueue<SoundIoAudioTrack>(trackCollection); } /// <summary> /// Creates a new <see cref="SoundIoAudioTrack"/> with the proper AudioContext and AudioDevice /// and the specified <paramref name="trackId" /> /// </summary> /// <param name="trackId">The ID of the track to be created</param> /// <returns>A new AudioTrack with the specified ID</returns> private SoundIoAudioTrack TrackFactory(int trackId) { // Create a new AudioTrack SoundIoAudioTrack track = new SoundIoAudioTrack(trackId, m_Context, m_Device); // Keep track of issued tracks m_TrackList[trackId] = track; return track; } /// <summary> /// Retrieves a <see cref="SoundIoAudioTrack"/> from the pool /// </summary> /// <returns>An AudioTrack from the pool</returns> public SoundIoAudioTrack Get() { // If we have a track available, reuse it if (m_Queue.TryDequeue(out SoundIoAudioTrack track)) { return track; } // Have we reached the maximum size of our pool? if (m_Size >= m_MaxSize) { return null; } // We don't have any pooled tracks, so create a new one return TrackFactory(m_Size++); } /// <summary> /// Retrieves the <see cref="SoundIoAudioTrack"/> associated with the specified <paramref name="trackId"/> from the pool /// </summary> /// <param name="trackId">The ID of the track to retrieve</param> public SoundIoAudioTrack Get(int trackId) { if (m_TrackList.TryGetValue(trackId, out SoundIoAudioTrack track)) { return track; } return null; } /// <summary> /// Attempts to get a <see cref="SoundIoAudioTrack"/> from the pool /// </summary> /// <param name="track">The track retrieved from the pool</param> /// <returns>True if retrieve was successful</returns> public bool TryGet(out SoundIoAudioTrack track) { track = Get(); return track != null; } /// <summary> /// Attempts to get the <see cref="SoundIoAudioTrack" /> associated with the specified <paramref name="trackId"/> from the pool /// </summary> /// <param name="trackId">The ID of the track to retrieve</param> /// <param name="track">The track retrieved from the pool</param> public bool TryGet(int trackId, out SoundIoAudioTrack track) { return m_TrackList.TryGetValue(trackId, out track); } /// <summary> /// Returns an <see cref="SoundIoAudioTrack"/> back to the pool for reuse /// </summary> /// <param name="track">The track to be returned to the pool</param> public void Put(SoundIoAudioTrack track) { // Ensure the track is disposed and not playing audio track.Close(); // Requeue the track for reuse later m_Queue.Enqueue(track); } /// <summary> /// Releases the unmanaged resources used by the <see cref="SoundIoAudioTrackPool" /> /// </summary> public void Dispose() { foreach (var track in m_TrackList) { track.Value.Close(); track.Value.Dispose(); } m_Size = 0; m_Queue.Clear(); m_TrackList.Clear(); } } }