diff --git a/src/Audio/AudioDevice.cs b/src/Audio/AudioDevice.cs index 7b9404e..3a6a9f9 100644 --- a/src/Audio/AudioDevice.cs +++ b/src/Audio/AudioDevice.cs @@ -30,6 +30,7 @@ namespace MoonWorks.Audio private readonly HashSet resources = new HashSet(); private readonly List autoUpdateStreamingSoundReferences = new List(); private readonly List autoFreeStaticSoundInstanceReferences = new List(); + private readonly List> soundQueueReferences = new List>(); private AudioTweenManager AudioTweenManager; @@ -184,6 +185,18 @@ namespace MoonWorks.Audio } } + for (var i = soundQueueReferences.Count - 1; i >= 0; i -= 1) + { + if (soundQueueReferences[i].TryGetTarget(out var soundQueue)) + { + soundQueue.Update(); + } + else + { + soundQueueReferences.RemoveAt(i); + } + } + AudioTweenManager.Update(elapsedSeconds); } @@ -256,6 +269,11 @@ namespace MoonWorks.Audio autoFreeStaticSoundInstanceReferences.Add(instance); } + internal void AddSoundQueueReference(SoundQueue queue) + { + soundQueueReferences.Add(new WeakReference(queue)); + } + protected virtual void Dispose(bool disposing) { if (!IsDisposed) diff --git a/src/Audio/SoundQueue.cs b/src/Audio/SoundQueue.cs new file mode 100644 index 0000000..d05a213 --- /dev/null +++ b/src/Audio/SoundQueue.cs @@ -0,0 +1,132 @@ +using System; + +namespace MoonWorks.Audio +{ + // NOTE: all sounds played with a playlist must have the same audio format! + public class SoundQueue : SoundInstance + { + public int NeedBufferThreshold = 0; + private uint queuedBufferCount = 0; + + public delegate void OnBufferNeededFunc(); + public OnBufferNeededFunc OnBufferNeeded; + + private object StateLock = new object(); + + public SoundQueue(AudioDevice device, ushort formatTag, ushort bitsPerSample, ushort blockAlign, ushort channels, uint samplesPerSecond) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond) + { + device.AddSoundQueueReference(this); + } + + public SoundQueue(AudioDevice device, StaticSound templateSound) : base(device, templateSound.FormatTag, templateSound.BitsPerSample, templateSound.BlockAlign, templateSound.Channels, templateSound.SamplesPerSecond) + { + device.AddSoundQueueReference(this); + } + + public void Update() + { + lock (StateLock) + { + if (IsDisposed) { return; } + if (State != SoundState.Playing) { return; } + + if (NeedBufferThreshold > 0) + { + FAudio.FAudioSourceVoice_GetState( + Voice, + out var state, + FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED + ); + + var queuedBufferCount = state.BuffersQueued; + for (int i = 0; i < NeedBufferThreshold - queuedBufferCount; i += 1) + { + if (OnBufferNeeded != null) + { + OnBufferNeeded(); + } + } + } + } + } + + public void EnqueueSound(StaticSound sound) + { +#if DEBUG + if ( + sound.FormatTag != Format.wFormatTag || + sound.BitsPerSample != Format.wBitsPerSample || + sound.Channels != Format.nChannels || + sound.SamplesPerSecond != Format.nSamplesPerSec + ) + { + Logger.LogWarn("Playlist audio format mismatch!"); + } +#endif + + lock (StateLock) + { + FAudio.FAudioSourceVoice_SubmitSourceBuffer( + Voice, + ref sound.Handle, + IntPtr.Zero + ); + } + } + + public override void Pause() + { + lock (StateLock) + { + if (State == SoundState.Playing) + { + FAudio.FAudioSourceVoice_Stop(Voice, 0, 0); + State = SoundState.Paused; + } + } + } + + public override void Play() + { + PlayUsingOperationSet(0); + } + + public override void QueueSyncPlay() + { + PlayUsingOperationSet(1); + } + + private void PlayUsingOperationSet(uint operationSet) + { + lock (StateLock) + { + if (State == SoundState.Playing) + { + return; + } + + FAudio.FAudioSourceVoice_Start(Voice, 0, operationSet); + State = SoundState.Playing; + } + } + + public override void Stop() + { + lock (StateLock) + { + FAudio.FAudioSourceVoice_ExitLoop(Voice, 0); + State = SoundState.Stopped; + } + } + + public override void StopImmediate() + { + lock (StateLock) + { + FAudio.FAudioSourceVoice_Stop(Voice, 0, 0); + FAudio.FAudioSourceVoice_FlushSourceBuffers(Voice); + State = SoundState.Stopped; + } + } + } +}