forked from MoonsideGames/MoonWorks
Audio Improvements (#47)
- Audio is now processed on a background thread instead of the main thread - Audio tick rate is now ~200Hz - MoonWorks.Math.Easings class completely rewritten to be easier to understand and use - SoundInstance properties no longer call into FAudio unless the value actually changed - SoundInstance property values can now be interpolated over time (tweens) - SoundInstance tweens can be delayed - SoundInstance sets a sane filter frequency default when switching filter type - StreamingSound classes can be designated to update automatically on the audio thread or manually - StreamingSound buffer consumption should now set Stopped state in a more sane way - Added ReverbEffect, which creates a submix voice for a reverb effect - SoundInstance can apply a ReverbEffect, which enables the Reverb property - Audio resource tracking improvements - Some tweaks to VideoPlayer to make its behavior more consistent Reviewed-on: MoonsideGames/MoonWorks#47remotes/1695061714407202320/main
parent
f8b14ea94f
commit
1f0e3b5040
|
@ -1,6 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.InteropServices;
|
using System.Threading;
|
||||||
|
|
||||||
namespace MoonWorks.Audio
|
namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
|
@ -26,13 +26,25 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<WeakReference<AudioResource>> resources = new List<WeakReference<AudioResource>>();
|
private readonly HashSet<WeakReference> resources = new HashSet<WeakReference>();
|
||||||
private readonly List<WeakReference<StreamingSound>> streamingSounds = new List<WeakReference<StreamingSound>>();
|
private readonly HashSet<WeakReference> autoUpdateStreamingSoundReferences = new HashSet<WeakReference>();
|
||||||
|
|
||||||
|
private AudioTweenManager AudioTweenManager;
|
||||||
|
|
||||||
|
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;
|
||||||
|
internal readonly object StateLock = new object();
|
||||||
|
|
||||||
private bool IsDisposed;
|
private bool IsDisposed;
|
||||||
|
|
||||||
public unsafe AudioDevice()
|
public unsafe AudioDevice()
|
||||||
{
|
{
|
||||||
|
UpdateInterval = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / Step);
|
||||||
|
|
||||||
FAudio.FAudioCreate(out var handle, 0, FAudio.FAUDIO_DEFAULT_PROCESSOR);
|
FAudio.FAudioCreate(out var handle, 0, FAudio.FAUDIO_DEFAULT_PROCESSOR);
|
||||||
Handle = handle;
|
Handle = handle;
|
||||||
|
|
||||||
|
@ -90,8 +102,8 @@ namespace MoonWorks.Audio
|
||||||
) != 0)
|
) != 0)
|
||||||
{
|
{
|
||||||
Logger.LogError("No mastering voice found!");
|
Logger.LogError("No mastering voice found!");
|
||||||
Handle = IntPtr.Zero;
|
|
||||||
FAudio.FAudio_Release(Handle);
|
FAudio.FAudio_Release(Handle);
|
||||||
|
Handle = IntPtr.Zero;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,22 +117,52 @@ namespace MoonWorks.Audio
|
||||||
SpeedOfSound,
|
SpeedOfSound,
|
||||||
Handle3D
|
Handle3D
|
||||||
);
|
);
|
||||||
|
|
||||||
|
AudioTweenManager = new AudioTweenManager();
|
||||||
|
|
||||||
|
Logger.LogInfo("Setting up audio thread...");
|
||||||
|
WakeSignal = new AutoResetEvent(true);
|
||||||
|
|
||||||
|
Thread = new Thread(ThreadMain);
|
||||||
|
Thread.IsBackground = true;
|
||||||
|
Thread.Start();
|
||||||
|
|
||||||
|
TickStopwatch.Start();
|
||||||
|
previousTickTime = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Update()
|
private void ThreadMain()
|
||||||
{
|
{
|
||||||
for (var i = streamingSounds.Count - 1; i >= 0; i--)
|
while (!IsDisposed)
|
||||||
{
|
{
|
||||||
var weakReference = streamingSounds[i];
|
lock (StateLock)
|
||||||
if (weakReference.TryGetTarget(out var streamingSound))
|
{
|
||||||
|
ThreadMainTick();
|
||||||
|
}
|
||||||
|
|
||||||
|
WakeSignal.WaitOne(UpdateInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThreadMainTick()
|
||||||
|
{
|
||||||
|
long tickDelta = TickStopwatch.Elapsed.Ticks - previousTickTime;
|
||||||
|
previousTickTime = TickStopwatch.Elapsed.Ticks;
|
||||||
|
float elapsedSeconds = (float) tickDelta / System.TimeSpan.TicksPerSecond;
|
||||||
|
|
||||||
|
foreach (var weakReference in autoUpdateStreamingSoundReferences)
|
||||||
|
{
|
||||||
|
if (weakReference.Target is StreamingSound streamingSound)
|
||||||
{
|
{
|
||||||
streamingSound.Update();
|
streamingSound.Update();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
streamingSounds.RemoveAt(i);
|
autoUpdateStreamingSoundReferences.Remove(weakReference);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AudioTweenManager.Update(elapsedSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SyncPlay()
|
public void SyncPlay()
|
||||||
|
@ -128,40 +170,95 @@ namespace MoonWorks.Audio
|
||||||
FAudio.FAudio_CommitChanges(Handle, 1);
|
FAudio.FAudio_CommitChanges(Handle, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void AddDynamicSoundInstance(StreamingSound instance)
|
internal void CreateTween(
|
||||||
|
SoundInstance soundInstance,
|
||||||
|
AudioTweenProperty property,
|
||||||
|
System.Func<float, float> easingFunction,
|
||||||
|
float start,
|
||||||
|
float end,
|
||||||
|
float duration,
|
||||||
|
float delayTime
|
||||||
|
) {
|
||||||
|
lock (StateLock)
|
||||||
{
|
{
|
||||||
streamingSounds.Add(new WeakReference<StreamingSound>(instance));
|
AudioTweenManager.CreateTween(
|
||||||
}
|
soundInstance,
|
||||||
|
property,
|
||||||
internal void AddResourceReference(WeakReference<AudioResource> resourceReference)
|
easingFunction,
|
||||||
{
|
start,
|
||||||
lock (resources)
|
end,
|
||||||
{
|
duration,
|
||||||
resources.Add(resourceReference);
|
delayTime
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void RemoveResourceReference(WeakReference<AudioResource> resourceReference)
|
internal void ClearTweens(
|
||||||
|
WeakReference soundReference,
|
||||||
|
AudioTweenProperty property
|
||||||
|
) {
|
||||||
|
lock (StateLock)
|
||||||
{
|
{
|
||||||
lock (resources)
|
AudioTweenManager.ClearTweens(soundReference, property);
|
||||||
{
|
|
||||||
resources.Remove(resourceReference);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void WakeThread()
|
||||||
|
{
|
||||||
|
WakeSignal.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void AddResourceReference(AudioResource resource)
|
||||||
|
{
|
||||||
|
lock (StateLock)
|
||||||
|
{
|
||||||
|
resources.Add(resource.weakReference);
|
||||||
|
|
||||||
|
if (resource is StreamingSound streamingSound && streamingSound.AutoUpdate)
|
||||||
|
{
|
||||||
|
AddAutoUpdateStreamingSoundInstance(streamingSound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RemoveResourceReference(AudioResource resource)
|
||||||
|
{
|
||||||
|
lock (StateLock)
|
||||||
|
{
|
||||||
|
resources.Remove(resource.weakReference);
|
||||||
|
|
||||||
|
if (resource is StreamingSound streamingSound && streamingSound.AutoUpdate)
|
||||||
|
{
|
||||||
|
RemoveAutoUpdateStreamingSoundInstance(streamingSound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddAutoUpdateStreamingSoundInstance(StreamingSound instance)
|
||||||
|
{
|
||||||
|
autoUpdateStreamingSoundReferences.Add(instance.weakReference);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveAutoUpdateStreamingSoundInstance(StreamingSound instance)
|
||||||
|
{
|
||||||
|
autoUpdateStreamingSoundReferences.Remove(instance.weakReference);
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (!IsDisposed)
|
if (!IsDisposed)
|
||||||
|
{
|
||||||
|
lock (StateLock)
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
for (var i = resources.Count - 1; i >= 0; i--)
|
foreach (var weakReference in resources)
|
||||||
{
|
{
|
||||||
var weakReference = resources[i];
|
var target = weakReference.Target;
|
||||||
|
|
||||||
if (weakReference.TryGetTarget(out var resource))
|
if (target != null)
|
||||||
{
|
{
|
||||||
resource.Dispose();
|
(target as IDisposable).Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resources.Clear();
|
resources.Clear();
|
||||||
|
@ -173,8 +270,8 @@ namespace MoonWorks.Audio
|
||||||
IsDisposed = true;
|
IsDisposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
|
||||||
~AudioDevice()
|
~AudioDevice()
|
||||||
{
|
{
|
||||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
|
|
|
@ -8,14 +8,14 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
public bool IsDisposed { get; private set; }
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
private WeakReference<AudioResource> selfReference;
|
internal WeakReference weakReference;
|
||||||
|
|
||||||
public AudioResource(AudioDevice device)
|
public AudioResource(AudioDevice device)
|
||||||
{
|
{
|
||||||
Device = device;
|
Device = device;
|
||||||
|
|
||||||
selfReference = new WeakReference<AudioResource>(this);
|
weakReference = new WeakReference(this);
|
||||||
Device.AddResourceReference(selfReference);
|
Device.AddResourceReference(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void Destroy();
|
protected abstract void Destroy();
|
||||||
|
@ -26,10 +26,10 @@ namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
Destroy();
|
Destroy();
|
||||||
|
|
||||||
if (selfReference != null)
|
if (weakReference != null)
|
||||||
{
|
{
|
||||||
Device.RemoveResourceReference(selfReference);
|
Device.RemoveResourceReference(this);
|
||||||
selfReference = null;
|
weakReference = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
IsDisposed = true;
|
IsDisposed = true;
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
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 SoundInstanceReference;
|
||||||
|
public AudioTweenProperty Property;
|
||||||
|
public EasingFunction EasingFunction;
|
||||||
|
public float Time;
|
||||||
|
public float StartValue;
|
||||||
|
public float EndValue;
|
||||||
|
public float DelayTime;
|
||||||
|
public float Duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class AudioTweenPool
|
||||||
|
{
|
||||||
|
private Queue<AudioTween> Tweens = new Queue<AudioTween>(16);
|
||||||
|
|
||||||
|
public AudioTweenPool()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 16; i += 1)
|
||||||
|
{
|
||||||
|
Tweens.Enqueue(new 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,169 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MoonWorks.Audio
|
||||||
|
{
|
||||||
|
internal class AudioTweenManager
|
||||||
|
{
|
||||||
|
private AudioTweenPool AudioTweenPool = new AudioTweenPool();
|
||||||
|
private readonly Dictionary<(WeakReference, AudioTweenProperty), AudioTween> AudioTweens = new Dictionary<(WeakReference, AudioTweenProperty), AudioTween>();
|
||||||
|
private readonly List<AudioTween> DelayedAudioTweens = new List<AudioTween>();
|
||||||
|
|
||||||
|
public void Update(float elapsedSeconds)
|
||||||
|
{
|
||||||
|
for (var i = DelayedAudioTweens.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var audioTween = DelayedAudioTweens[i];
|
||||||
|
if (audioTween.SoundInstanceReference.Target is SoundInstance soundInstance)
|
||||||
|
{
|
||||||
|
audioTween.Time += elapsedSeconds;
|
||||||
|
|
||||||
|
if (audioTween.Time >= audioTween.DelayTime)
|
||||||
|
{
|
||||||
|
// set the tween start value to the current value of the property
|
||||||
|
switch (audioTween.Property)
|
||||||
|
{
|
||||||
|
case AudioTweenProperty.Pan:
|
||||||
|
audioTween.StartValue = soundInstance.Pan;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AudioTweenProperty.Pitch:
|
||||||
|
audioTween.StartValue = soundInstance.Pitch;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AudioTweenProperty.Volume:
|
||||||
|
audioTween.StartValue = soundInstance.Volume;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AudioTweenProperty.FilterFrequency:
|
||||||
|
audioTween.StartValue = soundInstance.FilterFrequency;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AudioTweenProperty.Reverb:
|
||||||
|
audioTween.StartValue = soundInstance.Reverb;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
audioTween.Time = 0;
|
||||||
|
DelayedAudioTweens.RemoveAt(i);
|
||||||
|
|
||||||
|
AddTween(audioTween);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AudioTweenPool.Free(audioTween);
|
||||||
|
DelayedAudioTweens.RemoveAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (key, audioTween) in AudioTweens)
|
||||||
|
{
|
||||||
|
bool finished = true;
|
||||||
|
if (audioTween.SoundInstanceReference.Target is SoundInstance soundInstance)
|
||||||
|
{
|
||||||
|
finished = UpdateAudioTween(audioTween, soundInstance, elapsedSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finished)
|
||||||
|
{
|
||||||
|
AudioTweenPool.Free(audioTween);
|
||||||
|
AudioTweens.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateTween(
|
||||||
|
SoundInstance soundInstance,
|
||||||
|
AudioTweenProperty property,
|
||||||
|
System.Func<float, float> easingFunction,
|
||||||
|
float start,
|
||||||
|
float end,
|
||||||
|
float duration,
|
||||||
|
float delayTime
|
||||||
|
) {
|
||||||
|
var tween = AudioTweenPool.Obtain();
|
||||||
|
tween.SoundInstanceReference = soundInstance.weakReference;
|
||||||
|
tween.Property = property;
|
||||||
|
tween.EasingFunction = easingFunction;
|
||||||
|
tween.StartValue = start;
|
||||||
|
tween.EndValue = end;
|
||||||
|
tween.Duration = duration;
|
||||||
|
tween.Time = 0;
|
||||||
|
tween.DelayTime = delayTime;
|
||||||
|
|
||||||
|
if (delayTime == 0)
|
||||||
|
{
|
||||||
|
AddTween(tween);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DelayedAudioTweens.Add(tween);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearTweens(WeakReference soundReference, AudioTweenProperty property)
|
||||||
|
{
|
||||||
|
AudioTweens.Remove((soundReference, property));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddTween(
|
||||||
|
AudioTween audioTween
|
||||||
|
) {
|
||||||
|
// if a tween with the same sound and property already exists, get rid of it
|
||||||
|
if (AudioTweens.TryGetValue((audioTween.SoundInstanceReference, audioTween.Property), out var currentTween))
|
||||||
|
{
|
||||||
|
AudioTweenPool.Free(currentTween);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioTweens[(audioTween.SoundInstanceReference, audioTween.Property)] = audioTween;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool UpdateAudioTween(AudioTween audioTween, SoundInstance soundInstance, float delta)
|
||||||
|
{
|
||||||
|
float value;
|
||||||
|
audioTween.Time += delta;
|
||||||
|
|
||||||
|
var finished = audioTween.Time >= audioTween.Duration;
|
||||||
|
if (finished)
|
||||||
|
{
|
||||||
|
value = audioTween.EndValue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = MoonWorks.Math.Easing.Interp(
|
||||||
|
audioTween.StartValue,
|
||||||
|
audioTween.EndValue,
|
||||||
|
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 finished;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,14 +4,12 @@ using System.Runtime.InteropServices;
|
||||||
namespace MoonWorks.Audio
|
namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
// sound instances can send their audio to this voice to add reverb
|
// sound instances can send their audio to this voice to add reverb
|
||||||
public unsafe class ReverbEffect : IDisposable
|
public unsafe class ReverbEffect : AudioResource
|
||||||
{
|
{
|
||||||
private IntPtr voice;
|
private IntPtr voice;
|
||||||
public IntPtr Voice => voice;
|
public IntPtr Voice => voice;
|
||||||
|
|
||||||
private bool disposedValue;
|
public ReverbEffect(AudioDevice audioDevice) : base(audioDevice)
|
||||||
|
|
||||||
public ReverbEffect(AudioDevice audioDevice)
|
|
||||||
{
|
{
|
||||||
/* Init reverb */
|
/* Init reverb */
|
||||||
|
|
||||||
|
@ -97,32 +95,9 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected override void Destroy()
|
||||||
{
|
{
|
||||||
if (!disposedValue)
|
FAudio.FAudioVoice_DestroyVoice(Voice);
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
// TODO: dispose managed state (managed objects)
|
|
||||||
}
|
|
||||||
|
|
||||||
FAudio.FAudioVoice_DestroyVoice(voice);
|
|
||||||
|
|
||||||
disposedValue = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
~ReverbEffect()
|
|
||||||
{
|
|
||||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
||||||
Dispose(disposing: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
||||||
Dispose(disposing: true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using EasingFunction = System.Func<float, float>;
|
||||||
|
|
||||||
namespace MoonWorks.Audio
|
namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
public abstract class SoundInstance : AudioResource
|
public abstract class SoundInstance : AudioResource
|
||||||
{
|
{
|
||||||
internal IntPtr Voice;
|
internal IntPtr Voice;
|
||||||
internal FAudio.FAudioWaveFormatEx Format;
|
|
||||||
|
private FAudio.FAudioWaveFormatEx format;
|
||||||
|
public FAudio.FAudioWaveFormatEx Format => format;
|
||||||
|
|
||||||
protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings;
|
protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings;
|
||||||
|
|
||||||
|
@ -21,7 +24,10 @@ namespace MoonWorks.Audio
|
||||||
public float Pan
|
public float Pan
|
||||||
{
|
{
|
||||||
get => pan;
|
get => pan;
|
||||||
set
|
internal set
|
||||||
|
{
|
||||||
|
value = Math.MathHelper.Clamp(value, -1f, 1f);
|
||||||
|
if (pan != value)
|
||||||
{
|
{
|
||||||
pan = value;
|
pan = value;
|
||||||
|
|
||||||
|
@ -47,28 +53,37 @@ namespace MoonWorks.Audio
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private float pitch = 0;
|
private float pitch = 0;
|
||||||
public float Pitch
|
public float Pitch
|
||||||
{
|
{
|
||||||
get => pitch;
|
get => pitch;
|
||||||
set
|
internal set
|
||||||
{
|
{
|
||||||
pitch = Math.MathHelper.Clamp(value, -1f, 1f);
|
value = Math.MathHelper.Clamp(value, -1f, 1f);
|
||||||
|
if (pitch != value)
|
||||||
|
{
|
||||||
|
pitch = value;
|
||||||
UpdatePitch();
|
UpdatePitch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private float volume = 1;
|
private float volume = 1;
|
||||||
public float Volume
|
public float Volume
|
||||||
{
|
{
|
||||||
get => volume;
|
get => volume;
|
||||||
set
|
internal set
|
||||||
|
{
|
||||||
|
value = Math.MathHelper.Max(0, value);
|
||||||
|
if (volume != value)
|
||||||
{
|
{
|
||||||
volume = value;
|
volume = value;
|
||||||
FAudio.FAudioVoice_SetVolume(Voice, volume, 0);
|
FAudio.FAudioVoice_SetVolume(Voice, volume, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private const float MAX_FILTER_FREQUENCY = 1f;
|
private const float MAX_FILTER_FREQUENCY = 1f;
|
||||||
private const float MAX_FILTER_ONEOVERQ = 1.5f;
|
private const float MAX_FILTER_ONEOVERQ = 1.5f;
|
||||||
|
@ -80,12 +95,14 @@ 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);
|
||||||
|
if (filterParameters.Frequency != value)
|
||||||
|
{
|
||||||
filterParameters.Frequency = value;
|
filterParameters.Frequency = value;
|
||||||
|
|
||||||
FAudio.FAudioVoice_SetFilterParameters(
|
FAudio.FAudioVoice_SetFilterParameters(
|
||||||
|
@ -95,13 +112,16 @@ namespace MoonWorks.Audio
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private float FilterOneOverQ
|
public float FilterOneOverQ
|
||||||
{
|
{
|
||||||
get => filterParameters.OneOverQ;
|
get => filterParameters.OneOverQ;
|
||||||
set
|
internal set
|
||||||
{
|
{
|
||||||
value = System.Math.Clamp(value, 0.01f, MAX_FILTER_ONEOVERQ);
|
value = System.Math.Clamp(value, 0.01f, MAX_FILTER_ONEOVERQ);
|
||||||
|
if (filterParameters.OneOverQ != value)
|
||||||
|
{
|
||||||
filterParameters.OneOverQ = value;
|
filterParameters.OneOverQ = value;
|
||||||
|
|
||||||
FAudio.FAudioVoice_SetFilterParameters(
|
FAudio.FAudioVoice_SetFilterParameters(
|
||||||
|
@ -111,12 +131,15 @@ namespace MoonWorks.Audio
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private FilterType filterType;
|
private FilterType filterType;
|
||||||
public FilterType FilterType
|
public FilterType FilterType
|
||||||
{
|
{
|
||||||
get => filterType;
|
get => filterType;
|
||||||
set
|
set
|
||||||
|
{
|
||||||
|
if (filterType != value)
|
||||||
{
|
{
|
||||||
filterType = value;
|
filterType = value;
|
||||||
|
|
||||||
|
@ -133,6 +156,7 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
case FilterType.LowPass:
|
case FilterType.LowPass:
|
||||||
filterParameters.Type = FAudio.FAudioFilterType.FAudioLowPassFilter;
|
filterParameters.Type = FAudio.FAudioFilterType.FAudioLowPassFilter;
|
||||||
|
filterParameters.Frequency = 1f;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FilterType.BandPass:
|
case FilterType.BandPass:
|
||||||
|
@ -141,6 +165,7 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
case FilterType.HighPass:
|
case FilterType.HighPass:
|
||||||
filterParameters.Type = FAudio.FAudioFilterType.FAudioHighPassFilter;
|
filterParameters.Type = FAudio.FAudioFilterType.FAudioHighPassFilter;
|
||||||
|
filterParameters.Frequency = 0f;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,14 +176,18 @@ namespace MoonWorks.Audio
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private float reverb;
|
private float reverb;
|
||||||
public unsafe float Reverb
|
public unsafe float Reverb
|
||||||
{
|
{
|
||||||
get => reverb;
|
get => reverb;
|
||||||
set
|
internal set
|
||||||
{
|
{
|
||||||
if (ReverbEffect != null)
|
if (ReverbEffect != null)
|
||||||
|
{
|
||||||
|
value = MathF.Max(0, value);
|
||||||
|
if (reverb != value)
|
||||||
{
|
{
|
||||||
reverb = value;
|
reverb = value;
|
||||||
|
|
||||||
|
@ -178,6 +207,7 @@ namespace MoonWorks.Audio
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if (ReverbEffect == null)
|
if (ReverbEffect == null)
|
||||||
|
@ -188,7 +218,7 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public SoundInstance(
|
public unsafe SoundInstance(
|
||||||
AudioDevice device,
|
AudioDevice device,
|
||||||
ushort formatTag,
|
ushort formatTag,
|
||||||
ushort bitsPerSample,
|
ushort bitsPerSample,
|
||||||
|
@ -197,7 +227,7 @@ namespace MoonWorks.Audio
|
||||||
uint samplesPerSecond
|
uint samplesPerSecond
|
||||||
) : base(device)
|
) : base(device)
|
||||||
{
|
{
|
||||||
var format = new FAudio.FAudioWaveFormatEx
|
format = new FAudio.FAudioWaveFormatEx
|
||||||
{
|
{
|
||||||
wFormatTag = formatTag,
|
wFormatTag = formatTag,
|
||||||
wBitsPerSample = bitsPerSample,
|
wBitsPerSample = bitsPerSample,
|
||||||
|
@ -207,12 +237,10 @@ namespace MoonWorks.Audio
|
||||||
nAvgBytesPerSec = blockAlign * samplesPerSecond
|
nAvgBytesPerSec = blockAlign * samplesPerSecond
|
||||||
};
|
};
|
||||||
|
|
||||||
Format = format;
|
|
||||||
|
|
||||||
FAudio.FAudio_CreateSourceVoice(
|
FAudio.FAudio_CreateSourceVoice(
|
||||||
Device.Handle,
|
Device.Handle,
|
||||||
out Voice,
|
out Voice,
|
||||||
ref Format,
|
ref format,
|
||||||
FAudio.FAUDIO_VOICE_USEFILTER,
|
FAudio.FAUDIO_VOICE_USEFILTER,
|
||||||
FAudio.FAUDIO_DEFAULT_FREQ_RATIO,
|
FAudio.FAUDIO_DEFAULT_FREQ_RATIO,
|
||||||
IntPtr.Zero,
|
IntPtr.Zero,
|
||||||
|
@ -277,6 +305,91 @@ namespace MoonWorks.Audio
|
||||||
ReverbEffect = reverbEffect;
|
ReverbEffect = reverbEffect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetPan(float targetValue)
|
||||||
|
{
|
||||||
|
Pan = targetValue;
|
||||||
|
Device.ClearTweens(weakReference, AudioTweenProperty.Pan);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPan(float targetValue, float duration, EasingFunction easingFunction)
|
||||||
|
{
|
||||||
|
Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPan(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
|
||||||
|
{
|
||||||
|
Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, delayTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPitch(float targetValue)
|
||||||
|
{
|
||||||
|
Pitch = targetValue;
|
||||||
|
Device.ClearTweens(weakReference, AudioTweenProperty.Pitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPitch(float targetValue, float duration, EasingFunction easingFunction)
|
||||||
|
{
|
||||||
|
Device.CreateTween(this, AudioTweenProperty.Pitch, easingFunction, Pan, targetValue, duration, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPitch(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
|
||||||
|
{
|
||||||
|
Device.CreateTween(this, AudioTweenProperty.Pitch, easingFunction, Pan, targetValue, duration, delayTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetVolume(float targetValue)
|
||||||
|
{
|
||||||
|
Volume = targetValue;
|
||||||
|
Device.ClearTweens(weakReference, AudioTweenProperty.Volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetVolume(float targetValue, float duration, EasingFunction easingFunction)
|
||||||
|
{
|
||||||
|
Device.CreateTween(this, AudioTweenProperty.Volume, easingFunction, Volume, targetValue, duration, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetVolume(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
|
||||||
|
{
|
||||||
|
Device.CreateTween(this, AudioTweenProperty.Volume, easingFunction, Volume, targetValue, duration, delayTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetFilterFrequency(float targetValue)
|
||||||
|
{
|
||||||
|
FilterFrequency = targetValue;
|
||||||
|
Device.ClearTweens(weakReference, AudioTweenProperty.FilterFrequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetFilterFrequency(float targetValue, float duration, EasingFunction easingFunction)
|
||||||
|
{
|
||||||
|
Device.CreateTween(this, AudioTweenProperty.FilterFrequency, easingFunction, FilterFrequency, targetValue, duration, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetFilterFrequency(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
|
||||||
|
{
|
||||||
|
Device.CreateTween(this, AudioTweenProperty.FilterFrequency, easingFunction, FilterFrequency, targetValue, duration, delayTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetFilterOneOverQ(float targetValue)
|
||||||
|
{
|
||||||
|
FilterOneOverQ = targetValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetReverb(float targetValue)
|
||||||
|
{
|
||||||
|
Reverb = targetValue;
|
||||||
|
Device.ClearTweens(weakReference, AudioTweenProperty.Reverb);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetReverb(float targetValue, float duration, EasingFunction easingFunction)
|
||||||
|
{
|
||||||
|
Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetReverb(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
|
||||||
|
{
|
||||||
|
Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, delayTime);
|
||||||
|
}
|
||||||
|
|
||||||
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 +410,12 @@ namespace MoonWorks.Audio
|
||||||
);
|
);
|
||||||
|
|
||||||
dspSettings.pMatrixCoefficients = (nint) NativeMemory.Alloc(memsize);
|
dspSettings.pMatrixCoefficients = (nint) NativeMemory.Alloc(memsize);
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
byte* memPtr = (byte*) dspSettings.pMatrixCoefficients;
|
byte* memPtr = (byte*) dspSettings.pMatrixCoefficients;
|
||||||
for (uint i = 0; i < memsize; i += 1)
|
for (uint i = 0; i < memsize; i += 1)
|
||||||
{
|
{
|
||||||
memPtr[i] = 0;
|
memPtr[i] = 0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
SetPanMatrixCoefficients();
|
SetPanMatrixCoefficients();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,11 +10,21 @@ namespace MoonWorks.Audio
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class StreamingSound : SoundInstance
|
public abstract class StreamingSound : SoundInstance
|
||||||
{
|
{
|
||||||
|
// How big should each buffer we consume be?
|
||||||
|
protected abstract int BUFFER_SIZE { get; }
|
||||||
|
|
||||||
|
// Should the AudioDevice thread automatically update this class?
|
||||||
|
public abstract bool AutoUpdate { get; }
|
||||||
|
|
||||||
|
// Are we actively consuming buffers?
|
||||||
|
protected bool ConsumingBuffers = false;
|
||||||
|
|
||||||
private const int BUFFER_COUNT = 3;
|
private const int BUFFER_COUNT = 3;
|
||||||
private readonly IntPtr[] buffers;
|
private readonly IntPtr[] buffers;
|
||||||
private int nextBufferIndex = 0;
|
private int nextBufferIndex = 0;
|
||||||
private uint queuedBufferCount = 0;
|
private uint queuedBufferCount = 0;
|
||||||
protected abstract int BUFFER_SIZE { get; }
|
|
||||||
|
private readonly object StateLock = new object();
|
||||||
|
|
||||||
public unsafe StreamingSound(
|
public unsafe StreamingSound(
|
||||||
AudioDevice device,
|
AudioDevice device,
|
||||||
|
@ -25,8 +35,6 @@ namespace MoonWorks.Audio
|
||||||
uint samplesPerSecond
|
uint samplesPerSecond
|
||||||
) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond)
|
) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond)
|
||||||
{
|
{
|
||||||
device.AddDynamicSoundInstance(this);
|
|
||||||
|
|
||||||
buffers = new IntPtr[BUFFER_COUNT];
|
buffers = new IntPtr[BUFFER_COUNT];
|
||||||
for (int i = 0; i < BUFFER_COUNT; i += 1)
|
for (int i = 0; i < BUFFER_COUNT; i += 1)
|
||||||
{
|
{
|
||||||
|
@ -45,6 +53,8 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PlayUsingOperationSet(uint operationSet)
|
private void PlayUsingOperationSet(uint operationSet)
|
||||||
|
{
|
||||||
|
lock (StateLock)
|
||||||
{
|
{
|
||||||
if (State == SoundState.Playing)
|
if (State == SoundState.Playing)
|
||||||
{
|
{
|
||||||
|
@ -53,40 +63,65 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
State = SoundState.Playing;
|
State = SoundState.Playing;
|
||||||
|
|
||||||
Update();
|
ConsumingBuffers = true;
|
||||||
|
QueueBuffers();
|
||||||
FAudio.FAudioSourceVoice_Start(Voice, 0, operationSet);
|
FAudio.FAudioSourceVoice_Start(Voice, 0, operationSet);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void Pause()
|
public override void Pause()
|
||||||
|
{
|
||||||
|
lock (StateLock)
|
||||||
{
|
{
|
||||||
if (State == SoundState.Playing)
|
if (State == SoundState.Playing)
|
||||||
{
|
{
|
||||||
|
ConsumingBuffers = false;
|
||||||
FAudio.FAudioSourceVoice_Stop(Voice, 0, 0);
|
FAudio.FAudioSourceVoice_Stop(Voice, 0, 0);
|
||||||
State = SoundState.Paused;
|
State = SoundState.Paused;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void Stop()
|
public override void Stop()
|
||||||
{
|
{
|
||||||
|
lock (StateLock)
|
||||||
|
{
|
||||||
|
ConsumingBuffers = false;
|
||||||
State = SoundState.Stopped;
|
State = SoundState.Stopped;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void StopImmediate()
|
public override void StopImmediate()
|
||||||
{
|
{
|
||||||
|
lock (StateLock)
|
||||||
|
{
|
||||||
|
ConsumingBuffers = false;
|
||||||
FAudio.FAudioSourceVoice_Stop(Voice, 0, 0);
|
FAudio.FAudioSourceVoice_Stop(Voice, 0, 0);
|
||||||
FAudio.FAudioSourceVoice_FlushSourceBuffers(Voice);
|
FAudio.FAudioSourceVoice_FlushSourceBuffers(Voice);
|
||||||
ClearBuffers();
|
ClearBuffers();
|
||||||
|
|
||||||
State = SoundState.Stopped;
|
State = SoundState.Stopped;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal unsafe void Update()
|
internal unsafe void Update()
|
||||||
|
{
|
||||||
|
lock (StateLock)
|
||||||
|
{
|
||||||
|
if (!IsDisposed)
|
||||||
{
|
{
|
||||||
if (State != SoundState.Playing)
|
if (State != SoundState.Playing)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueueBuffers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void QueueBuffers()
|
||||||
|
{
|
||||||
FAudio.FAudioSourceVoice_GetState(
|
FAudio.FAudioSourceVoice_GetState(
|
||||||
Voice,
|
Voice,
|
||||||
out var state,
|
out var state,
|
||||||
|
@ -95,16 +130,18 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
queuedBufferCount = state.BuffersQueued;
|
queuedBufferCount = state.BuffersQueued;
|
||||||
|
|
||||||
QueueBuffers();
|
if (ConsumingBuffers)
|
||||||
}
|
|
||||||
|
|
||||||
protected void QueueBuffers()
|
|
||||||
{
|
{
|
||||||
for (int i = 0; i < BUFFER_COUNT - queuedBufferCount; i += 1)
|
for (int i = 0; i < BUFFER_COUNT - queuedBufferCount; i += 1)
|
||||||
{
|
{
|
||||||
AddBuffer();
|
AddBuffer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (queuedBufferCount == 0)
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected unsafe void ClearBuffers()
|
protected unsafe void ClearBuffers()
|
||||||
{
|
{
|
||||||
|
@ -124,6 +161,8 @@ namespace MoonWorks.Audio
|
||||||
out bool reachedEnd
|
out bool reachedEnd
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (filledLengthInBytes > 0)
|
||||||
|
{
|
||||||
FAudio.FAudioBuffer buf = new FAudio.FAudioBuffer
|
FAudio.FAudioBuffer buf = new FAudio.FAudioBuffer
|
||||||
{
|
{
|
||||||
AudioBytes = (uint) filledLengthInBytes,
|
AudioBytes = (uint) filledLengthInBytes,
|
||||||
|
@ -142,19 +181,16 @@ namespace MoonWorks.Audio
|
||||||
);
|
);
|
||||||
|
|
||||||
queuedBufferCount += 1;
|
queuedBufferCount += 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* We have reached the end of the file, what do we do? */
|
|
||||||
if (reachedEnd)
|
if (reachedEnd)
|
||||||
{
|
{
|
||||||
|
/* We have reached the end of the data, what do we do? */
|
||||||
|
ConsumingBuffers = false;
|
||||||
OnReachedEnd();
|
OnReachedEnd();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void OnReachedEnd()
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected unsafe abstract void FillBuffer(
|
protected unsafe abstract void FillBuffer(
|
||||||
void* buffer,
|
void* buffer,
|
||||||
int bufferLengthInBytes, /* in bytes */
|
int bufferLengthInBytes, /* in bytes */
|
||||||
|
@ -162,7 +198,13 @@ namespace MoonWorks.Audio
|
||||||
out bool reachedEnd
|
out bool reachedEnd
|
||||||
);
|
);
|
||||||
|
|
||||||
|
protected abstract void OnReachedEnd();
|
||||||
|
|
||||||
protected unsafe override void Destroy()
|
protected unsafe override void Destroy()
|
||||||
|
{
|
||||||
|
lock (StateLock)
|
||||||
|
{
|
||||||
|
if (!IsDisposed)
|
||||||
{
|
{
|
||||||
StopImmediate();
|
StopImmediate();
|
||||||
|
|
||||||
|
@ -173,3 +215,5 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ namespace MoonWorks.Audio
|
||||||
private FAudio.stb_vorbis_info Info;
|
private FAudio.stb_vorbis_info Info;
|
||||||
|
|
||||||
protected override int BUFFER_SIZE => 32768;
|
protected override int BUFFER_SIZE => 32768;
|
||||||
|
public override bool AutoUpdate => true;
|
||||||
|
|
||||||
public unsafe static StreamingSoundOgg Load(AudioDevice device, string filePath)
|
public unsafe static StreamingSoundOgg Load(AudioDevice device, string filePath)
|
||||||
{
|
{
|
||||||
|
@ -35,7 +36,7 @@ namespace MoonWorks.Audio
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal StreamingSoundOgg(
|
internal unsafe StreamingSoundOgg(
|
||||||
AudioDevice device,
|
AudioDevice device,
|
||||||
IntPtr fileDataPtr, // MUST BE A NATIVE MEMORY HANDLE!!
|
IntPtr fileDataPtr, // MUST BE A NATIVE MEMORY HANDLE!!
|
||||||
IntPtr vorbisHandle,
|
IntPtr vorbisHandle,
|
||||||
|
@ -47,8 +48,7 @@ namespace MoonWorks.Audio
|
||||||
(ushort) (4 * info.channels),
|
(ushort) (4 * info.channels),
|
||||||
(ushort) info.channels,
|
(ushort) info.channels,
|
||||||
info.sample_rate
|
info.sample_rate
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
FileDataPtr = fileDataPtr;
|
FileDataPtr = fileDataPtr;
|
||||||
VorbisHandle = vorbisHandle;
|
VorbisHandle = vorbisHandle;
|
||||||
Info = info;
|
Info = info;
|
||||||
|
@ -64,8 +64,7 @@ namespace MoonWorks.Audio
|
||||||
int bufferLengthInBytes,
|
int bufferLengthInBytes,
|
||||||
out int filledLengthInBytes,
|
out int filledLengthInBytes,
|
||||||
out bool reachedEnd
|
out bool reachedEnd
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
var lengthInFloats = bufferLengthInBytes / sizeof(float);
|
var lengthInFloats = bufferLengthInBytes / sizeof(float);
|
||||||
|
|
||||||
/* NOTE: this function returns samples per channel, not total samples */
|
/* NOTE: this function returns samples per channel, not total samples */
|
||||||
|
@ -82,9 +81,14 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
|
|
||||||
protected unsafe override void Destroy()
|
protected unsafe override void Destroy()
|
||||||
|
{
|
||||||
|
base.Destroy();
|
||||||
|
|
||||||
|
if (!IsDisposed)
|
||||||
{
|
{
|
||||||
FAudio.stb_vorbis_close(VorbisHandle);
|
FAudio.stb_vorbis_close(VorbisHandle);
|
||||||
NativeMemory.Free((void*) FileDataPtr);
|
NativeMemory.Free((void*) FileDataPtr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,12 +14,9 @@ namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
if (Loop)
|
if (Loop)
|
||||||
{
|
{
|
||||||
|
ConsumingBuffers = true;
|
||||||
Seek(0);
|
Seek(0);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,9 +168,8 @@ namespace MoonWorks
|
||||||
while (accumulatedUpdateTime >= Timestep)
|
while (accumulatedUpdateTime >= Timestep)
|
||||||
{
|
{
|
||||||
Inputs.Update();
|
Inputs.Update();
|
||||||
AudioDevice.Update();
|
|
||||||
|
|
||||||
Update(Timestep);
|
Update(Timestep);
|
||||||
|
AudioDevice.WakeThread();
|
||||||
|
|
||||||
accumulatedUpdateTime -= Timestep;
|
accumulatedUpdateTime -= Timestep;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ namespace MoonWorks.Graphics
|
||||||
|
|
||||||
public bool IsDisposed { get; private set; }
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
private readonly List<WeakReference<GraphicsResource>> resources = new List<WeakReference<GraphicsResource>>();
|
private readonly HashSet<WeakReference<GraphicsResource>> resources = new HashSet<WeakReference<GraphicsResource>>();
|
||||||
|
|
||||||
public GraphicsDevice(
|
public GraphicsDevice(
|
||||||
Backend preferredBackend,
|
Backend preferredBackend,
|
||||||
|
@ -237,10 +237,9 @@ namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
lock (resources)
|
lock (resources)
|
||||||
{
|
{
|
||||||
for (var i = resources.Count - 1; i >= 0; i--)
|
foreach (var weakReference in resources)
|
||||||
{
|
{
|
||||||
var resource = resources[i];
|
if (weakReference.TryGetTarget(out var target))
|
||||||
if (resource.TryGetTarget(out var target))
|
|
||||||
{
|
{
|
||||||
target.Dispose();
|
target.Dispose();
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace MoonWorks.Graphics
|
||||||
public bool IsDisposed { get; private set; }
|
public bool IsDisposed { get; private set; }
|
||||||
protected abstract Action<IntPtr, IntPtr> QueueDestroyFunction { get; }
|
protected abstract Action<IntPtr, IntPtr> QueueDestroyFunction { get; }
|
||||||
|
|
||||||
private WeakReference<GraphicsResource> selfReference;
|
internal WeakReference<GraphicsResource> weakReference;
|
||||||
|
|
||||||
public GraphicsResource(GraphicsDevice device, bool trackResource = true)
|
public GraphicsResource(GraphicsDevice device, bool trackResource = true)
|
||||||
{
|
{
|
||||||
|
@ -18,8 +18,8 @@ namespace MoonWorks.Graphics
|
||||||
|
|
||||||
if (trackResource)
|
if (trackResource)
|
||||||
{
|
{
|
||||||
selfReference = new WeakReference<GraphicsResource>(this);
|
weakReference = new WeakReference<GraphicsResource>(this);
|
||||||
Device.AddResourceReference(selfReference);
|
Device.AddResourceReference(weakReference);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,11 +27,11 @@ namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
if (!IsDisposed)
|
if (!IsDisposed)
|
||||||
{
|
{
|
||||||
if (selfReference != null)
|
if (weakReference != null)
|
||||||
{
|
{
|
||||||
QueueDestroyFunction(Device.Handle, Handle);
|
QueueDestroyFunction(Device.Handle, Handle);
|
||||||
Device.RemoveResourceReference(selfReference);
|
Device.RemoveResourceReference(weakReference);
|
||||||
selfReference = null;
|
weakReference = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
IsDisposed = true;
|
IsDisposed = true;
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -7,6 +7,8 @@ namespace MoonWorks.Video
|
||||||
{
|
{
|
||||||
private IntPtr VideoHandle;
|
private IntPtr VideoHandle;
|
||||||
protected override int BUFFER_SIZE => 8192;
|
protected override int BUFFER_SIZE => 8192;
|
||||||
|
// Theorafile is not thread safe, so let's update on the main thread.
|
||||||
|
public override bool AutoUpdate => false;
|
||||||
|
|
||||||
internal StreamingSoundTheora(
|
internal StreamingSoundTheora(
|
||||||
AudioDevice device,
|
AudioDevice device,
|
||||||
|
@ -32,6 +34,11 @@ namespace MoonWorks.Video
|
||||||
) {
|
) {
|
||||||
var lengthInFloats = bufferLengthInBytes / sizeof(float);
|
var lengthInFloats = bufferLengthInBytes / sizeof(float);
|
||||||
|
|
||||||
|
// FIXME: this gets gnarly with theorafile being not thread safe
|
||||||
|
// is there some way we could just manually update in VideoPlayer
|
||||||
|
// instead of going through AudioDevice?
|
||||||
|
lock (Device.StateLock)
|
||||||
|
{
|
||||||
int samples = Theorafile.tf_readaudio(
|
int samples = Theorafile.tf_readaudio(
|
||||||
VideoHandle,
|
VideoHandle,
|
||||||
(IntPtr) buffer,
|
(IntPtr) buffer,
|
||||||
|
@ -42,4 +49,7 @@ namespace MoonWorks.Video
|
||||||
reachedEnd = Theorafile.tf_eos(VideoHandle) == 1;
|
reachedEnd = Theorafile.tf_eos(VideoHandle) == 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnReachedEnd() { }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ namespace MoonWorks.Video
|
||||||
private int yWidth;
|
private int yWidth;
|
||||||
private int yHeight;
|
private int yHeight;
|
||||||
|
|
||||||
private bool disposed;
|
private bool IsDisposed;
|
||||||
|
|
||||||
public Video(string filename)
|
public Video(string filename)
|
||||||
{
|
{
|
||||||
|
@ -89,7 +89,7 @@ namespace MoonWorks.Video
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (!disposed)
|
if (!IsDisposed)
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
|
@ -100,7 +100,7 @@ namespace MoonWorks.Video
|
||||||
Theorafile.tf_close(ref Handle);
|
Theorafile.tf_close(ref Handle);
|
||||||
NativeMemory.Free(videoData);
|
NativeMemory.Free(videoData);
|
||||||
|
|
||||||
disposed = true;
|
IsDisposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using MoonWorks.Audio;
|
using MoonWorks.Audio;
|
||||||
|
@ -130,6 +130,8 @@ namespace MoonWorks.Video
|
||||||
|
|
||||||
public void Play()
|
public void Play()
|
||||||
{
|
{
|
||||||
|
if (Video == null) { return; }
|
||||||
|
|
||||||
if (State == VideoState.Playing)
|
if (State == VideoState.Playing)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
@ -147,6 +149,8 @@ namespace MoonWorks.Video
|
||||||
|
|
||||||
public void Pause()
|
public void Pause()
|
||||||
{
|
{
|
||||||
|
if (Video == null) { return; }
|
||||||
|
|
||||||
if (State != VideoState.Playing)
|
if (State != VideoState.Playing)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
@ -164,6 +168,8 @@ namespace MoonWorks.Video
|
||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
|
if (Video == null) { return; }
|
||||||
|
|
||||||
if (State == VideoState.Stopped)
|
if (State == VideoState.Stopped)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
@ -172,20 +178,32 @@ namespace MoonWorks.Video
|
||||||
timer.Stop();
|
timer.Stop();
|
||||||
timer.Reset();
|
timer.Reset();
|
||||||
|
|
||||||
Theorafile.tf_reset(Video.Handle);
|
|
||||||
lastTimestamp = 0;
|
lastTimestamp = 0;
|
||||||
timeElapsed = 0;
|
timeElapsed = 0;
|
||||||
|
|
||||||
if (audioStream != null)
|
DestroyAudioStream();
|
||||||
{
|
|
||||||
audioStream.StopImmediate();
|
Theorafile.tf_reset(Video.Handle);
|
||||||
audioStream.Dispose();
|
|
||||||
audioStream = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
State = VideoState.Stopped;
|
State = VideoState.Stopped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Unload()
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
Video = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
if (Video == null) { return; }
|
||||||
|
|
||||||
|
if (audioStream != null)
|
||||||
|
{
|
||||||
|
audioStream.Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Render()
|
public void Render()
|
||||||
{
|
{
|
||||||
if (Video == null || State == VideoState.Stopped)
|
if (Video == null || State == VideoState.Stopped)
|
||||||
|
@ -203,7 +221,8 @@ namespace MoonWorks.Video
|
||||||
Video.Handle,
|
Video.Handle,
|
||||||
(IntPtr) yuvData,
|
(IntPtr) yuvData,
|
||||||
thisFrame - currentFrame
|
thisFrame - currentFrame
|
||||||
) == 1 || currentFrame == -1) {
|
) == 1 || currentFrame == -1)
|
||||||
|
{
|
||||||
UpdateRenderTexture();
|
UpdateRenderTexture();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,12 +235,7 @@ namespace MoonWorks.Video
|
||||||
timer.Stop();
|
timer.Stop();
|
||||||
timer.Reset();
|
timer.Reset();
|
||||||
|
|
||||||
if (audioStream != null)
|
DestroyAudioStream();
|
||||||
{
|
|
||||||
audioStream.Stop();
|
|
||||||
audioStream.Dispose();
|
|
||||||
audioStream = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Theorafile.tf_reset(Video.Handle);
|
Theorafile.tf_reset(Video.Handle);
|
||||||
|
|
||||||
|
@ -299,14 +313,27 @@ namespace MoonWorks.Video
|
||||||
// Grab the first bit of audio. We're trying to start the decoding ASAP.
|
// Grab the first bit of audio. We're trying to start the decoding ASAP.
|
||||||
if (AudioDevice != null && Theorafile.tf_hasaudio(Video.Handle) == 1)
|
if (AudioDevice != null && Theorafile.tf_hasaudio(Video.Handle) == 1)
|
||||||
{
|
{
|
||||||
|
DestroyAudioStream();
|
||||||
|
|
||||||
int channels, sampleRate;
|
int channels, sampleRate;
|
||||||
Theorafile.tf_audioinfo(Video.Handle, out channels, out sampleRate);
|
Theorafile.tf_audioinfo(Video.Handle, out channels, out sampleRate);
|
||||||
|
|
||||||
audioStream = new StreamingSoundTheora(AudioDevice, Video.Handle, channels, (uint) sampleRate);
|
audioStream = new StreamingSoundTheora(AudioDevice, Video.Handle, channels, (uint) sampleRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentFrame = -1;
|
currentFrame = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DestroyAudioStream()
|
||||||
|
{
|
||||||
|
if (audioStream != null)
|
||||||
|
{
|
||||||
|
audioStream.StopImmediate();
|
||||||
|
audioStream.Dispose();
|
||||||
|
audioStream = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (!disposed)
|
if (!disposed)
|
||||||
|
|
Loading…
Reference in New Issue