forked from MoonsideGames/MoonWorks
				
			initial 3D audio implementation
							parent
							
								
									7cc14ea819
								
							
						
					
					
						commit
						5eb5027bf1
					
				|  | @ -17,6 +17,8 @@ namespace MoonWorks.Audio | |||
|         public float SpeedOfSound = 343.5f; | ||||
| 
 | ||||
|         internal FAudio.FAudioVoiceSends ReverbSends; | ||||
| 
 | ||||
|         private readonly List<WeakReference<AudioResource>> resources = new List<WeakReference<AudioResource>>(); | ||||
|         private readonly List<WeakReference<StreamingSound>> streamingSounds = new List<WeakReference<StreamingSound>>(); | ||||
| 
 | ||||
|         private bool IsDisposed; | ||||
|  | @ -216,6 +218,22 @@ namespace MoonWorks.Audio | |||
|             streamingSounds.Add(new WeakReference<StreamingSound>(instance)); | ||||
|         } | ||||
| 
 | ||||
|         internal void AddResourceReference(WeakReference<AudioResource> resourceReference) | ||||
|         { | ||||
|             lock (resources) | ||||
|             { | ||||
|                 resources.Add(resourceReference); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         internal void RemoveResourceReference(WeakReference<AudioResource> resourceReference) | ||||
|         { | ||||
|             lock (resources) | ||||
|             { | ||||
|                 resources.Remove(resourceReference); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         protected virtual void Dispose(bool disposing) | ||||
|         { | ||||
|             if (!IsDisposed) | ||||
|  |  | |||
|  | @ -0,0 +1,135 @@ | |||
| using System; | ||||
| using System.Runtime.InteropServices; | ||||
| using MoonWorks.Math; | ||||
| 
 | ||||
| namespace MoonWorks.Audio | ||||
| { | ||||
|     public class AudioEmitter : AudioResource | ||||
|     { | ||||
|         internal FAudio.F3DAUDIO_EMITTER emitterData; | ||||
| 
 | ||||
| 		public float DopplerScale | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				return emitterData.DopplerScaler; | ||||
| 			} | ||||
| 			set | ||||
| 			{ | ||||
| 				if (value < 0.0f) | ||||
| 				{ | ||||
| 					throw new ArgumentOutOfRangeException("AudioEmitter.DopplerScale must be greater than or equal to 0.0f"); | ||||
| 				} | ||||
| 				emitterData.DopplerScaler = value; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public Vector3 Forward | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				return new Vector3( | ||||
| 					emitterData.OrientFront.x, | ||||
| 					emitterData.OrientFront.y, | ||||
| 					-emitterData.OrientFront.z | ||||
| 				); | ||||
| 			} | ||||
| 			set | ||||
| 			{ | ||||
| 				emitterData.OrientFront.x = value.X; | ||||
| 				emitterData.OrientFront.y = value.Y; | ||||
| 				emitterData.OrientFront.z = -value.Z; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public Vector3 Position | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				return new Vector3( | ||||
| 					emitterData.Position.x, | ||||
| 					emitterData.Position.y, | ||||
| 					-emitterData.Position.z | ||||
| 				); | ||||
| 			} | ||||
| 			set | ||||
| 			{ | ||||
| 				emitterData.Position.x = value.X; | ||||
| 				emitterData.Position.y = value.Y; | ||||
| 				emitterData.Position.z = -value.Z; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 
 | ||||
| 		public Vector3 Up | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				return new Vector3( | ||||
| 					emitterData.OrientTop.x, | ||||
| 					emitterData.OrientTop.y, | ||||
| 					-emitterData.OrientTop.z | ||||
| 				); | ||||
| 			} | ||||
| 			set | ||||
| 			{ | ||||
| 				emitterData.OrientTop.x = value.X; | ||||
| 				emitterData.OrientTop.y = value.Y; | ||||
| 				emitterData.OrientTop.z = -value.Z; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public Vector3 Velocity | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				return new Vector3( | ||||
| 					emitterData.Velocity.x, | ||||
| 					emitterData.Velocity.y, | ||||
| 					-emitterData.Velocity.z | ||||
| 				); | ||||
| 			} | ||||
| 			set | ||||
| 			{ | ||||
| 				emitterData.Velocity.x = value.X; | ||||
| 				emitterData.Velocity.y = value.Y; | ||||
| 				emitterData.Velocity.z = -value.Z; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		private static readonly float[] stereoAzimuth = new float[] | ||||
| 		{ | ||||
| 			0.0f, 0.0f | ||||
| 		}; | ||||
| 
 | ||||
| 		private static readonly GCHandle stereoAzimuthHandle = GCHandle.Alloc( | ||||
| 			stereoAzimuth, | ||||
| 			GCHandleType.Pinned | ||||
| 		); | ||||
| 
 | ||||
|         public AudioEmitter(AudioDevice device) : base(device) | ||||
|         { | ||||
|             emitterData = new FAudio.F3DAUDIO_EMITTER(); | ||||
| 
 | ||||
|             DopplerScale = 1f; | ||||
|             Forward = Vector3.Forward; | ||||
|             Position = Vector3.Zero; | ||||
|             Up = Vector3.Up; | ||||
|             Velocity = Vector3.Zero; | ||||
| 
 | ||||
|             /* Unexposed variables, defaults based on XNA behavior */ | ||||
| 			emitterData.pCone = IntPtr.Zero; | ||||
| 			emitterData.ChannelCount = 1; | ||||
| 			emitterData.ChannelRadius = 1.0f; | ||||
| 			emitterData.pChannelAzimuths = stereoAzimuthHandle.AddrOfPinnedObject(); | ||||
| 			emitterData.pVolumeCurve = IntPtr.Zero; | ||||
| 			emitterData.pLFECurve = IntPtr.Zero; | ||||
| 			emitterData.pLPFDirectCurve = IntPtr.Zero; | ||||
| 			emitterData.pLPFReverbCurve = IntPtr.Zero; | ||||
| 			emitterData.pReverbCurve = IntPtr.Zero; | ||||
| 			emitterData.CurveDistanceScaler = 1.0f; | ||||
|         } | ||||
| 
 | ||||
|         protected override void Destroy() { } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,97 @@ | |||
| using System; | ||||
| using MoonWorks.Math; | ||||
| 
 | ||||
| namespace MoonWorks.Audio | ||||
| { | ||||
|     public class AudioListener : AudioResource | ||||
|     { | ||||
|         internal FAudio.F3DAUDIO_LISTENER listenerData; | ||||
| 
 | ||||
| 		public Vector3 Forward | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				return new Vector3( | ||||
| 					listenerData.OrientFront.x, | ||||
| 					listenerData.OrientFront.y, | ||||
| 					-listenerData.OrientFront.z | ||||
| 				); | ||||
| 			} | ||||
| 			set | ||||
| 			{ | ||||
| 				listenerData.OrientFront.x = value.X; | ||||
| 				listenerData.OrientFront.y = value.Y; | ||||
| 				listenerData.OrientFront.z = -value.Z; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public Vector3 Position | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				return new Vector3( | ||||
| 					listenerData.Position.x, | ||||
| 					listenerData.Position.y, | ||||
| 					-listenerData.Position.z | ||||
| 				); | ||||
| 			} | ||||
| 			set | ||||
| 			{ | ||||
| 				listenerData.Position.x = value.X; | ||||
| 				listenerData.Position.y = value.Y; | ||||
| 				listenerData.Position.z = -value.Z; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 
 | ||||
| 		public Vector3 Up | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				return new Vector3( | ||||
| 					listenerData.OrientTop.x, | ||||
| 					listenerData.OrientTop.y, | ||||
| 					-listenerData.OrientTop.z | ||||
| 				); | ||||
| 			} | ||||
| 			set | ||||
| 			{ | ||||
| 				listenerData.OrientTop.x = value.X; | ||||
| 				listenerData.OrientTop.y = value.Y; | ||||
| 				listenerData.OrientTop.z = -value.Z; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public Vector3 Velocity | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				return new Vector3( | ||||
| 					listenerData.Velocity.x, | ||||
| 					listenerData.Velocity.y, | ||||
| 					-listenerData.Velocity.z | ||||
| 				); | ||||
| 			} | ||||
| 			set | ||||
| 			{ | ||||
| 				listenerData.Velocity.x = value.X; | ||||
| 				listenerData.Velocity.y = value.Y; | ||||
| 				listenerData.Velocity.z = -value.Z; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
|         public AudioListener(AudioDevice device) : base(device) | ||||
|         { | ||||
|             listenerData = new FAudio.F3DAUDIO_LISTENER(); | ||||
|             Forward = Vector3.Forward; | ||||
|             Position = Vector3.Zero; | ||||
|             Up = Vector3.Up; | ||||
|             Velocity = Vector3.Zero; | ||||
| 
 | ||||
|             /* Unexposed variables, defaults based on XNA behavior */ | ||||
| 			listenerData.pCone = IntPtr.Zero; | ||||
|         } | ||||
| 
 | ||||
|         protected override void Destroy() { } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,52 @@ | |||
| using System; | ||||
| 
 | ||||
| namespace MoonWorks.Audio | ||||
| { | ||||
|     public abstract class AudioResource : IDisposable | ||||
|     { | ||||
|         public AudioDevice Device { get; } | ||||
| 
 | ||||
|         public bool IsDisposed { get; private set; } | ||||
| 
 | ||||
|         private WeakReference<AudioResource> selfReference; | ||||
| 
 | ||||
|         public AudioResource(AudioDevice device) | ||||
|         { | ||||
|             Device = device; | ||||
| 
 | ||||
|             selfReference = new WeakReference<AudioResource>(this); | ||||
|             Device.AddResourceReference(selfReference); | ||||
|         } | ||||
| 
 | ||||
|         protected abstract void Destroy(); | ||||
| 
 | ||||
|         protected virtual void Dispose(bool disposing) | ||||
|         { | ||||
|             if (!IsDisposed) | ||||
|             { | ||||
|                 Destroy(); | ||||
| 
 | ||||
|                 if (selfReference != null) | ||||
|                 { | ||||
|                     Device.RemoveResourceReference(selfReference); | ||||
|                     selfReference = null; | ||||
|                 } | ||||
| 
 | ||||
|                 IsDisposed = true; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         ~AudioResource() | ||||
|         { | ||||
|             // 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,11 +1,11 @@ | |||
| using System; | ||||
| using System.Runtime.InteropServices; | ||||
| using MoonWorks.Math; | ||||
| 
 | ||||
| namespace MoonWorks.Audio | ||||
| { | ||||
|     public abstract class SoundInstance : IDisposable | ||||
|     public abstract class SoundInstance : AudioResource | ||||
|     { | ||||
|         protected AudioDevice Device { get; } | ||||
|         internal IntPtr Handle { get; } | ||||
|         internal FAudio.FAudioWaveFormatEx Format { get; } | ||||
|         public bool Loop { get; } | ||||
|  | @ -13,7 +13,6 @@ namespace MoonWorks.Audio | |||
|         protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings; | ||||
| 
 | ||||
|         protected bool is3D; | ||||
|         private bool IsDisposed; | ||||
| 
 | ||||
|         public abstract SoundState State { get; protected set; } | ||||
| 
 | ||||
|  | @ -54,22 +53,8 @@ namespace MoonWorks.Audio | |||
|             get => _pitch; | ||||
|             set | ||||
|             { | ||||
|                 float doppler; | ||||
|                 if (!is3D || Device.DopplerScale == 0f) | ||||
|                 { | ||||
|                     doppler = 1f; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     doppler = dspSettings.DopplerFactor * Device.DopplerScale; | ||||
|                 } | ||||
| 
 | ||||
|                 _pitch = value; | ||||
|                 FAudio.FAudioSourceVoice_SetFrequencyRatio( | ||||
|                     Handle, | ||||
|                     (float) System.Math.Pow(2.0, _pitch) * doppler, | ||||
|                     0 | ||||
|                 ); | ||||
|                 _pitch = MathHelper.Clamp(value, -1f, 1f); | ||||
|                 UpdatePitch(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  | @ -182,9 +167,8 @@ namespace MoonWorks.Audio | |||
|             uint samplesPerSecond, | ||||
|             bool is3D, | ||||
|             bool loop | ||||
|         ) { | ||||
|             Device = device; | ||||
| 
 | ||||
|         ) : base(device) | ||||
|         { | ||||
|             var blockAlign = (ushort)(4 * channels); | ||||
|             var format = new FAudio.FAudioWaveFormatEx | ||||
|             { | ||||
|  | @ -228,6 +212,32 @@ namespace MoonWorks.Audio | |||
|             State = SoundState.Stopped; | ||||
|         } | ||||
| 
 | ||||
|         public void Apply3D(AudioListener listener, AudioEmitter emitter) | ||||
|         { | ||||
|             is3D = true; | ||||
| 
 | ||||
|             emitter.emitterData.CurveDistanceScaler = Device.CurveDistanceScalar; | ||||
|             emitter.emitterData.ChannelCount = dspSettings.SrcChannelCount; | ||||
| 
 | ||||
|             FAudio.F3DAudioCalculate( | ||||
|                 Device.Handle3D, | ||||
|                 ref listener.listenerData, | ||||
|                 ref emitter.emitterData, | ||||
|                 FAudio.F3DAUDIO_CALCULATE_MATRIX | FAudio.F3DAUDIO_CALCULATE_DOPPLER, | ||||
|                 ref dspSettings | ||||
|             ); | ||||
| 
 | ||||
|             UpdatePitch(); | ||||
|             FAudio.FAudioVoice_SetOutputMatrix( | ||||
|                 Handle, | ||||
|                 Device.MasteringVoice, | ||||
|                 dspSettings.SrcChannelCount, | ||||
|                 dspSettings.DstChannelCount, | ||||
|                 dspSettings.pMatrixCoefficients, | ||||
|                 0 | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         public abstract void Play(); | ||||
|         public abstract void Pause(); | ||||
|         public abstract void Stop(bool immediate); | ||||
|  | @ -257,6 +267,26 @@ namespace MoonWorks.Audio | |||
|             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() | ||||
|         { | ||||
|  | @ -311,33 +341,12 @@ namespace MoonWorks.Audio | |||
| 			} | ||||
|         } | ||||
| 
 | ||||
|         protected virtual void Dispose(bool disposing) | ||||
|         protected override void Destroy() | ||||
|         { | ||||
|             if (!IsDisposed) | ||||
|             { | ||||
|                 if (disposing) | ||||
|                 { | ||||
|                     // dispose managed state (managed objects) | ||||
|             Stop(true); | ||||
|                 } | ||||
| 
 | ||||
|             FAudio.FAudioVoice_DestroyVoice(Handle); | ||||
|             Marshal.FreeHGlobal(dspSettings.pMatrixCoefficients); | ||||
|                 IsDisposed = true; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         ~SoundInstance() | ||||
|         { | ||||
|             // 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,10 @@ | |||
| using System; | ||||
| using System.IO; | ||||
| using System.Runtime.InteropServices; | ||||
| 
 | ||||
| namespace MoonWorks.Audio | ||||
| { | ||||
|     public class StaticSound : IDisposable | ||||
|     public class StaticSound : AudioResource | ||||
|     { | ||||
|         internal AudioDevice Device { get; } | ||||
|         internal FAudio.FAudioBuffer Handle; | ||||
|         public ushort Channels { get; } | ||||
|         public uint SamplesPerSecond { get; } | ||||
|  | @ -14,8 +12,6 @@ namespace MoonWorks.Audio | |||
|         public uint LoopStart { get; set; } = 0; | ||||
|         public uint LoopLength { get; set; } = 0; | ||||
| 
 | ||||
|         private bool IsDisposed; | ||||
| 
 | ||||
|         public static StaticSound LoadOgg(AudioDevice device, string filePath) | ||||
|         { | ||||
|             var filePointer = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero); | ||||
|  | @ -54,9 +50,8 @@ namespace MoonWorks.Audio | |||
|             float[] buffer, | ||||
|             uint bufferOffset, /* in floats */ | ||||
|             uint bufferLength  /* in floats */ | ||||
|         ) { | ||||
|             Device = device; | ||||
| 
 | ||||
|         ) : base(device) | ||||
|         { | ||||
|             Channels = channels; | ||||
|             SamplesPerSecond = samplesPerSecond; | ||||
| 
 | ||||
|  | @ -79,32 +74,9 @@ namespace MoonWorks.Audio | |||
|             return new StaticSoundInstance(Device, this, false, loop); | ||||
|         } | ||||
| 
 | ||||
|         protected virtual void Dispose(bool disposing) | ||||
|         protected override void Destroy() | ||||
|         { | ||||
|             if (!IsDisposed) | ||||
|             { | ||||
|                 if (disposing) | ||||
|                 { | ||||
|                     // dispose managed state (managed objects) | ||||
|                 } | ||||
| 
 | ||||
|             Marshal.FreeHGlobal(Handle.pAudioData); | ||||
|                 IsDisposed = true; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources | ||||
|         ~StaticSound() | ||||
|         { | ||||
|             // 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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -16,8 +16,6 @@ namespace MoonWorks.Audio | |||
| 
 | ||||
|         public int PendingBufferCount => queuedBuffers.Count; | ||||
| 
 | ||||
|         private bool IsDisposed; | ||||
| 
 | ||||
|         public StreamingSound( | ||||
|             AudioDevice device, | ||||
|             ushort channels, | ||||
|  | @ -172,21 +170,9 @@ namespace MoonWorks.Audio | |||
| 
 | ||||
|         protected abstract void SeekStart(); | ||||
| 
 | ||||
|         protected override void Dispose(bool disposing) | ||||
|         protected override void Destroy() | ||||
|         { | ||||
|             if (!IsDisposed) | ||||
|             { | ||||
|                 if (disposing) | ||||
|                 { | ||||
|                     // dispose managed state (managed objects) | ||||
|             Stop(true); | ||||
|         } | ||||
| 
 | ||||
|                 // dispose unmanaged state | ||||
|                 IsDisposed = true; | ||||
|             } | ||||
| 
 | ||||
|             base.Dispose(disposing); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -15,8 +15,6 @@ namespace MoonWorks.Audio | |||
| 
 | ||||
|         public override SoundState State { get; protected set; } | ||||
| 
 | ||||
|         private bool IsDisposed; | ||||
| 
 | ||||
|         public static StreamingSoundOgg Load( | ||||
|             AudioDevice device, | ||||
|             string filePath, | ||||
|  | @ -83,22 +81,9 @@ namespace MoonWorks.Audio | |||
|             FAudio.stb_vorbis_seek_start(FileHandle); | ||||
|         } | ||||
| 
 | ||||
|         protected override void Dispose(bool disposing) | ||||
|         protected override void Destroy() | ||||
|         { | ||||
|             if (!IsDisposed) | ||||
|             { | ||||
|                 if (disposing) | ||||
|                 { | ||||
|                     // dispose managed state (managed objects) | ||||
|                 } | ||||
| 
 | ||||
|                 // dispose unmanaged state | ||||
|             FAudio.stb_vorbis_close(FileHandle); | ||||
| 
 | ||||
|                 IsDisposed = true; | ||||
|             } | ||||
| 
 | ||||
|             base.Dispose(disposing); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue