using System; using System.Collections.Generic; using System.Runtime.InteropServices; namespace MoonWorks.Audio { /// /// For streaming long playback. /// Can be extended to support custom decoders. /// public abstract class StreamingSound : SoundInstance { private readonly List queuedBuffers = new List(); private const int MINIMUM_BUFFER_CHECK = 3; private int PendingBufferCount => queuedBuffers.Count; public abstract int BUFFER_SIZE { get; } public StreamingSound( AudioDevice device, ushort formatTag, ushort bitsPerSample, ushort blockAlign, ushort channels, uint samplesPerSecond ) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond) { device.AddDynamicSoundInstance(this); } public override void Play() { if (State == SoundState.Playing) { return; } State = SoundState.Playing; Update(); FAudio.FAudioSourceVoice_Start(Handle, 0, 0); } public override void Pause() { if (State == SoundState.Playing) { FAudio.FAudioSourceVoice_Stop(Handle, 0, 0); State = SoundState.Paused; } } public override void Stop(bool immediate = true) { if (immediate) { FAudio.FAudioSourceVoice_Stop(Handle, 0, 0); FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle); ClearBuffers(); } State = SoundState.Stopped; } internal unsafe void Update() { if (State != SoundState.Playing) { return; } FAudio.FAudioSourceVoice_GetState( Handle, out var state, FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED ); while (PendingBufferCount > state.BuffersQueued) { lock (queuedBuffers) { NativeMemory.Free((void*) queuedBuffers[0]); queuedBuffers.RemoveAt(0); } } QueueBuffers(); } protected void QueueBuffers() { for ( int i = MINIMUM_BUFFER_CHECK - PendingBufferCount; i > 0; i -= 1 ) { AddBuffer(); } } protected unsafe void ClearBuffers() { lock (queuedBuffers) { foreach (IntPtr buf in queuedBuffers) { NativeMemory.Free((void*) buf); } queuedBuffers.Clear(); } } protected unsafe void AddBuffer() { void* buffer = NativeMemory.Alloc((nuint) BUFFER_SIZE); AddBuffer( buffer, BUFFER_SIZE, out int filledLengthInBytes, out bool reachedEnd ); lock (queuedBuffers) { queuedBuffers.Add((IntPtr) buffer); if (State != SoundState.Stopped) { FAudio.FAudioBuffer buf = new FAudio.FAudioBuffer { AudioBytes = (uint) filledLengthInBytes, pAudioData = (IntPtr) buffer, PlayLength = ( (uint) (filledLengthInBytes / Format.nChannels / (uint) (Format.wBitsPerSample / 8)) ) }; FAudio.FAudioSourceVoice_SubmitSourceBuffer( Handle, ref buf, IntPtr.Zero ); } } /* We have reached the end of the file, what do we do? */ if (reachedEnd) { OnReachedEnd(); } } protected virtual void OnReachedEnd() { Stop(false); } protected unsafe abstract void AddBuffer( void* buffer, int bufferLength, /* in bytes */ out int filledLength, /* in bytes */ out bool reachedEnd ); protected override void Destroy() { Stop(true); } } }