faux mastering voice + submitting static sounds
							parent
							
								
									e2c85ec728
								
							
						
					
					
						commit
						c2cb83f93f
					
				|  | @ -10,8 +10,12 @@ namespace MoonWorks.Audio | |||
| 		public byte[] Handle3D { get; } | ||||
| 		public FAudio.FAudioDeviceDetails DeviceDetails { get; } | ||||
| 
 | ||||
| 		private MasteringVoice masteringVoice; | ||||
| 		public MasteringVoice MasteringVoice => masteringVoice; | ||||
| 		private IntPtr trueMasteringVoice; | ||||
| 
 | ||||
| 		// this is a fun little trick where we use a submix voice as a "faux" mastering voice | ||||
| 		// this lets us maintain API consistency for effects like panning and reverb | ||||
| 		private SubmixVoice fauxMasteringVoice; | ||||
| 		public SubmixVoice MasteringVoice => fauxMasteringVoice; | ||||
| 
 | ||||
| 		public float CurveDistanceScalar = 1f; | ||||
| 		public float DopplerScale = 1f; | ||||
|  | @ -19,8 +23,8 @@ namespace MoonWorks.Audio | |||
| 
 | ||||
| 		private readonly HashSet<WeakReference> resources = new HashSet<WeakReference>(); | ||||
| 		private readonly List<StreamingSound> autoUpdateStreamingSoundReferences = new List<StreamingSound>(); | ||||
| 		private readonly List<StaticSoundInstance> autoFreeStaticSoundInstanceReferences = new List<StaticSoundInstance>(); | ||||
| 		private readonly List<WeakReference<SoundSequence>> soundSequenceReferences = new List<WeakReference<SoundSequence>>(); | ||||
| 		private readonly List<SourceVoice> autoFreeSourceVoices = new List<SourceVoice>(); | ||||
| 
 | ||||
| 		private AudioTweenManager AudioTweenManager; | ||||
| 
 | ||||
|  | @ -85,18 +89,25 @@ namespace MoonWorks.Audio | |||
| 			} | ||||
| 
 | ||||
