diff --git a/src/Audio/AudioDevice.cs b/src/Audio/AudioDevice.cs
index e5a86b0..9ffbdf9 100644
--- a/src/Audio/AudioDevice.cs
+++ b/src/Audio/AudioDevice.cs
@@ -9,24 +9,15 @@ namespace MoonWorks.Audio
 	{
 		public IntPtr Handle { get; }
 		public byte[] Handle3D { get; }
-		public IntPtr MasteringVoice { get; }
 		public FAudio.FAudioDeviceDetails DeviceDetails { get; }
 
+		private MasteringVoice masteringVoice;
+		public MasteringVoice MasteringVoice => masteringVoice;
+
 		public float CurveDistanceScalar = 1f;
 		public float DopplerScale = 1f;
 		public float SpeedOfSound = 343.5f;
 
-		private float masteringVolume = 1f;
-		public float MasteringVolume
-		{
-			get => masteringVolume;
-			set
-			{
-				masteringVolume = value;
-				FAudio.FAudioVoice_SetVolume(MasteringVoice, masteringVolume, 0);
-			}
-		}
-
 		private readonly HashSet<WeakReference> resources = new HashSet<WeakReference>();
 		private readonly List<StreamingSound> autoUpdateStreamingSoundReferences = new List<StreamingSound>();
 		private readonly List<StaticSoundInstance> autoFreeStaticSoundInstanceReferences = new List<StaticSoundInstance>();
@@ -93,26 +84,18 @@ namespace MoonWorks.Audio
 			}
 
 			/* Init Mastering Voice */
-			IntPtr masteringVoice;
-
-			if (FAudio.FAudio_CreateMasteringVoice(
-				Handle,
-				out masteringVoice,
-				FAudio.FAUDIO_DEFAULT_CHANNELS,
-				FAudio.FAUDIO_DEFAULT_SAMPLERATE,
-				0,
+			var result = MasteringVoice.Create(
+				this,
 				i,
-				IntPtr.Zero
-			) != 0)
+				out masteringVoice
+			);
+
+			if (!result)
 			{
-				Logger.LogError("No mastering voice found!");
-				FAudio.FAudio_Release(Handle);
-				Handle = IntPtr.Zero;
+				Logger.LogError("Audio device creation failed!");
 				return;
 			}
 
-			MasteringVoice = masteringVoice;
-
 			/* Init 3D Audio */
 
 			Handle3D = new byte[FAudio.F3DAUDIO_HANDLE_BYTESIZE];
