audio interpolation support + audio threading

pull/47/head
cosmonaut 2023-03-03 15:20:26 -08:00
parent 040bf1c184
commit f70caa1a42
3 changed files with 244 additions and 33 deletions

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading;
using EasingFunction = System.Func<float, float>;
namespace MoonWorks.Audio
{
@ -29,10 +30,16 @@ namespace MoonWorks.Audio
private readonly List<WeakReference<AudioResource>> resources = new List<WeakReference<AudioResource>>();
private readonly List<WeakReference<StreamingSound>> streamingSounds = new List<WeakReference<StreamingSound>>();
private AudioTweenPool AudioTweenPool = new AudioTweenPool();
private readonly List<AudioTween> AudioTweens = new List<AudioTween>();
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>(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<StreamingSound>(instance));
lock (StateLock)
{
streamingSounds.Add(new WeakReference<StreamingSound>(instance));
}
}
internal void AddResourceReference(WeakReference<AudioResource> resourceReference)
{
lock (resources)
lock (StateLock)
{
resources.Add(resourceReference);
}
@ -172,7 +281,7 @@ namespace MoonWorks.Audio
internal void RemoveResourceReference(WeakReference<AudioResource> 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;
}
}

48
src/Audio/AudioTween.cs Normal file
View File

@ -0,0 +1,48 @@
using System.Collections.Generic;
using EasingFunction = System.Func<float, float>;
namespace MoonWorks.Audio
{
internal enum AudioTweenProperty
{
Pan,
Pitch,
Volume,
FilterFrequency,
Reverb
}
internal class AudioTween
{
public System.WeakReference<SoundInstance> SoundInstanceReference;
public AudioTweenProperty Property;
public EasingFunction EasingFunction;
public float Time;
public float Start;
public float End;
public float Duration;
}
internal class AudioTweenPool
{
private Queue<AudioTween> Tweens = new Queue<AudioTween>();
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);
}
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
using EasingFunction = System.Func<float, float>;
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();
}