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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using EasingFunction = System.Func<float, float>;
namespace MoonWorks.Audio namespace MoonWorks.Audio
{ {
@ -29,10 +30,16 @@ namespace MoonWorks.Audio
private readonly List<WeakReference<AudioResource>> resources = new List<WeakReference<AudioResource>>(); private readonly List<WeakReference<AudioResource>> resources = new List<WeakReference<AudioResource>>();
private readonly List<WeakReference<StreamingSound>> streamingSounds = new List<WeakReference<StreamingSound>>(); 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 const int Step = 200;
private TimeSpan UpdateInterval; private TimeSpan UpdateInterval;
private System.Diagnostics.Stopwatch TickStopwatch = new System.Diagnostics.Stopwatch();
private long previousTickTime;
private Thread Thread; private Thread Thread;
private AutoResetEvent WakeSignal; private AutoResetEvent WakeSignal;
private readonly object StateLock = new object();
private bool IsDisposed; private bool IsDisposed;
@ -119,13 +126,19 @@ namespace MoonWorks.Audio
Thread = new Thread(ThreadMain); Thread = new Thread(ThreadMain);
Thread.IsBackground = true; Thread.IsBackground = true;
Thread.Start(); Thread.Start();
TickStopwatch.Start();
previousTickTime = 0;
} }
private void ThreadMain() private void ThreadMain()
{ {
while (!IsDisposed) while (!IsDisposed)
{ {
ThreadMainTick(); lock (StateLock)
{
ThreadMainTick();
}
WakeSignal.WaitOne(UpdateInterval); WakeSignal.WaitOne(UpdateInterval);
} }
@ -133,6 +146,10 @@ namespace MoonWorks.Audio
private void ThreadMainTick() 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--) for (var i = streamingSounds.Count - 1; i >= 0; i--)
{ {
var weakReference = streamingSounds[i]; var weakReference = streamingSounds[i];
@ -145,11 +162,26 @@ namespace MoonWorks.Audio
streamingSounds.RemoveAt(i); streamingSounds.RemoveAt(i);
} }
} }
}
internal void WakeThread() lock (AudioTweens)
{ {
WakeSignal.Set(); 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() public void SyncPlay()
@ -157,14 +189,91 @@ namespace MoonWorks.Audio
FAudio.FAudio_CommitChanges(Handle, 1); 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) 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) internal void AddResourceReference(WeakReference<AudioResource> resourceReference)
{ {
lock (resources) lock (StateLock)
{ {
resources.Add(resourceReference); resources.Add(resourceReference);
} }
@ -172,7 +281,7 @@ namespace MoonWorks.Audio
internal void RemoveResourceReference(WeakReference<AudioResource> resourceReference) internal void RemoveResourceReference(WeakReference<AudioResource> resourceReference)
{ {
lock (resources) lock (StateLock)
{ {
resources.Remove(resourceReference); resources.Remove(resourceReference);
} }
@ -182,24 +291,27 @@ namespace MoonWorks.Audio
{ {
if (!IsDisposed) if (!IsDisposed)
{ {
if (disposing) lock (StateLock)
{ {
for (var i = resources.Count - 1; i >= 0; i--) if (disposing)
{ {
var weakReference = resources[i]; for (var i = resources.Count - 1; i >= 0; i--)
if (weakReference.TryGetTarget(out var resource))
{ {
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;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using EasingFunction = System.Func<float, float>;
namespace MoonWorks.Audio namespace MoonWorks.Audio
{ {
@ -21,7 +22,7 @@ namespace MoonWorks.Audio
public float Pan public float Pan
{ {
get => pan; get => pan;
set internal set
{ {
pan = value; pan = value;
@ -52,7 +53,7 @@ namespace MoonWorks.Audio
public float Pitch public float Pitch
{ {
get => pitch; get => pitch;
set internal set
{ {
pitch = Math.MathHelper.Clamp(value, -1f, 1f); pitch = Math.MathHelper.Clamp(value, -1f, 1f);
UpdatePitch(); UpdatePitch();
@ -63,7 +64,7 @@ namespace MoonWorks.Audio
public float Volume public float Volume
{ {
get => volume; get => volume;
set internal set
{ {
volume = value; volume = value;
FAudio.FAudioVoice_SetVolume(Voice, volume, 0); FAudio.FAudioVoice_SetVolume(Voice, volume, 0);
@ -80,10 +81,10 @@ namespace MoonWorks.Audio
OneOverQ = 1f OneOverQ = 1f
}; };
private float FilterFrequency public float FilterFrequency
{ {
get => filterParameters.Frequency; get => filterParameters.Frequency;
set internal set
{ {
value = System.Math.Clamp(value, 0.01f, MAX_FILTER_FREQUENCY); value = System.Math.Clamp(value, 0.01f, MAX_FILTER_FREQUENCY);
filterParameters.Frequency = value; filterParameters.Frequency = value;
@ -156,7 +157,7 @@ namespace MoonWorks.Audio
public unsafe float Reverb public unsafe float Reverb
{ {
get => reverb; get => reverb;
set internal set
{ {
if (ReverbEffect != null) if (ReverbEffect != null)
{ {
@ -188,7 +189,7 @@ namespace MoonWorks.Audio
} }
} }
public SoundInstance( public unsafe SoundInstance(
AudioDevice device, AudioDevice device,
ushort formatTag, ushort formatTag,
ushort bitsPerSample, ushort bitsPerSample,
@ -277,6 +278,58 @@ namespace MoonWorks.Audio
ReverbEffect = reverbEffect; 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 Play();
public abstract void QueueSyncPlay(); public abstract void QueueSyncPlay();
public abstract void Pause(); public abstract void Pause();
@ -297,14 +350,12 @@ namespace MoonWorks.Audio
); );
dspSettings.pMatrixCoefficients = (nint) NativeMemory.Alloc(memsize); 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; memPtr[i] = 0;
for (uint i = 0; i < memsize; i += 1)
{
memPtr[i] = 0;
}
} }
SetPanMatrixCoefficients(); SetPanMatrixCoefficients();
} }