@@ -192,7 +175,7 @@ namespace MoonWorks.Audio
 			{
 				if (soundSequenceReferences[i].TryGetTarget(out var soundSequence))
 				{
-					soundSequence.Update();
+					soundSequence.OnUpdate();
 				}
 				else
 				{
@@ -203,13 +186,13 @@ namespace MoonWorks.Audio
 			AudioTweenManager.Update(elapsedSeconds);
 		}
 
-		public void SyncPlay()
+		public void TriggerSyncGroup(uint syncGroup)
 		{
-			FAudio.FAudio_CommitChanges(Handle, 1);
+			FAudio.FAudio_CommitChanges(Handle, syncGroup);
 		}
 
 		internal void CreateTween(
-			SoundInstance soundInstance,
+			Voice voice,
 			AudioTweenProperty property,
 			System.Func<float, float> easingFunction,
 			float start,
@@ -220,7 +203,7 @@ namespace MoonWorks.Audio
 			lock (StateLock)
 			{
 				AudioTweenManager.CreateTween(
-					soundInstance,
+					voice,
 					property,
 					easingFunction,
 					start,
@@ -232,12 +215,12 @@ namespace MoonWorks.Audio
 		}
 
 		internal void ClearTweens(
-			SoundInstance soundReference,
+			Voice voice,
 			AudioTweenProperty property
 		) {
 			lock (StateLock)
 			{
-				AudioTweenManager.ClearTweens(soundReference, property);
+				AudioTweenManager.ClearTweens(voice, property);
 			}
 		}
 
@@ -286,6 +269,18 @@ namespace MoonWorks.Audio
 
 				if (disposing)
 				{
+					// stop all source voices
+					foreach (var weakReference in resources)
+					{
+						var target = weakReference.Target;
+
+						if (target != null && target is SourceVoice voice)
+						{
+							voice.Stop();
+						}
+					}
+
+					// destroy all audio resources
 					foreach (var weakReference in resources)
 					{
 						var target = weakReference.Target;
@@ -295,10 +290,10 @@ namespace MoonWorks.Audio
 							(target as IDisposable).Dispose();
 						}
 					}
+
 					resources.Clear();
 				}
 
-				FAudio.FAudioVoice_DestroyVoice(MasteringVoice);
 				FAudio.FAudio_Release(Handle);
 
 				IsDisposed = true;
diff --git a/src/Audio/AudioTween.cs b/src/Audio/AudioTween.cs
index dc71fef..75b2e9d 100644
--- a/src/Audio/AudioTween.cs
+++ b/src/Audio/AudioTween.cs
@@ -14,7 +14,7 @@ namespace MoonWorks.Audio
 
 	internal class AudioTween
 	{
-		public SoundInstance SoundInstance;
+		public Voice Voice;
 		public AudioTweenProperty Property;
 		public EasingFunction EasingFunction;
 		public float Time;
@@ -51,7 +51,7 @@ namespace MoonWorks.Audio
 
 		public void Free(AudioTween tween)
 		{
-			tween.SoundInstance = null;
+			tween.Voice = null;
 			Tweens.Enqueue(tween);
 		}
 	}
diff --git a/src/Audio/AudioTweenManager.cs b/src/Audio/AudioTweenManager.cs
index f98adcc..d5b05ee 100644
--- a/src/Audio/AudioTweenManager.cs
+++ b/src/Audio/AudioTweenManager.cs
@@ -6,7 +6,7 @@ namespace MoonWorks.Audio
 	internal class AudioTweenManager
 	{
 		private AudioTweenPool AudioTweenPool = new AudioTweenPool();
-		private readonly Dictionary<(SoundInstance, AudioTweenProperty), AudioTween> AudioTweens = new Dictionary<(SoundInstance, AudioTweenProperty), AudioTween>();
+		private readonly Dictionary<(Voice, AudioTweenProperty), AudioTween> AudioTweens = new Dictionary<(Voice, AudioTweenProperty), AudioTween>();
 		private readonly List<AudioTween> DelayedAudioTweens = new List<AudioTween>();
 
 		public void Update(float elapsedSeconds)
@@ -14,7 +14,7 @@ namespace MoonWorks.Audio
 			for (var i = DelayedAudioTweens.Count - 1; i >= 0; i--)
 			{
 				var audioTween = DelayedAudioTweens[i];
-				var soundInstance = audioTween.SoundInstance;
+				var voice = audioTween.Voice;
 
 				audioTween.Time += elapsedSeconds;
 
@@ -24,23 +24,23 @@ namespace MoonWorks.Audio
 					switch (audioTween.Property)
 					{
 						case AudioTweenProperty.Pan:
-							audioTween.StartValue = soundInstance.Pan;
+							audioTween.StartValue = voice.Pan;
 							break;
 
 						case AudioTweenProperty.Pitch:
-							audioTween.StartValue = soundInstance.Pitch;
+							audioTween.StartValue = voice.Pitch;
 							break;
 
 						case AudioTweenProperty.Volume:
-							audioTween.StartValue = soundInstance.Volume;
+							audioTween.StartValue = voice.Volume;
 							break;
 
 						case AudioTweenProperty.FilterFrequency:
-							audioTween.StartValue = soundInstance.FilterFrequency;
+							audioTween.StartValue = voice.FilterFrequency;
 							break;
 
 						case AudioTweenProperty.Reverb:
-							audioTween.StartValue = soundInstance.Reverb;
+							audioTween.StartValue = voice.Reverb;
 							break;
 					}
 
@@ -64,7 +64,7 @@ namespace MoonWorks.Audio
 		}
 
 		public void CreateTween(
-			SoundInstance soundInstance,
+			Voice voice,
 			AudioTweenProperty property,
 			System.Func<float, float> easingFunction,
 			float start,
@@ -73,7 +73,7 @@ namespace MoonWorks.Audio
 			float delayTime
 		) {
 			var tween = AudioTweenPool.Obtain();
-			tween.SoundInstance = soundInstance;
+			tween.Voice = voice;
 			tween.Property = property;
 			tween.EasingFunction = easingFunction;
 			tween.StartValue = start;
@@ -92,21 +92,21 @@ namespace MoonWorks.Audio
 			}
 		}
 
-		public void ClearTweens(SoundInstance soundInstance, AudioTweenProperty property)
+		public void ClearTweens(Voice voice, AudioTweenProperty property)
 		{
-			AudioTweens.Remove((soundInstance, property));
+			AudioTweens.Remove((voice, 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.SoundInstance, audioTween.Property), out var currentTween))
+			if (AudioTweens.TryGetValue((audioTween.Voice, audioTween.Property), out var currentTween))
 			{
 				AudioTweenPool.Free(currentTween);
 			}
 
-			AudioTweens[(audioTween.SoundInstance, audioTween.Property)] = audioTween;
+			AudioTweens[(audioTween.Voice, audioTween.Property)] = audioTween;
 		}
 
 		private static bool UpdateAudioTween(AudioTween audioTween, float delta)
@@ -133,23 +133,23 @@ namespace MoonWorks.Audio
 			switch (audioTween.Property)
 			{
 				case AudioTweenProperty.Pan:
-					audioTween.SoundInstance.Pan = value;
+					audioTween.Voice.Pan = value;
 					break;
 
 				case AudioTweenProperty.Pitch:
-					audioTween.SoundInstance.Pitch = value;
+					audioTween.Voice.Pitch = value;
 					break;
 
 				case AudioTweenProperty.Volume:
-					audioTween.SoundInstance.Volume = value;
+					audioTween.Voice.Volume = value;
 					break;
 
 				case AudioTweenProperty.FilterFrequency:
-					audioTween.SoundInstance.FilterFrequency = value;
+					audioTween.Voice.FilterFrequency = value;
 					break;
 
 				case AudioTweenProperty.Reverb:
-					audioTween.SoundInstance.Reverb = value;
+					audioTween.Voice.Reverb = value;
 					break;
 			}
 
diff --git a/src/Audio/MasteringVoice.cs b/src/Audio/MasteringVoice.cs
new file mode 100644
index 0000000..78fe624
--- /dev/null
+++ b/src/Audio/MasteringVoice.cs
@@ -0,0 +1,46 @@
+using System;
+
+namespace MoonWorks.Audio
+{
+	public class MasteringVoice : Voice
+	{
+		// mastering voice can't pan
+		public override float Pan => 0;
+
+		internal static bool Create(
+			AudioDevice device,
+			uint deviceIndex,
+			out MasteringVoice masteringVoice
+		) {
+			var result = FAudio.FAudio_CreateMasteringVoice(
+				device.Handle,
+				out var handle,
+				FAudio.FAUDIO_DEFAULT_CHANNELS,
+				FAudio.FAUDIO_DEFAULT_SAMPLERATE,
+				0,
+				deviceIndex,
+				IntPtr.Zero
+			);
+
+			if (result == 0)
+			{
+				masteringVoice = new MasteringVoice(device, handle);
+			}
+			else
+			{
+				Logger.LogError("Failed to create mastering voice!");
+				masteringVoice = null;
+			}
+
+			return result == 0;
+		}
+
+		internal MasteringVoice(
+			AudioDevice device,
+			IntPtr handle
+		) : base(device)
+		{
+			Handle = handle;
+		}
+	}
+}
diff --git a/src/Audio/ReverbEffect.cs b/src/Audio/ReverbEffect.cs
index 42fab2c..eac7cda 100644
--- a/src/Audio/ReverbEffect.cs
+++ b/src/Audio/ReverbEffect.cs
@@ -4,53 +4,31 @@ using System.Runtime.InteropServices;
 namespace MoonWorks.Audio
 {
 	// sound instances can send their audio to this voice to add reverb
-	public unsafe class ReverbEffect : AudioResource
+	public unsafe class ReverbEffect : SubmixVoice
 	{
-		private IntPtr voice;
-		public IntPtr Voice => voice;
-
-		public ReverbEffect(AudioDevice audioDevice) : base(audioDevice)
+		public ReverbEffect(AudioDevice audioDevice) : base(audioDevice, 1, audioDevice.DeviceDetails.OutputFormat.Format.nSamplesPerSec)
 		{
 			/* Init reverb */
-
 			IntPtr reverb;
 			FAudio.FAudioCreateReverb(out reverb, 0);
 
-			IntPtr chainPtr;
-			chainPtr = (nint) NativeMemory.Alloc(
-				(nuint) Marshal.SizeOf<FAudio.FAudioEffectChain>()
+			var chain = new FAudio.FAudioEffectChain();
+			var descriptor = new FAudio.FAudioEffectDescriptor();
+
+			descriptor.InitialState = 1;
+			descriptor.OutputChannels = Device.DeviceDetails.OutputFormat.Format.nChannels;
+			descriptor.pEffect = reverb;
+
+			chain.EffectCount = 1;
+			chain.pEffectDescriptors = (nint) (&descriptor);
+
+			FAudio.FAudioVoice_SetEffectChain(
+				Handle,
+				ref chain
 			);
 
-			FAudio.FAudioEffectChain* reverbChain = (FAudio.FAudioEffectChain*) chainPtr;
-			reverbChain->EffectCount = 1;
-			reverbChain->pEffectDescriptors = (nint) NativeMemory.Alloc(
-				(nuint) Marshal.SizeOf<FAudio.FAudioEffectDescriptor>()
-			);
-
-			FAudio.FAudioEffectDescriptor* reverbDescriptor =
-				(FAudio.FAudioEffectDescriptor*) reverbChain->pEffectDescriptors;
-
-			reverbDescriptor->InitialState = 1;
-			reverbDescriptor->OutputChannels = (uint) (
-				(audioDevice.DeviceDetails.OutputFormat.Format.nChannels == 6) ? 6 : 1
-			);
-			reverbDescriptor->pEffect = reverb;
-
-			FAudio.FAudio_CreateSubmixVoice(
-				audioDevice.Handle,
-				out voice,
-				1, /* omnidirectional reverb */
-				audioDevice.DeviceDetails.OutputFormat.Format.nSamplesPerSec,
-				0,
-				0,
-				IntPtr.Zero,
-				chainPtr
-			);
 			FAudio.FAPOBase_Release(reverb);
 
-			NativeMemory.Free((void*) reverbChain->pEffectDescriptors);
-			NativeMemory.Free((void*) chainPtr);
-
 			/* Init reverb params */
 			// Defaults based on FAUDIOFX_I3DL2_PRESET_GENERIC
 
@@ -86,7 +64,7 @@ namespace MoonWorks.Audio
 			fixed (FAudio.FAudioFXReverbParameters* reverbParamsPtr = &reverbParams)
 			{
 				FAudio.FAudioVoice_SetEffectParameters(
-					voice,
+					Handle,
 					0,
 					(nint) reverbParamsPtr,
 					(uint) Marshal.SizeOf<FAudio.FAudioFXReverbParameters>(),
@@ -94,10 +72,5 @@ namespace MoonWorks.Audio
 				);
 			}
 		}
-
-		protected override void Destroy()
-		{
-			FAudio.FAudioVoice_DestroyVoice(Voice);
-		}
 	}
 }
diff --git a/src/Audio/SoundQueue.cs b/src/Audio/SoundSequence.cs
similarity index 61%
rename from src/Audio/SoundQueue.cs
rename to src/Audio/SoundSequence.cs
index d098ecf..6b8c554 100644
--- a/src/Audio/SoundQueue.cs
+++ b/src/Audio/SoundSequence.cs
@@ -3,25 +3,25 @@ using System;
 namespace MoonWorks.Audio
 {
 	// NOTE: all sounds played with a SoundSequence must have the same audio format!
-	public class SoundSequence : SoundInstance
+	public class SoundSequence : SourceVoice
 	{
 		public int NeedSoundThreshold = 0;
 		public delegate void OnSoundNeededFunc();
 		public OnSoundNeededFunc OnSoundNeeded;
 
-		private object StateLock = new object();
-
 		public SoundSequence(AudioDevice device, ushort formatTag, ushort bitsPerSample, ushort blockAlign, ushort channels, uint samplesPerSecond) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond)
 		{
 			device.AddSoundSequenceReference(this);
+			OnUpdate += Update;
 		}
 
 		public SoundSequence(AudioDevice device, StaticSound templateSound) : base(device, templateSound.FormatTag, templateSound.BitsPerSample, templateSound.BlockAlign, templateSound.Channels, templateSound.SamplesPerSecond)
 		{
 			device.AddSoundSequenceReference(this);
+			OnUpdate += Update;
 		}
 
-		public void Update()
+		private void Update()
 		{
 			lock (StateLock)
 			{
@@ -31,7 +31,7 @@ namespace MoonWorks.Audio
 				if (NeedSoundThreshold > 0)
 				{
 					FAudio.FAudioSourceVoice_GetState(
-						Voice,
+						Handle,
 						out var state,
 						FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED
 					);
@@ -65,66 +65,11 @@ namespace MoonWorks.Audio
 			lock (StateLock)
 			{
 				FAudio.FAudioSourceVoice_SubmitSourceBuffer(
-					Voice,
+					Handle,
 					ref sound.Handle,
 					IntPtr.Zero
 				);
 			}
 		}
-
-		public override void Pause()
-		{
-			lock (StateLock)
-			{
-				if (State == SoundState.Playing)
-				{
-					FAudio.FAudioSourceVoice_Stop(Voice, 0, 0);
-					State = SoundState.Paused;
-				}
-			}
-		}
-
-		public override void Play()
-		{
-			PlayUsingOperationSet(0);
-		}
-
-		public override void QueueSyncPlay()
-		{
-			PlayUsingOperationSet(1);
-		}
-
-		private void PlayUsingOperationSet(uint operationSet)
-		{
-			lock (StateLock)
-			{
-				if (State == SoundState.Playing)
-				{
-					return;
-				}
-
-				FAudio.FAudioSourceVoice_Start(Voice, 0, operationSet);
-				State = SoundState.Playing;
-			}
-		}
-
-		public override void Stop()
-		{
-			lock (StateLock)
-			{
-				FAudio.FAudioSourceVoice_ExitLoop(Voice, 0);
-				State = SoundState.Stopped;
-			}
-		}
-
-		public override void StopImmediate()
-		{
-			lock (StateLock)
-			{
-				FAudio.FAudioSourceVoice_Stop(Voice, 0, 0);
-				FAudio.FAudioSourceVoice_FlushSourceBuffers(Voice);
-				State = SoundState.Stopped;
-			}
-		}
 	}
 }
diff --git a/src/Audio/SourceVoice.cs b/src/Audio/SourceVoice.cs
new file mode 100644
index 0000000..55c1918
--- /dev/null
+++ b/src/Audio/SourceVoice.cs
@@ -0,0 +1,123 @@
+using System;
+
+namespace MoonWorks.Audio
+{
+	public class SourceVoice : Voice
+	{
+		protected FAudio.FAudioWaveFormatEx Format;
+
+		protected object StateLock = new object();
+
+		public uint BuffersQueued
+		{
+			get
+			{
+				FAudio.FAudioSourceVoice_GetState(
+					Handle,
+					out var state,
+					FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED
+				);
+
+				return state.BuffersQueued;
+			}
+		}
+
+		private SoundState state;
+		public SoundState State
+		{
+			get
+			{
+				if (BuffersQueued == 0)
+				{
+					Stop();
+				}
+
+				return state;
+			}
+
+			internal set
+			{
+				state = value;
+			}
+		}
+
+		public delegate void OnUpdateFunc();
+		public OnUpdateFunc OnUpdate; // called by AudioDevice thread
+
+		public SourceVoice(
+			AudioDevice device,
+			ushort formatTag,
+			ushort bitsPerSample,
+			ushort blockAlign,
+			ushort channels,
+			uint samplesPerSecond
+		) : base(device)
+		{
+			Format = new FAudio.FAudioWaveFormatEx
+			{
+				wFormatTag = formatTag,
+				wBitsPerSample = bitsPerSample,
+				nChannels = channels,
+				nBlockAlign = blockAlign,
+				nSamplesPerSec = samplesPerSecond,
+				nAvgBytesPerSec = blockAlign * samplesPerSecond
+			};
+
+			 FAudio.FAudio_CreateSourceVoice(
+				device.Handle,
+				out var Handle,
+				ref Format,
+				FAudio.FAUDIO_VOICE_USEFILTER,
+				FAudio.FAUDIO_DEFAULT_FREQ_RATIO,
+				IntPtr.Zero,
+				IntPtr.Zero, // default sends to mastering voice!
+				IntPtr.Zero
+			);
+		}
+
+		public void Play(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
+		{
+			lock (StateLock)
+			{
+				FAudio.FAudioSourceVoice_Start(Handle, 0, syncGroup);
+
+				State = SoundState.Playing;
+			}
+		}
+
+		public void Pause(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
+		{
+			lock (StateLock)
+			{
+				FAudio.FAudioSourceVoice_Stop(Handle, 0, syncGroup);
+
+				State = SoundState.Paused;
+			}
+		}
+
+		public void ExitLoop(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
+		{
+			lock (StateLock)
+			{
+				FAudio.FAudioSourceVoice_ExitLoop(Handle, syncGroup);
+			}
+		}
+
+		public void Stop(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
+		{
+			lock (StateLock)
+			{
+				FAudio.FAudioSourceVoice_Stop(Handle, 0, syncGroup);
+				FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle);
+
+				State = SoundState.Stopped;
+			}
+		}
+
+		protected override unsafe void Destroy()
+		{
+			Stop();
+			base.Destroy();
+		}
+	}
+}
diff --git a/src/Audio/StaticSourceVoice.cs b/src/Audio/StaticSourceVoice.cs
new file mode 100644
index 0000000..f4f6a7b
--- /dev/null
+++ b/src/Audio/StaticSourceVoice.cs
@@ -0,0 +1,11 @@
+namespace MoonWorks.Audio
+{
+	public class StaticSourceVoice : SourceVoice
+	{
+		public StaticSourceVoice(AudioDevice device, ushort formatTag, ushort bitsPerSample, ushort blockAlign, ushort channels, uint samplesPerSecond) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond)
+		{
+		}
+
+
+	}
+}
diff --git a/src/Audio/SubmixVoice.cs b/src/Audio/SubmixVoice.cs
new file mode 100644
index 0000000..02ad4ad
--- /dev/null
+++ b/src/Audio/SubmixVoice.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace MoonWorks.Audio
+{
+	public class SubmixVoice : Voice
+	{
+		public SubmixVoice(
+			AudioDevice device,
+			uint inputChannels,
+			uint sampleRate
+		) : base(device)
+		{
+			FAudio.FAudio_CreateSubmixVoice(
+				device.Handle,
+				out Handle,
+				inputChannels,
+				sampleRate,
+				0,
+				0,
+				IntPtr.Zero,
+				IntPtr.Zero
+			);
+		}
+	}
+}
diff --git a/src/Audio/Voice.cs b/src/Audio/Voice.cs
new file mode 100644
index 0000000..14ee3a2
--- /dev/null
+++ b/src/Audio/Voice.cs
@@ -0,0 +1,446 @@
+using System;
+using System.Runtime.InteropServices;
+using EasingFunction = System.Func<float, float>;
+
+namespace MoonWorks.Audio
+{
+	public abstract class Voice : AudioResource
+	{
+		protected IntPtr Handle;
+
+		protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings;
+
+		private ReverbEffect ReverbEffect;
+
+		public bool Is3D { get; protected set; }
+
+		private float pan = 0;
+		public virtual float Pan
+		{
+			get => pan;
+			internal set
+			{
+				value = Math.MathHelper.Clamp(value, -1f, 1f);
+				if (pan != value)
+				{
+					pan = value;
+
+					if (pan < -1f)
+					{
+						pan = -1f;
+					}
+					if (pan > 1f)
+					{
+						pan = 1f;
+					}
+
+					if (Is3D) { return; }
+
+					SetPanMatrixCoefficients();
+					FAudio.FAudioVoice_SetOutputMatrix(
+						Handle,
+						Device.MasteringVoice.Handle,
+						dspSettings.SrcChannelCount,
+						dspSettings.DstChannelCount,
+						dspSettings.pMatrixCoefficients,
+						0
+					);
+				}
+			}
+		}
+
+		private float pitch = 0;
+		public float Pitch
+		{
+			get => pitch;
+			internal set
+			{
+				value = Math.MathHelper.Clamp(value, -1f, 1f);
+				if (pitch != value)
+				{
+					pitch = value;
+					UpdatePitch();
+				}
+			}
+		}
+
+		private float volume = 1;
+		public float Volume
+		{
+			get => volume;
+			internal set
+			{
+				value = Math.MathHelper.Max(0, value);
+				if (volume != value)
+				{
+					volume = value;
+					FAudio.FAudioVoice_SetVolume(Handle, volume, 0);
+				}
+			}
+		}
+
+		private const float MAX_FILTER_FREQUENCY = 1f;
+		private const float MAX_FILTER_ONEOVERQ = 1.5f;
+
+		private FAudio.FAudioFilterParameters filterParameters = new FAudio.FAudioFilterParameters
+		{
+			Type = FAudio.FAudioFilterType.FAudioLowPassFilter,
+			Frequency = 1f,
+			OneOverQ = 1f
+		};
+
+		public float FilterFrequency
+		{
+			get => filterParameters.Frequency;
+			internal set
+			{
+				value = System.Math.Clamp(value, 0.01f, MAX_FILTER_FREQUENCY);
+				if (filterParameters.Frequency != value)
+				{
+					filterParameters.Frequency = value;
+
+					FAudio.FAudioVoice_SetFilterParameters(
+						Handle,
+						ref filterParameters,
+						0
+					);
+				}
+			}
+		}
+
+		public float FilterOneOverQ
+		{
+			get => filterParameters.OneOverQ;
+			internal set
+			{
+				value = System.Math.Clamp(value, 0.01f, MAX_FILTER_ONEOVERQ);
+				if (filterParameters.OneOverQ != value)
+				{
+					filterParameters.OneOverQ = value;
+
+					FAudio.FAudioVoice_SetFilterParameters(
+						Handle,
+						ref filterParameters,
+						0
+					);
+				}
+			}
+		}
+
+		private FilterType filterType;
+		public FilterType FilterType
+		{
+			get => filterType;
+			set
+			{
+				if (filterType != value)
+				{
+					filterType = value;
+
+					switch (filterType)
+					{
+						case FilterType.None:
+							filterParameters = new FAudio.FAudioFilterParameters
+							{
+								Type = FAudio.FAudioFilterType.FAudioLowPassFilter,
+								Frequency = 1f,
+								OneOverQ = 1f
+							};
+							break;
+
+						case FilterType.LowPass:
+							filterParameters.Type = FAudio.FAudioFilterType.FAudioLowPassFilter;
+							filterParameters.Frequency = 1f;
+							break;
+
+						case FilterType.BandPass:
+							filterParameters.Type = FAudio.FAudioFilterType.FAudioBandPassFilter;
+							break;
+
+						case FilterType.HighPass:
+							filterParameters.Type = FAudio.FAudioFilterType.FAudioHighPassFilter;
+							filterParameters.Frequency = 0f;
+							break;
+					}
+
+					FAudio.FAudioVoice_SetFilterParameters(
+						Handle,
+						ref filterParameters,
+						0
+					);
+				}
+			}
+		}
+
+		private float reverb;
+		public unsafe float Reverb
+		{
+			get => reverb;
+			internal set
+			{
+				if (ReverbEffect != null)
+				{
+					value = MathF.Max(0, value);
+					if (reverb != value)
+					{
+						reverb = value;
+
+						float* outputMatrix = (float*) dspSettings.pMatrixCoefficients;
+						outputMatrix[0] = reverb;
+						if (dspSettings.SrcChannelCount == 2)
+						{
+							outputMatrix[1] = reverb;
+						}
+
+						FAudio.FAudioVoice_SetOutputMatrix(
+							Handle,
+							ReverbEffect.Handle,
+							dspSettings.SrcChannelCount,
+							1,
+							dspSettings.pMatrixCoefficients,
+							0
+						);
+					}
+				}
+
+				#if DEBUG
+				if (ReverbEffect == null)
+				{
+					Logger.LogWarn("Tried to set reverb value before applying a reverb effect");
+				}
+				#endif
+			}
+		}
+
+		public Voice(AudioDevice device) : base(device) { }
+
+		public void SetPan(float targetValue)
+		{
+			Pan = targetValue;
+			Device.ClearTweens(this, 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(this, AudioTweenProperty.Pitch);
+		}
+
+		public void SetPitch(float targetValue, float duration, EasingFunction easingFunction)
+		{
+			Device.CreateTween(this, AudioTweenProperty.Pitch, easingFunction, Pitch, targetValue, duration, 0);
+		}
+
+		public void SetPitch(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
+		{
+			Device.CreateTween(this, AudioTweenProperty.Pitch, easingFunction, Pitch, targetValue, duration, delayTime);
+		}
+
+		public void SetVolume(float targetValue)
+		{
+			Volume = targetValue;
+			Device.ClearTweens(this, 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(this, 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(this, 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 unsafe void SetSends(Voice send)
+		{
+			FAudio.FAudioSendDescriptor* sendDesc = stackalloc FAudio.FAudioSendDescriptor[1];
+			sendDesc[0].Flags = 0;
+			sendDesc[0].pOutputVoice = send.Handle;
+
+			var sends = new FAudio.FAudioVoiceSends();
+			sends.SendCount = 1;
+			sends.pSends = (nint) sendDesc;
+
+			FAudio.FAudioVoice_SetOutputVoices(
+				Handle,
+				ref sends
+			);
+		}
+
+		public unsafe void SetSends(Voice sendOne, Voice sendTwo)
+		{
+			var sendDesc = stackalloc FAudio.FAudioSendDescriptor[2];
+			sendDesc[0].Flags = 0;
+			sendDesc[0].pOutputVoice = sendOne.Handle;
+			sendDesc[1].Flags = 0;
+			sendDesc[1].pOutputVoice = sendTwo.Handle;
+
+			var sends = new FAudio.FAudioVoiceSends();
+			sends.SendCount = 2;
+			sends.pSends = (nint) sendDesc;
+
+			FAudio.FAudioVoice_SetOutputVoices(
+				Handle,
+				ref sends
+			);
+		}
+
+		public unsafe void SetReverbEffectChain(ReverbEffect reverbEffect)
+		{
+			SetSends(Device.MasteringVoice, reverbEffect);
+			ReverbEffect = reverbEffect;
+		}
+
+		private unsafe void InitDSPSettings(uint srcChannels)
+		{
+			dspSettings = new FAudio.F3DAUDIO_DSP_SETTINGS();
+			dspSettings.DopplerFactor = 1f;
+			dspSettings.SrcChannelCount = srcChannels;
+			dspSettings.DstChannelCount = Device.DeviceDetails.OutputFormat.Format.nChannels;
+
+			nuint memsize = (
+				4 *
+				dspSettings.SrcChannelCount *
+				dspSettings.DstChannelCount
+			);
+
+			dspSettings.pMatrixCoefficients = (nint) NativeMemory.Alloc(memsize);
+			byte* memPtr = (byte*) dspSettings.pMatrixCoefficients;
+			for (uint i = 0; i < memsize; i += 1)
+			{
+				memPtr[i] = 0;
+			}
+
+			SetPanMatrixCoefficients();
+		}
+
+		private void UpdatePitch()
+		{
+			float doppler;
+			float dopplerScale = Device.DopplerScale;
+			if (!Is3D || dopplerScale == 0.0f)
+			{
+				doppler = 1.0f;
+			}
+			else
+			{
+				doppler = dspSettings.DopplerFactor * dopplerScale;
+			}
+
+			FAudio.FAudioSourceVoice_SetFrequencyRatio(
+				Handle,
+				(float) System.Math.Pow(2.0, pitch) * doppler,
+				0
+			);
+		}
+
+		// Taken from https://github.com/FNA-XNA/FNA/blob/master/src/Audio/SoundEffectInstance.cs
+		private unsafe void SetPanMatrixCoefficients()
+		{
+			/* Two major things to notice:
+			 * 1. The spec assumes any speaker count >= 2 has Front Left/Right.
+			 * 2. Stereo panning is WAY more complicated than you think.
+			 *    The main thing is that hard panning does NOT eliminate an
+			 *    entire channel; the two channels are blended on each side.
+			 * -flibit
+			 */
+			float* outputMatrix = (float*) dspSettings.pMatrixCoefficients;
+			if (dspSettings.SrcChannelCount == 1)
+			{
+				if (dspSettings.DstChannelCount == 1)
+				{
+					outputMatrix[0] = 1.0f;
+				}
+				else
+				{
+					outputMatrix[0] = (pan > 0.0f) ? (1.0f - pan) : 1.0f;
+					outputMatrix[1] = (pan < 0.0f) ? (1.0f + pan) : 1.0f;
+				}
+			}
+			else
+			{
+				if (dspSettings.DstChannelCount == 1)
+				{
+					outputMatrix[0] = 1.0f;
+					outputMatrix[1] = 1.0f;
+				}
+				else
+				{
+					if (pan <= 0.0f)
+					{
+						// Left speaker blends left/right channels
+						outputMatrix[0] = 0.5f * pan + 1.0f;
+						outputMatrix[1] = 0.5f * -pan;
+						// Right speaker gets less of the right channel
+						outputMatrix[2] = 0.0f;
+						outputMatrix[3] = pan + 1.0f;
+					}
+					else
+					{
+						// Left speaker gets less of the left channel
+						outputMatrix[0] = -pan + 1.0f;
+						outputMatrix[1] = 0.0f;
+						// Right speaker blends right/left channels
+						outputMatrix[2] = 0.5f * pan;
+						outputMatrix[3] = 0.5f * -pan + 1.0f;
+					}
+				}
+			}
+		}
+
+		protected unsafe override void Destroy()
+		{
+			FAudio.FAudioVoice_DestroyVoice(Handle);
+
+			NativeMemory.Free((void*) dspSettings.pMatrixCoefficients);
+		}
+	}
+}