From f70caa1a42817941ce7c19fd2d53c615569bca3e Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Fri, 3 Mar 2023 15:20:26 -0800 Subject: [PATCH] audio interpolation support + audio threading --- src/Audio/AudioDevice.cs | 152 ++++++++++++++++++++++++++++++++----- src/Audio/AudioTween.cs | 48 ++++++++++++ src/Audio/SoundInstance.cs | 77 +++++++++++++++---- 3 files changed, 244 insertions(+), 33 deletions(-) create mode 100644 src/Audio/AudioTween.cs diff --git a/src/Audio/AudioDevice.cs b/src/Audio/AudioDevice.cs index 84978e8..b0bd155 100644 --- a/src/Audio/AudioDevice.cs +++ b/src/Audio/AudioDevice.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading; +using EasingFunction = System.Func; namespace MoonWorks.Audio { @@ -29,10 +30,16 @@ namespace MoonWorks.Audio private readonly List> resources = new List>(); private readonly List> streamingSounds = new List>(); + private AudioTweenPool AudioTweenPool = new AudioTweenPool(); + private readonly List AudioTweens = new List(); + private const int Step = 200; private TimeSpan UpdateInterval; + private System.Diagnostics.Stopwatch TickStopwatch = new System.Diagnostics.Stopwatch(); + private long previousTickTime; private Thread Thread; private AutoResetEvent WakeSignal; + private readonly object StateLock = new object(); private bool IsDisposed; @@ -119,13 +126,19 @@ namespace MoonWorks.Audio Thread = new Thread(ThreadMain); Thread.IsBackground = true; Thread.Start(); + + TickStopwatch.Start(); + previousTickTime = 0; } private void ThreadMain() { while (!IsDisposed) { - ThreadMainTick(); + lock (StateLock) + { + ThreadMainTick(); + } WakeSignal.WaitOne(UpdateInterval); } @@ -133,6 +146,10 @@ namespace MoonWorks.Audio private void ThreadMainTick() { + long tickDelta = TickStopwatch.Elapsed.Ticks - previousTickTime; + previousTickTime = TickStopwatch.Elapsed.Ticks; + float elapsedSeconds = (float) tickDelta / System.TimeSpan.TicksPerSecond; + for (var i = streamingSounds.Count - 1; i >= 0; i--) { var weakReference = streamingSounds[i]; @@ -145,11 +162,26 @@ namespace MoonWorks.Audio streamingSounds.RemoveAt(i); } } - } - internal void WakeThread() - { - WakeSignal.Set(); + lock (AudioTweens) + { + for (var i = AudioTweens.Count - 1; i >= 0; i--) + { + bool finished = true; + var audioTween = AudioTweens[i]; + + if (audioTween.SoundInstanceReference.TryGetTarget(out var soundInstance)) + { + finished = UpdateAudioTween(audioTween, soundInstance, elapsedSeconds); + } + + if (finished) + { + AudioTweenPool.Free(audioTween); + AudioTweens.RemoveAt(i); + } + } + } } public void SyncPlay() @@ -157,14 +189,91 @@ namespace MoonWorks.Audio FAudio.FAudio_CommitChanges(Handle, 1); } + internal void CreateTween( + SoundInstance soundInstance, + AudioTweenProperty property, + EasingFunction easingFunction, + float start, + float end, + float duration + ) { + var tween = AudioTweenPool.Obtain(); + tween.SoundInstanceReference = new WeakReference(soundInstance); + tween.Property = property; + tween.EasingFunction = easingFunction; + tween.Start = start; + tween.End = end; + tween.Duration = duration; + tween.Time = 0; + + lock (AudioTweens) + { + AudioTweens.Add(tween); + } + } + + private bool UpdateAudioTween(AudioTween audioTween, SoundInstance soundInstance, float delta) + { + float value; + audioTween.Time += delta; + + if (audioTween.Time > audioTween.Duration) + { + value = audioTween.End; + } + else + { + value = MoonWorks.Math.Easing.Interp( + audioTween.Start, + audioTween.End, + audioTween.Time, + audioTween.Duration, + audioTween.EasingFunction + ); + } + + switch (audioTween.Property) + { + case AudioTweenProperty.Pan: + soundInstance.Pan = value; + break; + + case AudioTweenProperty.Pitch: + soundInstance.Pitch = value; + break; + + case AudioTweenProperty.Volume: + soundInstance.Volume = value; + break; + + case AudioTweenProperty.FilterFrequency: + soundInstance.FilterFrequency = value; + break; + + case AudioTweenProperty.Reverb: + soundInstance.Reverb = value; + break; + } + + return audioTween.Time > audioTween.Duration; + } + + internal void WakeThread() + { + WakeSignal.Set(); + } + internal void AddDynamicSoundInstance(StreamingSound instance) { - streamingSounds.Add(new WeakReference(instance)); + lock (StateLock) + { + streamingSounds.Add(new WeakReference(instance)); + } } internal void AddResourceReference(WeakReference resourceReference) { - lock (resources) + lock (StateLock) { resources.Add(resourceReference); } @@ -172,7 +281,7 @@ namespace MoonWorks.Audio internal void RemoveResourceReference(WeakReference resourceReference) { - lock (resources) + lock (StateLock) { resources.Remove(resourceReference); } @@ -182,24 +291,27 @@ namespace MoonWorks.Audio { if (!IsDisposed) { - if (disposing) + lock (StateLock) { - for (var i = resources.Count - 1; i >= 0; i--) + if (disposing) { - var weakReference = resources[i]; - - if (weakReference.TryGetTarget(out var resource)) + for (var i = resources.Count - 1; i >= 0; i--) { - resource.Dispose(); + var weakReference = resources[i]; + + if (weakReference.TryGetTarget(out var resource)) + { + resource.Dispose(); + } } + resources.Clear(); } - resources.Clear(); + + FAudio.FAudioVoice_DestroyVoice(MasteringVoice); + FAudio.FAudio_Release(Handle); + + IsDisposed = true; } - - FAudio.FAudioVoice_DestroyVoice(MasteringVoice); - FAudio.FAudio_Release(Handle); - - IsDisposed = true; } } diff --git a/src/Audio/AudioTween.cs b/src/Audio/AudioTween.cs new file mode 100644 index 0000000..721cb92 --- /dev/null +++ b/src/Audio/AudioTween.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using EasingFunction = System.Func; + +namespace MoonWorks.Audio +{ + internal enum AudioTweenProperty + { + Pan, + Pitch, + Volume, + FilterFrequency, + Reverb + } + + internal class AudioTween + { + public System.WeakReference SoundInstanceReference; + public AudioTweenProperty Property; + public EasingFunction EasingFunction; + public float Time; + public float Start; + public float End; + public float Duration; + } + + internal class AudioTweenPool + { + private Queue Tweens = new Queue(); + + public AudioTween Obtain() + { + if (Tweens.Count > 0) + { + var tween = Tweens.Dequeue(); + return tween; + } + else + { + return new AudioTween(); + } + } + + public void Free(AudioTween tween) + { + Tweens.Enqueue(tween); + } + } +} diff --git a/src/Audio/SoundInstance.cs b/src/Audio/SoundInstance.cs index b82fbf2..e43e3fb 100644 --- a/src/Audio/SoundInstance.cs +++ b/src/Audio/SoundInstance.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using EasingFunction = System.Func; namespace MoonWorks.Audio { @@ -21,7 +22,7 @@ namespace MoonWorks.Audio public float Pan { get => pan; - set + internal set { pan = value; @@ -52,7 +53,7 @@ namespace MoonWorks.Audio public float Pitch { get => pitch; - set + internal set { pitch = Math.MathHelper.Clamp(value, -1f, 1f); UpdatePitch(); @@ -63,7 +64,7 @@ namespace MoonWorks.Audio public float Volume { get => volume; - set + internal set { volume = value; FAudio.FAudioVoice_SetVolume(Voice, volume, 0); @@ -80,10 +81,10 @@ namespace MoonWorks.Audio OneOverQ = 1f }; - private float FilterFrequency + public float FilterFrequency { get => filterParameters.Frequency; - set + internal set { value = System.Math.Clamp(value, 0.01f, MAX_FILTER_FREQUENCY); filterParameters.Frequency = value; @@ -156,7 +157,7 @@ namespace MoonWorks.Audio public unsafe float Reverb { get => reverb; - set + internal set { if (ReverbEffect != null) { @@ -188,7 +189,7 @@ namespace MoonWorks.Audio } } - public SoundInstance( + public unsafe SoundInstance( AudioDevice device, ushort formatTag, ushort bitsPerSample, @@ -277,6 +278,58 @@ namespace MoonWorks.Audio ReverbEffect = reverbEffect; } + // TODO: since we're using setters now, could just get rid of the property setters + + public void SetPan(float targetValue) + { + Pan = targetValue; + } + + public void SetPan(float targetValue, float duration, EasingFunction easingFunction) + { + Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration); + } + + public void SetPitch(float targetValue) + { + Pitch = targetValue; + } + + public void SetPitch(float targetValue, float duration, EasingFunction easingFunction) + { + Device.CreateTween(this, AudioTweenProperty.Pitch, easingFunction, Pan, targetValue, duration); + } + + public void SetVolume(float targetValue) + { + Volume = targetValue; + } + + public void SetVolume(float targetValue, float duration, EasingFunction easingFunction) + { + Device.CreateTween(this, AudioTweenProperty.Volume, easingFunction, Volume, targetValue, duration); + } + + public void SetFilterFrequency(float targetValue) + { + FilterFrequency = targetValue; + } + + public void SetFilterFrequency(float targetValue, float duration, EasingFunction easingFunction) + { + Device.CreateTween(this, AudioTweenProperty.FilterFrequency, easingFunction, FilterFrequency, targetValue, duration); + } + + public void SetReverb(float targetValue) + { + Reverb = targetValue; + } + + public void SetReverb(float targetValue, float duration, EasingFunction easingFunction) + { + Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration); + } + public abstract void Play(); public abstract void QueueSyncPlay(); public abstract void Pause(); @@ -297,14 +350,12 @@ namespace MoonWorks.Audio ); dspSettings.pMatrixCoefficients = (nint) NativeMemory.Alloc(memsize); - unsafe + byte* memPtr = (byte*) dspSettings.pMatrixCoefficients; + for (uint i = 0; i < memsize; i += 1) { - byte* memPtr = (byte*) dspSettings.pMatrixCoefficients; - for (uint i = 0; i < memsize; i += 1) - { - memPtr[i] = 0; - } + memPtr[i] = 0; } + SetPanMatrixCoefficients(); }