diff --git a/src/Audio/AudioBuffer.cs b/src/Audio/AudioBuffer.cs
index 6c79055c..78fe19d0 100644
--- a/src/Audio/AudioBuffer.cs
+++ b/src/Audio/AudioBuffer.cs
@@ -64,7 +64,7 @@ namespace MoonWorks.Audio
};
}
- protected override unsafe void Destroy()
+ protected override unsafe void DisposeUnmanagedState()
{
if (OwnsBufferData)
{
diff --git a/src/Audio/AudioDataOgg.cs b/src/Audio/AudioDataOgg.cs
index 3728cc8a..565ce49d 100644
--- a/src/Audio/AudioDataOgg.cs
+++ b/src/Audio/AudioDataOgg.cs
@@ -90,22 +90,15 @@ namespace MoonWorks.Audio
///
/// Unloads the Ogg data, freeing resources.
///
- public override unsafe void Unload()
+ public override void Unload()
{
- if (Loaded)
- {
- FAudio.stb_vorbis_close(VorbisHandle);
- NativeMemory.Free((void*) FileDataPtr);
-
- VorbisHandle = IntPtr.Zero;
- FileDataPtr = IntPtr.Zero;
- }
+ DisposeUnmanagedState();
}
///
/// Loads an entire ogg file into an AudioBuffer. Useful for static audio.
///
- public unsafe static AudioBuffer CreateBuffer(AudioDevice device, string filePath)
+ public static unsafe AudioBuffer CreateBuffer(AudioDevice device, string filePath)
{
var filePointer = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero);
@@ -143,5 +136,17 @@ namespace MoonWorks.Audio
(uint) lengthInBytes,
true);
}
+
+ protected override unsafe void DisposeUnmanagedState()
+ {
+ if (Loaded)
+ {
+ FAudio.stb_vorbis_close(VorbisHandle);
+ NativeMemory.Free((void*) FileDataPtr);
+
+ VorbisHandle = IntPtr.Zero;
+ FileDataPtr = IntPtr.Zero;
+ }
+ }
}
}
diff --git a/src/Audio/AudioDataQoa.cs b/src/Audio/AudioDataQoa.cs
index 2ee075fa..0adbe5ab 100644
--- a/src/Audio/AudioDataQoa.cs
+++ b/src/Audio/AudioDataQoa.cs
@@ -99,16 +99,9 @@ namespace MoonWorks.Audio
///
/// Unloads the qoa data, freeing resources.
///
- public override unsafe void Unload()
+ public override void Unload()
{
- if (Loaded)
- {
- FAudio.qoa_close(QoaHandle);
- NativeMemory.Free((void*) FileDataPtr);
-
- QoaHandle = IntPtr.Zero;
- FileDataPtr = IntPtr.Zero;
- }
+ DisposeUnmanagedState();
}
///
@@ -160,5 +153,17 @@ namespace MoonWorks.Audio
((UInt64)(bytes[4]) << 24) | ((UInt64)(bytes[5]) << 16) |
((UInt64)(bytes[6]) << 8) | ((UInt64)(bytes[7]) << 0);
}
+
+ protected override unsafe void DisposeUnmanagedState()
+ {
+ if (Loaded)
+ {
+ FAudio.qoa_close(QoaHandle);
+ NativeMemory.Free((void*) FileDataPtr);
+
+ QoaHandle = IntPtr.Zero;
+ FileDataPtr = IntPtr.Zero;
+ }
+ }
}
}
diff --git a/src/Audio/AudioDataStreamable.cs b/src/Audio/AudioDataStreamable.cs
index a0b9d605..7559bec3 100644
--- a/src/Audio/AudioDataStreamable.cs
+++ b/src/Audio/AudioDataStreamable.cs
@@ -36,10 +36,5 @@ namespace MoonWorks.Audio
/// How much data was actually filled in by the decode.
/// Whether the end of the data was reached on this decode.
public abstract unsafe void Decode(void* buffer, int bufferLengthInBytes, out int filledLengthInBytes, out bool reachedEnd);
-
- protected override void Destroy()
- {
- Unload();
- }
}
}
diff --git a/src/Audio/AudioDevice.cs b/src/Audio/AudioDevice.cs
index 30763a8f..dea4edc7 100644
--- a/src/Audio/AudioDevice.cs
+++ b/src/Audio/AudioDevice.cs
@@ -26,7 +26,7 @@ namespace MoonWorks.Audio
public float SpeedOfSound = 343.5f;
private readonly HashSet resources = new HashSet();
- private readonly HashSet activeSourceVoices = new HashSet();
+ private readonly HashSet updatingSourceVoices = new HashSet();
private AudioTweenManager AudioTweenManager;
@@ -42,7 +42,7 @@ namespace MoonWorks.Audio
internal readonly object StateLock = new object();
private bool Running;
- private bool IsDisposed;
+ public bool IsDisposed { get; private set; }
internal unsafe AudioDevice()
{
@@ -164,15 +164,19 @@ namespace MoonWorks.Audio
AudioTweenManager.Update(elapsedSeconds);
- foreach (var voice in activeSourceVoices)
+ foreach (var voice in updatingSourceVoices)
{
voice.Update();
}
foreach (var voice in VoicesToReturn)
{
+ if (voice is UpdatingSourceVoice updatingSourceVoice)
+ {
+ updatingSourceVoices.Remove(updatingSourceVoice);
+ }
+
voice.Reset();
- activeSourceVoices.Remove(voice);
VoicePool.Return(voice);
}
@@ -197,7 +201,12 @@ namespace MoonWorks.Audio
lock (StateLock)
{
var voice = VoicePool.Obtain(format);
- activeSourceVoices.Add(voice);
+
+ if (voice is UpdatingSourceVoice updatingSourceVoice)
+ {
+ updatingSourceVoices.Add(updatingSourceVoice);
+ }
+
return voice;
}
}
@@ -258,9 +267,9 @@ namespace MoonWorks.Audio
{
resources.Add(resourceReference);
- if (resourceReference.Target is SourceVoice voice)
+ if (resourceReference.Target is UpdatingSourceVoice updatableVoice)
{
- activeSourceVoices.Add(voice);
+ updatingSourceVoices.Add(updatableVoice);
}
}
}
diff --git a/src/Audio/AudioEmitter.cs b/src/Audio/AudioEmitter.cs
index 1a9c9833..8b3fbf5e 100644
--- a/src/Audio/AudioEmitter.cs
+++ b/src/Audio/AudioEmitter.cs
@@ -132,7 +132,5 @@ namespace MoonWorks.Audio
emitterData.pReverbCurve = IntPtr.Zero;
emitterData.CurveDistanceScaler = 1.0f;
}
-
- protected override void Destroy() { }
}
}
diff --git a/src/Audio/AudioListener.cs b/src/Audio/AudioListener.cs
index e1217909..7557485e 100644
--- a/src/Audio/AudioListener.cs
+++ b/src/Audio/AudioListener.cs
@@ -94,7 +94,5 @@ namespace MoonWorks.Audio
/* Unexposed variables, defaults based on XNA behavior */
listenerData.pCone = IntPtr.Zero;
}
-
- protected override void Destroy() { }
}
}
diff --git a/src/Audio/AudioResource.cs b/src/Audio/AudioResource.cs
index 4c8d4e92..3f6b4d0e 100644
--- a/src/Audio/AudioResource.cs
+++ b/src/Audio/AudioResource.cs
@@ -19,16 +19,22 @@ namespace MoonWorks.Audio
Device.AddResourceReference(SelfReference);
}
- protected abstract void Destroy();
+ protected virtual void DisposeManagedState() { }
+ protected virtual void DisposeUnmanagedState() { }
protected void Dispose(bool disposing)
{
if (!IsDisposed)
{
- Destroy();
+ if (disposing)
+ {
+ DisposeManagedState();
- Device.RemoveResourceReference(SelfReference);
- SelfReference.Free();
+ Device.RemoveResourceReference(SelfReference);
+ SelfReference.Free();
+ }
+
+ DisposeUnmanagedState();
IsDisposed = true;
}
@@ -36,6 +42,17 @@ namespace MoonWorks.Audio
~AudioResource()
{
+ #if DEBUG
+ // If the graphics device associated with this resource was already disposed, we assume
+ // that your game is in the middle of shutting down.
+ if (!IsDisposed && Device != null && !Device.IsDisposed)
+ {
+ // If you see this log message, you leaked a graphics resource without disposing it!
+ // This means your game may eventually run out of native memory for mysterious reasons.
+ Logger.LogWarn($"A resource of type {GetType().Name} was not Disposed.");
+ }
+ #endif
+
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false);
}
diff --git a/src/Audio/SoundSequence.cs b/src/Audio/SoundSequence.cs
index 9d5b1631..dacacedf 100644
--- a/src/Audio/SoundSequence.cs
+++ b/src/Audio/SoundSequence.cs
@@ -3,7 +3,7 @@ namespace MoonWorks.Audio
///
/// Plays back a series of AudioBuffers in sequence. Set the OnSoundNeeded callback to add AudioBuffers dynamically.
///
- public class SoundSequence : SourceVoice, IPoolable
+ public class SoundSequence : UpdatingSourceVoice, IPoolable
{
public int NeedSoundThreshold = 0;
public delegate void OnSoundNeededFunc();
diff --git a/src/Audio/SourceVoice.cs b/src/Audio/SourceVoice.cs
index 5e4efeca..d1a86268 100644
--- a/src/Audio/SourceVoice.cs
+++ b/src/Audio/SourceVoice.cs
@@ -191,12 +191,6 @@ namespace MoonWorks.Audio
Device.Return(this);
}
- ///
- /// Called automatically by AudioDevice in the audio thread.
- /// Don't call this yourself! You might regret it!
- ///
- public virtual void Update() { }
-
///
/// Adds an FAudio buffer to the voice queue.
/// The voice processes and plays back the buffers in its queue in the order that they were submitted.
@@ -221,10 +215,9 @@ namespace MoonWorks.Audio
base.Reset();
}
- protected override unsafe void Destroy()
+ protected override void DisposeManagedState()
{
Stop();
- base.Destroy();
}
}
}
diff --git a/src/Audio/StreamingVoice.cs b/src/Audio/StreamingVoice.cs
index 24623a1e..2e8b061b 100644
--- a/src/Audio/StreamingVoice.cs
+++ b/src/Audio/StreamingVoice.cs
@@ -6,7 +6,7 @@ namespace MoonWorks.Audio
///
/// Use in conjunction with an AudioDataStreamable object to play back streaming audio data.
///
- public class StreamingVoice : SourceVoice, IPoolable
+ public class StreamingVoice : UpdatingSourceVoice, IPoolable
{
private const int BUFFER_COUNT = 3;
private readonly IntPtr[] buffers;
diff --git a/src/Audio/TransientVoice.cs b/src/Audio/TransientVoice.cs
index 0b6e499c..550ce1b8 100644
--- a/src/Audio/TransientVoice.cs
+++ b/src/Audio/TransientVoice.cs
@@ -4,7 +4,7 @@ namespace MoonWorks.Audio
/// TransientVoice is intended for playing one-off sound effects that don't have a long term reference.
/// It will be automatically returned to the AudioDevice SourceVoice pool once it is done playing back.
///
- public class TransientVoice : SourceVoice, IPoolable
+ public class TransientVoice : UpdatingSourceVoice, IPoolable
{
static TransientVoice IPoolable.Create(AudioDevice device, Format format)
{
diff --git a/src/Audio/UpdatingSourceVoice.cs b/src/Audio/UpdatingSourceVoice.cs
new file mode 100644
index 00000000..4c68737f
--- /dev/null
+++ b/src/Audio/UpdatingSourceVoice.cs
@@ -0,0 +1,11 @@
+namespace MoonWorks.Audio
+{
+ public abstract class UpdatingSourceVoice : SourceVoice
+ {
+ protected UpdatingSourceVoice(AudioDevice device, Format format) : base(device, format)
+ {
+ }
+
+ public abstract void Update();
+ }
+}
diff --git a/src/Audio/Voice.cs b/src/Audio/Voice.cs
index d982f1bc..d1739c92 100644
--- a/src/Audio/Voice.cs
+++ b/src/Audio/Voice.cs
@@ -565,7 +565,7 @@ namespace MoonWorks.Audio
);
}
- protected unsafe override void Destroy()
+ protected override void DisposeUnmanagedState()
{
NativeMemory.Free(pMatrixCoefficients);
FAudio.FAudioVoice_DestroyVoice(Handle);