| 			/* Init Mastering Voice */ | ||||
| 			var result = MasteringVoice.Create( | ||||
| 				this, | ||||
| 			var result = FAudio.FAudio_CreateMasteringVoice( | ||||
| 				Handle, | ||||
| 				out trueMasteringVoice, | ||||
| 				FAudio.FAUDIO_DEFAULT_CHANNELS, | ||||
| 				FAudio.FAUDIO_DEFAULT_SAMPLERATE, | ||||
| 				0, | ||||
| 				i, | ||||
| 				out masteringVoice | ||||
| 				IntPtr.Zero | ||||
| 			); | ||||
| 
 | ||||
| 			if (!result) | ||||
| 			if (result != 0) | ||||
| 			{ | ||||
| 				Logger.LogError("Failed to create a mastering voice!"); | ||||
| 				Logger.LogError("Audio device creation failed!"); | ||||
| 				return; | ||||
| 			} | ||||
| 
 | ||||
| 			fauxMasteringVoice = new SubmixVoice(this, FAudio.FAUDIO_DEFAULT_CHANNELS, FAudio.FAUDIO_DEFAULT_SAMPLERATE); | ||||
| 
 | ||||
| 			/* Init 3D Audio */ | ||||
| 
 | ||||
| 			Handle3D = new byte[FAudio.F3DAUDIO_HANDLE_BYTESIZE]; | ||||
|  | @ -162,17 +173,6 @@ namespace MoonWorks.Audio | |||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			for (var i = autoFreeStaticSoundInstanceReferences.Count - 1; i >= 0; i -= 1) | ||||
| 			{ | ||||
| 				var staticSoundInstance = autoFreeStaticSoundInstanceReferences[i]; | ||||
| 
 | ||||
| 				if (staticSoundInstance.State == SoundState.Stopped) | ||||
| 				{ | ||||
| 					staticSoundInstance.Free(); | ||||
| 					autoFreeStaticSoundInstanceReferences.RemoveAt(i); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			for (var i = soundSequenceReferences.Count - 1; i >= 0; i -= 1) | ||||
| 			{ | ||||
| 				if (soundSequenceReferences[i].TryGetTarget(out var soundSequence)) | ||||
|  | @ -185,14 +185,61 @@ namespace MoonWorks.Audio | |||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			for (var i = autoFreeSourceVoices.Count - 1; i >= 0; i -= 1) | ||||
| 			{ | ||||
| 				var voice = autoFreeSourceVoices[i]; | ||||
| 				if (voice.BuffersQueued == 0) | ||||
| 				{ | ||||
| 					Return(voice); | ||||
| 					autoFreeSourceVoices.RemoveAt(i); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			AudioTweenManager.Update(elapsedSeconds); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Triggers all pending operations with the given syncGroup value. | ||||
| 		/// </summary> | ||||
| 		public void TriggerSyncGroup(uint syncGroup) | ||||
| 		{ | ||||
| 			FAudio.FAudio_CommitChanges(Handle, syncGroup); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Obtains an appropriate source voice from the voice pool. | ||||
| 		/// </summary> | ||||
| 		/// <param name="format">The format that the voice must match.</param> | ||||
| 		/// <returns>A source voice with the given format.</returns> | ||||
| 		public SourceVoice Obtain(Format format) | ||||
| 		{ | ||||
| 			lock (StateLock) | ||||
| 			{ | ||||
| 				return VoicePool.Obtain(format); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		internal void ReturnWhenIdle(SourceVoice voice) | ||||
| 		{ | ||||
| 			lock (StateLock) | ||||
| 			{ | ||||
| 				autoFreeSourceVoices.Add(voice); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Returns the source voice to the voice pool. | ||||
| 		/// </summary> | ||||
| 		/// <param name="voice"></param> | ||||
| 		internal void Return(SourceVoice voice) | ||||
| 		{ | ||||
| 			lock (StateLock) | ||||
| 			{ | ||||
| 				voice.Reset(); | ||||
| 				VoicePool.Return(voice); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		internal void CreateTween( | ||||
| 			Voice voice, | ||||
| 			AudioTweenProperty property, | ||||
|  | @ -252,26 +299,11 @@ namespace MoonWorks.Audio | |||
| 			autoUpdateStreamingSoundReferences.Add(instance); | ||||
| 		} | ||||
| 
 | ||||
| 		internal void AddAutoFreeStaticSoundInstance(StaticSoundInstance instance) | ||||
| 		{ | ||||
| 			autoFreeStaticSoundInstanceReferences.Add(instance); | ||||
| 		} | ||||
| 
 | ||||
| 		internal void AddSoundSequenceReference(SoundSequence sequence) | ||||
| 		{ | ||||
| 			soundSequenceReferences.Add(new WeakReference<SoundSequence>(sequence)); | ||||
| 		} | ||||
| 
 | ||||
| 		internal SourceVoice ObtainSourceVoice(Format format) | ||||
| 		{ | ||||
| 			return VoicePool.Obtain(format); | ||||
| 		} | ||||
| 
 | ||||
| 		internal void ReturnSourceVoice(SourceVoice voice) | ||||
| 		{ | ||||
| 			VoicePool.Return(voice); | ||||
| 		} | ||||
| 
 | ||||
| 		protected virtual void Dispose(bool disposing) | ||||
| 		{ | ||||
| 			if (!IsDisposed) | ||||
|  | @ -306,6 +338,7 @@ namespace MoonWorks.Audio | |||
| 					resources.Clear(); | ||||
| 				} | ||||
| 
 | ||||
| 				FAudio.FAudioVoice_DestroyVoice(trueMasteringVoice); | ||||
| 				FAudio.FAudio_Release(Handle); | ||||
| 
 | ||||
| 				IsDisposed = true; | ||||
|  |  | |||
|  | @ -24,10 +24,7 @@ namespace MoonWorks.Audio | |||
| 					switch (audioTween.Property) | ||||
| 					{ | ||||
| 						case AudioTweenProperty.Pan: | ||||
| 							if (voice is SendableVoice pannableVoice) | ||||
| 							{ | ||||
| 								audioTween.StartValue = pannableVoice.Pan; | ||||
| 							} | ||||
| 							audioTween.StartValue = voice.Pan; | ||||
| 							break; | ||||
| 
 | ||||
| 						case AudioTweenProperty.Pitch: | ||||
|  | @ -43,10 +40,7 @@ namespace MoonWorks.Audio | |||
| 							break; | ||||
| 
 | ||||
| 						case AudioTweenProperty.Reverb: | ||||
| 							if (voice is SendableVoice reverbableVoice) | ||||
| 							{ | ||||
| 								audioTween.StartValue = reverbableVoice.Reverb; | ||||
| 							} | ||||
| 							audioTween.StartValue = voice.Reverb; | ||||
| 							break; | ||||
| 					} | ||||
| 
 | ||||
|  | @ -139,10 +133,7 @@ namespace MoonWorks.Audio | |||
| 			switch (audioTween.Property) | ||||
| 			{ | ||||
| 				case AudioTweenProperty.Pan: | ||||
| 					if (audioTween.Voice is SendableVoice pannableVoice) | ||||
| 					{ | ||||
| 						pannableVoice.Pan = value; | ||||
| 					} | ||||
| 					audioTween.Voice.Pan = value; | ||||
| 					break; | ||||
| 
 | ||||
| 				case AudioTweenProperty.Pitch: | ||||
|  | @ -158,10 +149,7 @@ namespace MoonWorks.Audio | |||
| 					break; | ||||
| 
 | ||||
| 				case AudioTweenProperty.Reverb: | ||||
| 					if (audioTween.Voice is SendableVoice reverbableVoice) | ||||
| 					{ | ||||
| 						reverbableVoice.Reverb = value; | ||||
| 					} | ||||
| 					audioTween.Voice.Reverb = value; | ||||
| 					break; | ||||
| 			} | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,7 +0,0 @@ | |||
| namespace MoonWorks.Audio | ||||
| { | ||||
| 	public interface IReceivableVoice | ||||
| 	{ | ||||
| 		public System.IntPtr Handle { get; } | ||||
| 	} | ||||
| } | ||||
|  | @ -1,43 +0,0 @@ | |||
| using System; | ||||
| 
 | ||||
| namespace MoonWorks.Audio | ||||
| { | ||||
| 	public class MasteringVoice : Voice, IReceivableVoice | ||||
| 	{ | ||||
| 		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, device.DeviceDetails.OutputFormat.Format.nChannels, 0) | ||||
| 		{ | ||||
| 			this.handle = handle; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -1,227 +0,0 @@ | |||
| using System; | ||||
| using System.Runtime.InteropServices; | ||||
| using EasingFunction = System.Func<float, float>; | ||||
| 
 | ||||
| namespace MoonWorks.Audio | ||||
| { | ||||
| 	public unsafe class SendableVoice : Voice | ||||
| 	{ | ||||
| 		private IReceivableVoice OutputVoice; | ||||
| 		private ReverbEffect ReverbEffect; | ||||
| 
 | ||||
| 		byte* pMatrixCoefficients; | ||||
| 
 | ||||
| 		protected float pan = 0; | ||||
| 		public 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, | ||||
| 						OutputVoice.Handle, | ||||
| 						SourceChannelCount, | ||||
| 						DestinationChannelCount, | ||||
| 						(nint) pMatrixCoefficients, | ||||
| 						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*) pMatrixCoefficients; | ||||
| 						outputMatrix[0] = reverb; | ||||
| 						if (SourceChannelCount == 2) | ||||
| 						{ | ||||
| 							outputMatrix[1] = reverb; | ||||
| 						} | ||||
| 
 | ||||
| 						FAudio.FAudioVoice_SetOutputMatrix( | ||||
| 							Handle, | ||||
| 							ReverbEffect.Handle, | ||||
| 							SourceChannelCount, | ||||
| 							1, | ||||
| 							(nint) pMatrixCoefficients, | ||||
| 							0 | ||||
| 						); | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				#if DEBUG | ||||
| 				if (ReverbEffect == null) | ||||
| 				{ | ||||
| 					Logger.LogWarn("Tried to set reverb value before applying a reverb effect"); | ||||
| 				} | ||||
| 				#endif | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public SendableVoice(AudioDevice device, uint sourceChannelCount, uint destinationChannelCount) : base(device, sourceChannelCount, destinationChannelCount) | ||||
| 		{ | ||||
| 			OutputVoice = device.MasteringVoice; | ||||
| 			nuint memsize = (nuint) (4 * sourceChannelCount * destinationChannelCount); | ||||
| 			pMatrixCoefficients = (byte*) NativeMemory.AllocZeroed(memsize); | ||||
| 			SetPanMatrixCoefficients(); | ||||
| 		} | ||||
| 
 | ||||
| 		public virtual void SetPan(float targetValue) | ||||
| 		{ | ||||
| 			Pan = targetValue; | ||||
| 			Device.ClearTweens(this, AudioTweenProperty.Pan); | ||||
| 		} | ||||
| 
 | ||||
| 		public virtual void SetPan(float targetValue, float duration, EasingFunction easingFunction) | ||||
| 		{ | ||||
| 			Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, 0); | ||||
| 		} | ||||
| 
 | ||||
| 		public virtual void SetPan(float targetValue, float delayTime, float duration, EasingFunction easingFunction) | ||||
| 		{ | ||||
| 			Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, delayTime); | ||||
| 		} | ||||
| 
 | ||||
| 		public virtual void SetReverb(float targetValue) | ||||
| 		{ | ||||
| 			Reverb = targetValue; | ||||
| 			Device.ClearTweens(this, AudioTweenProperty.Reverb); | ||||
| 		} | ||||
| 
 | ||||
| 		public virtual void SetReverb(float targetValue, float duration, EasingFunction easingFunction) | ||||
| 		{ | ||||
| 			Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, 0); | ||||
| 		} | ||||
| 
 | ||||
| 		public virtual void SetReverb(float targetValue, float delayTime, float duration, EasingFunction easingFunction) | ||||
| 		{ | ||||
| 			Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, delayTime); | ||||
| 		} | ||||
| 
 | ||||
| 		public unsafe void SetOutputVoice(IReceivableVoice 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 | ||||
| 			); | ||||
| 
 | ||||
| 			OutputVoice = send; | ||||
| 		} | ||||
| 
 | ||||
| 		public virtual unsafe void SetReverbEffectChain(ReverbEffect reverbEffect) | ||||
| 		{ | ||||
| 			var sendDesc = stackalloc FAudio.FAudioSendDescriptor[2]; | ||||
| 			sendDesc[0].Flags = 0; | ||||
| 			sendDesc[0].pOutputVoice = OutputVoice.Handle; | ||||
| 			sendDesc[1].Flags = 0; | ||||
| 			sendDesc[1].pOutputVoice = reverbEffect.Handle; | ||||
| 
 | ||||
| 			var sends = new FAudio.FAudioVoiceSends(); | ||||
| 			sends.SendCount = 2; | ||||
| 			sends.pSends = (nint) sendDesc; | ||||
| 
 | ||||
| 			FAudio.FAudioVoice_SetOutputVoices( | ||||
| 				Handle, | ||||
| 				ref sends | ||||
| 			); | ||||
| 
 | ||||
| 			ReverbEffect = reverbEffect; | ||||
| 		} | ||||
| 
 | ||||
| 		// 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*) pMatrixCoefficients; | ||||
| 			if (SourceChannelCount == 1) | ||||
| 			{ | ||||
| 				if (DestinationChannelCount == 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 (DestinationChannelCount == 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 override unsafe void Destroy() | ||||
| 		{ | ||||
| 			NativeMemory.Free(pMatrixCoefficients); | ||||
| 			base.Destroy(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -59,11 +59,7 @@ namespace MoonWorks.Audio | |||
| 
 | ||||
| 			lock (StateLock) | ||||
| 			{ | ||||
| 				FAudio.FAudioSourceVoice_SubmitSourceBuffer( | ||||
| 					Handle, | ||||
| 					ref sound.Handle, | ||||
| 					IntPtr.Zero | ||||
| 				); | ||||
| 				Submit(sound); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
|  | @ -2,7 +2,10 @@ using System; | |||
| 
 | ||||
| namespace MoonWorks.Audio | ||||
| { | ||||
| 	public class SourceVoice : SendableVoice | ||||
| 	/// <summary> | ||||
| 	/// Emits audio from submitted audio buffers. | ||||
| 	/// </summary> | ||||
| 	public class SourceVoice : Voice | ||||
| 	{ | ||||
| 		private Format format; | ||||
| 		public Format Format => format; | ||||
|  | @ -55,7 +58,7 @@ namespace MoonWorks.Audio | |||
| 
 | ||||
| 			 FAudio.FAudio_CreateSourceVoice( | ||||
| 				device.Handle, | ||||
| 				out var Handle, | ||||
| 				out handle, | ||||
| 				ref fAudioFormat, | ||||
| 				FAudio.FAUDIO_VOICE_USEFILTER, | ||||
| 				FAudio.FAUDIO_DEFAULT_FREQ_RATIO, | ||||
|  | @ -65,6 +68,11 @@ namespace MoonWorks.Audio | |||
| 			); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Starts consumption and processing of audio by the voice. | ||||
| 		/// Delivers the result to any connected submix or mastering voice. | ||||
| 		/// </summary> | ||||
| 		/// <param name="syncGroup">Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.</param> | ||||
| 		public void Play(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW) | ||||
| 		{ | ||||
| 			lock (StateLock) | ||||
|  | @ -75,6 +83,11 @@ namespace MoonWorks.Audio | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Pauses playback. | ||||
| 		/// All source buffers that are queued on the voice and the current cursor position are preserved. | ||||
| 		/// </summary> | ||||
| 		/// <param name="syncGroup">Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.</param> | ||||
| 		public void Pause(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW) | ||||
| 		{ | ||||
| 			lock (StateLock) | ||||
|  | @ -85,6 +98,11 @@ namespace MoonWorks.Audio | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Stops looping the voice when it reaches the end of the current loop region. | ||||
| 		/// If the cursor for the voice is not in a loop region, ExitLoop does nothing. | ||||
| 		/// </summary> | ||||
| 		/// <param name="syncGroup">Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.</param> | ||||
| 		public void ExitLoop(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW) | ||||
| 		{ | ||||
| 			lock (StateLock) | ||||
|  | @ -93,6 +111,10 @@ namespace MoonWorks.Audio | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Stops playback and removes all pending audio buffers from the voice queue. | ||||
| 		/// </summary> | ||||
| 		/// <param name="syncGroup">Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.</param> | ||||
| 		public void Stop(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW) | ||||
| 		{ | ||||
| 			lock (StateLock) | ||||
|  | @ -104,7 +126,32 @@ namespace MoonWorks.Audio | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public void SubmitBuffer(FAudio.FAudioBuffer buffer) | ||||
| 		/// <summary> | ||||
| 		/// Adds a static sound to the voice queue. | ||||
| 		/// The voice processes and plays back the buffers in its queue in the order that they were submitted. | ||||
| 		/// </summary> | ||||
| 		/// <param name="sound">The sound to submit to the voice.</param> | ||||
| 		/// <param name="loop">Designates that the voice will loop the submitted buffer.</param> | ||||
| 		public void Submit(StaticSound sound, bool loop = false) | ||||
| 		{ | ||||
| 			if (loop) | ||||
| 			{ | ||||
| 				sound.Buffer.LoopCount = FAudio.FAUDIO_LOOP_INFINITE; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				sound.Buffer.LoopCount = 0; | ||||
| 			} | ||||
| 
 | ||||
| 			Submit(sound.Buffer); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// 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. | ||||
| 		/// </summary> | ||||
| 		/// <param name="buffer">The buffer to submit to the voice.</param> | ||||
| 		public void Submit(FAudio.FAudioBuffer buffer) | ||||
| 		{ | ||||
| 			FAudio.FAudioSourceVoice_SubmitSourceBuffer( | ||||
| 				Handle, | ||||
|  | @ -113,31 +160,27 @@ namespace MoonWorks.Audio | |||
| 			); | ||||
| 		} | ||||
| 
 | ||||
| 		// FIXME: maybe this is bad | ||||
| 		// NOTE: SourceVoices obtained this way will be returned to the voice pool when stopped! | ||||
| 		public static SourceVoice ObtainSourceVoice(AudioDevice device, Format format) | ||||
| 		public void Submit(StreamingSound streamingSound) | ||||
| 		{ | ||||
| 			return device.ObtainSourceVoice(format); | ||||
| 
 | ||||
| 		} | ||||
| 
 | ||||
| 		// intended for short-lived sound effects | ||||
| 		public static SourceVoice PlayStaticSound(AudioDevice device, StaticSound sound, SubmixVoice sendVoice = null) | ||||
| 		/// <summary> | ||||
| 		/// Designates that this source voice will return to the voice pool once all its buffers are exhausted. | ||||
| 		/// </summary> | ||||
| 		public void ReturnWhenIdle() | ||||
| 		{ | ||||
| 			var voice = ObtainSourceVoice(device, sound.Format); | ||||
| 			Device.ReturnWhenIdle(this); | ||||
| 		} | ||||
| 
 | ||||
| 			if (sendVoice == null) | ||||
| 			{ | ||||
| 				voice.SetOutputVoice(device.MasteringVoice); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				voice.SetOutputVoice(sendVoice); | ||||
| 			} | ||||
| 
 | ||||
| 			voice.SubmitBuffer(sound.Handle); | ||||
| 			voice.Play(); | ||||
| 
 | ||||
| 			return voice; | ||||
| 		/// <summary> | ||||
| 		/// Returns this source voice to the voice pool. | ||||
| 		/// </summary> | ||||
| 		public void Return() | ||||
| 		{ | ||||
| 			Stop(); | ||||
| 			Reset(); | ||||
| 			Device.Return(this); | ||||
| 		} | ||||
| 
 | ||||
| 		protected override unsafe void Destroy() | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Runtime.InteropServices; | ||||
| 
 | ||||
|  | @ -7,17 +6,11 @@ namespace MoonWorks.Audio | |||
| { | ||||
| 	public class StaticSound : AudioResource | ||||
| 	{ | ||||
| 		internal FAudio.FAudioBuffer Handle; | ||||
| 		internal FAudio.FAudioBuffer Buffer; | ||||
| 
 | ||||
| 		private Format format; | ||||
| 		public Format Format => format; | ||||
| 
 | ||||
| 		public uint LoopStart { get; set; } = 0; | ||||
| 		public uint LoopLength { get; set; } = 0; | ||||
| 
 | ||||
| 		private Stack<StaticSoundInstance> AvailableInstances = new Stack<StaticSoundInstance>(); | ||||
| 		private HashSet<StaticSoundInstance> UsedInstances = new HashSet<StaticSoundInstance>(); | ||||
| 
 | ||||
| 		private bool OwnsBuffer; | ||||
| 
 | ||||
| 		public static unsafe StaticSound LoadOgg(AudioDevice device, string filePath) | ||||
|  | @ -253,10 +246,9 @@ namespace MoonWorks.Audio | |||
| 			uint bufferLengthInBytes, | ||||
| 			bool ownsBuffer) : base(device) | ||||
| 		{ | ||||
| 			// TODO: should we wrap the format struct to make it nicer? | ||||
| 			this.format = format; | ||||
| 
 | ||||
| 			Handle = new FAudio.FAudioBuffer | ||||
| 			Buffer = new FAudio.FAudioBuffer | ||||
| 			{ | ||||
| 				Flags = FAudio.FAUDIO_END_OF_STREAM, | ||||
| 				pContext = IntPtr.Zero, | ||||
|  | @ -269,66 +261,11 @@ namespace MoonWorks.Audio | |||
| 			OwnsBuffer = ownsBuffer; | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Gets a sound instance from the pool. | ||||
| 		/// NOTE: If AutoFree is false, you will have to call StaticSoundInstance.Free() yourself or leak the instance! | ||||
| 		/// </summary> | ||||
| 		public StaticSoundInstance GetInstance(bool autoFree = true) | ||||
| 		{ | ||||
| 			StaticSoundInstance instance; | ||||
| 
 | ||||
| 			lock (AvailableInstances) | ||||
| 			{ | ||||
| 				if (AvailableInstances.Count == 0) | ||||
| 				{ | ||||
| 					AvailableInstances.Push(new StaticSoundInstance(Device, this)); | ||||
| 				} | ||||
| 
 | ||||
| 				instance = AvailableInstances.Pop(); | ||||
| 			} | ||||
| 
 | ||||
| 			instance.AutoFree = autoFree; | ||||
| 
 | ||||
| 			lock (UsedInstances) | ||||
| 			{ | ||||
| 				UsedInstances.Add(instance); | ||||
| 			} | ||||
| 
 | ||||
| 			return instance; | ||||
| 		} | ||||
| 
 | ||||
| 		internal void FreeInstance(StaticSoundInstance instance) | ||||
| 		{ | ||||
| 			instance.Reset(); | ||||
| 
 | ||||
| 			lock (UsedInstances) | ||||
| 			{ | ||||
| 				UsedInstances.Remove(instance); | ||||
| 			} | ||||
| 
 | ||||
| 			lock (AvailableInstances) | ||||
| 			{ | ||||
| 				AvailableInstances.Push(instance); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		protected override unsafe void Destroy() | ||||
| 		{ | ||||
| 			foreach (var instance in UsedInstances) | ||||
| 			{ | ||||
| 				instance.Free(); | ||||
| 			} | ||||
| 
 | ||||
| 			foreach (var instance in AvailableInstances) | ||||
| 			{ | ||||
| 				instance.Dispose(); | ||||
| 			} | ||||
| 
 | ||||
| 			AvailableInstances.Clear(); | ||||
| 
 | ||||
| 			if (OwnsBuffer) | ||||
| 			{ | ||||
| 				NativeMemory.Free((void*) Handle.pAudioData); | ||||
| 				NativeMemory.Free((void*) Buffer.pAudioData); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
|  | @ -1,141 +0,0 @@ | |||
| using System; | ||||
| 
 | ||||
| namespace MoonWorks.Audio | ||||
| { | ||||
| 	public class StaticSoundInstance : SoundInstance | ||||
| 	{ | ||||
| 		public StaticSound Parent { get; } | ||||
| 
 | ||||
| 		public bool Loop { get; set; } | ||||
| 
 | ||||
| 		private SoundState _state = SoundState.Stopped; | ||||
| 		public override SoundState State | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				FAudio.FAudioSourceVoice_GetState( | ||||
| 					Voice, | ||||
| 					out var state, | ||||
| 					FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED | ||||
| 				); | ||||
| 				if (state.BuffersQueued == 0) | ||||
| 				{ | ||||
| 					StopImmediate(); | ||||
| 				} | ||||
| 
 | ||||
| 				return _state; | ||||
| 			} | ||||
| 
 | ||||
| 			protected set | ||||
| 			{ | ||||
| 				_state = value; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public bool AutoFree { get; internal set; } | ||||
| 
 | ||||
| 		internal StaticSoundInstance( | ||||
| 			AudioDevice device, | ||||
| 			StaticSound parent | ||||
| 		) : base(device, parent.FormatTag, parent.BitsPerSample, parent.BlockAlign, parent.Channels, parent.SamplesPerSecond) | ||||
| 		{ | ||||
| 			Parent = parent; | ||||
| 		} | ||||
| 
 | ||||
| 		public override void Play() | ||||
| 		{ | ||||
| 			PlayUsingOperationSet(0); | ||||
| 		} | ||||
| 
 | ||||
| 		public override void QueueSyncPlay() | ||||
| 		{ | ||||
| 			PlayUsingOperationSet(1); | ||||
| 		} | ||||
| 
 | ||||
| 		private void PlayUsingOperationSet(uint operationSet) | ||||
| 		{ | ||||
| 			if (State == SoundState.Playing) | ||||
| 			{ | ||||
| 				return; | ||||
| 			} | ||||
| 
 | ||||
| 			if (Loop) | ||||
| 			{ | ||||
| 				Parent.Handle.LoopCount = 255; | ||||
| 				Parent.Handle.LoopBegin = Parent.LoopStart; | ||||
| 				Parent.Handle.LoopLength = Parent.LoopLength; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				Parent.Handle.LoopCount = 0; | ||||
| 				Parent.Handle.LoopBegin = 0; | ||||
| 				Parent.Handle.LoopLength = 0; | ||||
| 			} | ||||
| 
 | ||||
| 			FAudio.FAudioSourceVoice_SubmitSourceBuffer( | ||||
| 				Voice, | ||||
| 				ref Parent.Handle, | ||||
| 				IntPtr.Zero | ||||
| 			); | ||||
| 
 | ||||
| 			FAudio.FAudioSourceVoice_Start(Voice, 0, operationSet); | ||||
| 			State = SoundState.Playing; | ||||
| 
 | ||||
| 			if (AutoFree) | ||||
| 			{ | ||||
| 				Device.AddAutoFreeStaticSoundInstance(this); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public override void Pause() | ||||
| 		{ | ||||
| 			if (State == SoundState.Playing) | ||||
| 			{ | ||||
| 				FAudio.FAudioSourceVoice_Stop(Voice, 0, 0); | ||||
| 				State = SoundState.Paused; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public override void Stop() | ||||
| 		{ | ||||
| 			FAudio.FAudioSourceVoice_ExitLoop(Voice, 0); | ||||
| 			State = SoundState.Stopped; | ||||
| 		} | ||||
| 
 | ||||
| 		public override void StopImmediate() | ||||
| 		{ | ||||
| 			FAudio.FAudioSourceVoice_Stop(Voice, 0, 0); | ||||
| 			FAudio.FAudioSourceVoice_FlushSourceBuffers(Voice); | ||||
| 			State = SoundState.Stopped; | ||||
| 		} | ||||
| 
 | ||||
| 		public void Seek(uint sampleFrame) | ||||
| 		{ | ||||
| 			if (State == SoundState.Playing) | ||||
| 			{ | ||||
| 				FAudio.FAudioSourceVoice_Stop(Voice, 0, 0); | ||||
| 				FAudio.FAudioSourceVoice_FlushSourceBuffers(Voice); | ||||
| 			} | ||||
| 
 | ||||
| 			Parent.Handle.PlayBegin = sampleFrame; | ||||
| 		} | ||||
| 
 | ||||
| 		// Call this when you no longer need the sound instance. | ||||
| 		// If AutoFree is set, this will automatically be called when the sound instance stops playing. | ||||
| 		// If the sound isn't stopped when you call this, things might get weird! | ||||
| 		public void Free() | ||||
| 		{ | ||||
| 			Parent.FreeInstance(this); | ||||
| 		} | ||||
| 
 | ||||
| 		internal void Reset() | ||||
| 		{ | ||||
| 			Pan = 0; | ||||
| 			Pitch = 0; | ||||
| 			Volume = 1; | ||||
| 			Loop = false; | ||||
| 			Is3D = false; | ||||
| 			FilterType = FilterType.None; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -2,7 +2,7 @@ using System; | |||
| 
 | ||||
| namespace MoonWorks.Audio | ||||
| { | ||||
| 	public class SubmixVoice : SendableVoice, IReceivableVoice | ||||
| 	public class SubmixVoice : Voice | ||||
| 	{ | ||||
| 		public SubmixVoice( | ||||
| 			AudioDevice device, | ||||
|  |  | |||
|  | @ -1,9 +1,10 @@ | |||
| using System; | ||||
| using System.Runtime.InteropServices; | ||||
| using EasingFunction = System.Func<float, float>; | ||||
| 
 | ||||
| namespace MoonWorks.Audio | ||||
| { | ||||
| 	public abstract class Voice : AudioResource | ||||
| 	public abstract unsafe class Voice : AudioResource | ||||
| 	{ | ||||
| 		protected IntPtr handle; | ||||
| 		public IntPtr Handle => handle; | ||||
|  | @ -11,6 +12,11 @@ namespace MoonWorks.Audio | |||
| 		public uint SourceChannelCount { get; } | ||||
| 		public uint DestinationChannelCount { get; } | ||||
| 
 | ||||
| 		private SubmixVoice OutputVoice; | ||||
| 		private ReverbEffect ReverbEffect; | ||||
| 
 | ||||
| 		byte* pMatrixCoefficients; | ||||
| 
 | ||||
| 		public bool Is3D { get; protected set; } | ||||
| 
 | ||||
| 		private float dopplerFactor; | ||||
|  | @ -27,21 +33,6 @@ namespace MoonWorks.Audio | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		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 | ||||
| 		{ | ||||
|  | @ -57,6 +48,21 @@ namespace MoonWorks.Audio | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		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 const float MAX_FILTER_FREQUENCY = 1f; | ||||
| 		private const float MAX_FILTER_ONEOVERQ = 1.5f; | ||||
| 
 | ||||
|  | @ -150,10 +156,89 @@ namespace MoonWorks.Audio | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		protected float pan = 0; | ||||
| 		public 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, | ||||
| 						OutputVoice.Handle, | ||||
| 						SourceChannelCount, | ||||
| 						DestinationChannelCount, | ||||
| 						(nint) pMatrixCoefficients, | ||||
| 						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*) pMatrixCoefficients; | ||||
| 						outputMatrix[0] = reverb; | ||||
| 						if (SourceChannelCount == 2) | ||||
| 						{ | ||||
| 							outputMatrix[1] = reverb; | ||||
| 						} | ||||
| 
 | ||||
| 						FAudio.FAudioVoice_SetOutputMatrix( | ||||
| 							Handle, | ||||
| 							ReverbEffect.Handle, | ||||
| 							SourceChannelCount, | ||||
| 							1, | ||||
| 							(nint) pMatrixCoefficients, | ||||
| 							0 | ||||
| 						); | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				#if DEBUG | ||||
| 				if (ReverbEffect == null) | ||||
| 				{ | ||||
| 					Logger.LogWarn("Tried to set reverb value before applying a reverb effect"); | ||||
| 				} | ||||
| 				#endif | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public Voice(AudioDevice device, uint sourceChannelCount, uint destinationChannelCount) : base(device) | ||||
| 		{ | ||||
| 			SourceChannelCount = sourceChannelCount; | ||||
| 			DestinationChannelCount = destinationChannelCount; | ||||
| 			OutputVoice = device.MasteringVoice; | ||||
| 			nuint memsize = 4 * sourceChannelCount * destinationChannelCount; | ||||
| 			pMatrixCoefficients = (byte*) NativeMemory.AllocZeroed(memsize); | ||||
| 			SetPanMatrixCoefficients(); | ||||
| 		} | ||||
| 
 | ||||
| 		public void SetPitch(float targetValue) | ||||
|  | @ -209,6 +294,159 @@ namespace MoonWorks.Audio | |||
| 			FilterOneOverQ = targetValue; | ||||
| 		} | ||||
| 
 | ||||
| 		public virtual void SetPan(float targetValue) | ||||
| 		{ | ||||
| 			Pan = targetValue; | ||||
| 			Device.ClearTweens(this, AudioTweenProperty.Pan); | ||||
| 		} | ||||
| 
 | ||||
| 		public virtual void SetPan(float targetValue, float duration, EasingFunction easingFunction) | ||||
| 		{ | ||||
| 			Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, 0); | ||||
| 		} | ||||
| 
 | ||||
| 		public virtual void SetPan(float targetValue, float delayTime, float duration, EasingFunction easingFunction) | ||||
| 		{ | ||||
| 			Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, delayTime); | ||||
| 		} | ||||
| 
 | ||||
| 		public virtual void SetReverb(float targetValue) | ||||
| 		{ | ||||
| 			Reverb = targetValue; | ||||
| 			Device.ClearTweens(this, AudioTweenProperty.Reverb); | ||||
| 		} | ||||
| 
 | ||||
| 		public virtual void SetReverb(float targetValue, float duration, EasingFunction easingFunction) | ||||
| 		{ | ||||
| 			Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, 0); | ||||
| 		} | ||||
| 
 | ||||
| 		public virtual void SetReverb(float targetValue, float delayTime, float duration, EasingFunction easingFunction) | ||||
| 		{ | ||||
| 			Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, delayTime); | ||||
| 		} | ||||
| 
 | ||||
| 		public unsafe void SetOutputVoice(SubmixVoice send) | ||||
| 		{ | ||||
| 			OutputVoice = send; | ||||
| 
 | ||||
| 			if (ReverbEffect != null) | ||||
| 			{ | ||||
| 				SetReverbEffectChain(ReverbEffect); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				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 SetReverbEffectChain(ReverbEffect reverbEffect) | ||||
| 		{ | ||||
| 			var sendDesc = stackalloc FAudio.FAudioSendDescriptor[2]; | ||||
| 			sendDesc[0].Flags = 0; | ||||
| 			sendDesc[0].pOutputVoice = OutputVoice.Handle; | ||||
| 			sendDesc[1].Flags = 0; | ||||
| 			sendDesc[1].pOutputVoice = reverbEffect.Handle; | ||||
| 
 | ||||
| 			var sends = new FAudio.FAudioVoiceSends(); | ||||
| 			sends.SendCount = 2; | ||||
| 			sends.pSends = (nint) sendDesc; | ||||
| 
 | ||||
| 			FAudio.FAudioVoice_SetOutputVoices( | ||||
| 				Handle, | ||||
| 				ref sends | ||||
| 			); | ||||
| 
 | ||||
| 			ReverbEffect = reverbEffect; | ||||
| 		} | ||||
| 
 | ||||
| 		public void RemoveReverbEffectChain() | ||||
| 		{ | ||||
| 			if (ReverbEffect != null) | ||||
| 			{ | ||||
| 				ReverbEffect = null; | ||||
| 				reverb = 0; | ||||
| 				SetOutputVoice(OutputVoice); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// 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*) pMatrixCoefficients; | ||||
| 			if (SourceChannelCount == 1) | ||||
| 			{ | ||||
| 				if (DestinationChannelCount == 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 (DestinationChannelCount == 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; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public virtual void Reset() | ||||
| 		{ | ||||
| 			RemoveReverbEffectChain(); | ||||
| 			Volume = 1; | ||||
| 			Pan = 0; | ||||
| 			Pitch = 0; | ||||
| 			FilterType = FilterType.None; | ||||
| 			FilterFrequency = 1; | ||||
| 			FilterOneOverQ = 1; | ||||
| 			SetOutputVoice(Device.MasteringVoice); | ||||
| 		} | ||||
| 
 | ||||
| 		private void UpdatePitch() | ||||
| 		{ | ||||
| 			float doppler; | ||||
|  | @ -231,6 +469,7 @@ namespace MoonWorks.Audio | |||
| 
 | ||||
| 		protected unsafe override void Destroy() | ||||
| 		{ | ||||
| 			NativeMemory.Free(pMatrixCoefficients); | ||||
| 			FAudio.FAudioVoice_DestroyVoice(Handle); | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue