faux mastering voice + submitting static sounds
							parent
							
								
									e2c85ec728
								
							
						
					
					
						commit
						c2cb83f93f
					
				|  | @ -10,8 +10,12 @@ namespace MoonWorks.Audio | ||||||
| 		public byte[] Handle3D { get; } | 		public byte[] Handle3D { get; } | ||||||
| 		public FAudio.FAudioDeviceDetails DeviceDetails { get; } | 		public FAudio.FAudioDeviceDetails DeviceDetails { get; } | ||||||
| 
 | 
 | ||||||
| 		private MasteringVoice masteringVoice; | 		private IntPtr trueMasteringVoice; | ||||||
| 		public MasteringVoice MasteringVoice => masteringVoice; | 
 | ||||||
|  | 		// 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 CurveDistanceScalar = 1f; | ||||||
| 		public float DopplerScale = 1f; | 		public float DopplerScale = 1f; | ||||||
|  | @ -19,8 +23,8 @@ namespace MoonWorks.Audio | ||||||
| 
 | 
 | ||||||
| 		private readonly HashSet<WeakReference> resources = new HashSet<WeakReference>(); | 		private readonly HashSet<WeakReference> resources = new HashSet<WeakReference>(); | ||||||
| 		private readonly List<StreamingSound> autoUpdateStreamingSoundReferences = new List<StreamingSound>(); | 		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<WeakReference<SoundSequence>> soundSequenceReferences = new List<WeakReference<SoundSequence>>(); | ||||||
|  | 		private readonly List<SourceVoice> autoFreeSourceVoices = new List<SourceVoice>(); | ||||||
| 
 | 
 | ||||||
| 		private AudioTweenManager AudioTweenManager; | 		private AudioTweenManager AudioTweenManager; | ||||||
| 
 | 
 | ||||||
|  | @ -85,18 +89,25 @@ namespace MoonWorks.Audio | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			/* Init Mastering Voice */ | 			/* Init Mastering Voice */ | ||||||
| 			var result = MasteringVoice.Create( | 			var result = FAudio.FAudio_CreateMasteringVoice( | ||||||
| 				this, | 				Handle, | ||||||
|  | 				out trueMasteringVoice, | ||||||
|  | 				FAudio.FAUDIO_DEFAULT_CHANNELS, | ||||||
|  | 				FAudio.FAUDIO_DEFAULT_SAMPLERATE, | ||||||
|  | 				0, | ||||||
| 				i, | 				i, | ||||||
| 				out masteringVoice | 				IntPtr.Zero | ||||||
| 			); | 			); | ||||||
| 
 | 
 | ||||||
| 			if (!result) | 			if (result != 0) | ||||||
| 			{ | 			{ | ||||||
|  | 				Logger.LogError("Failed to create a mastering voice!"); | ||||||
| 				Logger.LogError("Audio device creation failed!"); | 				Logger.LogError("Audio device creation failed!"); | ||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | 			fauxMasteringVoice = new SubmixVoice(this, FAudio.FAUDIO_DEFAULT_CHANNELS, FAudio.FAUDIO_DEFAULT_SAMPLERATE); | ||||||
|  | 
 | ||||||
| 			/* Init 3D Audio */ | 			/* Init 3D Audio */ | ||||||
| 
 | 
 | ||||||
| 			Handle3D = new byte[FAudio.F3DAUDIO_HANDLE_BYTESIZE]; | 			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) | 			for (var i = soundSequenceReferences.Count - 1; i >= 0; i -= 1) | ||||||
| 			{ | 			{ | ||||||
| 				if (soundSequenceReferences[i].TryGetTarget(out var soundSequence)) | 				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); | 			AudioTweenManager.Update(elapsedSeconds); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		/// <summary> | ||||||
|  | 		/// Triggers all pending operations with the given syncGroup value. | ||||||
|  | 		/// </summary> | ||||||
| 		public void TriggerSyncGroup(uint syncGroup) | 		public void TriggerSyncGroup(uint syncGroup) | ||||||
| 		{ | 		{ | ||||||
| 			FAudio.FAudio_CommitChanges(Handle, 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( | 		internal void CreateTween( | ||||||
| 			Voice voice, | 			Voice voice, | ||||||
| 			AudioTweenProperty property, | 			AudioTweenProperty property, | ||||||
|  | @ -252,26 +299,11 @@ namespace MoonWorks.Audio | ||||||
| 			autoUpdateStreamingSoundReferences.Add(instance); | 			autoUpdateStreamingSoundReferences.Add(instance); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		internal void AddAutoFreeStaticSoundInstance(StaticSoundInstance instance) |  | ||||||
| 		{ |  | ||||||
| 			autoFreeStaticSoundInstanceReferences.Add(instance); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		internal void AddSoundSequenceReference(SoundSequence sequence) | 		internal void AddSoundSequenceReference(SoundSequence sequence) | ||||||
| 		{ | 		{ | ||||||
| 			soundSequenceReferences.Add(new WeakReference<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) | 		protected virtual void Dispose(bool disposing) | ||||||
| 		{ | 		{ | ||||||
| 			if (!IsDisposed) | 			if (!IsDisposed) | ||||||
|  | @ -306,6 +338,7 @@ namespace MoonWorks.Audio | ||||||
| 					resources.Clear(); | 					resources.Clear(); | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
|  | 				FAudio.FAudioVoice_DestroyVoice(trueMasteringVoice); | ||||||
| 				FAudio.FAudio_Release(Handle); | 				FAudio.FAudio_Release(Handle); | ||||||
| 
 | 
 | ||||||
| 				IsDisposed = true; | 				IsDisposed = true; | ||||||
|  |  | ||||||
|  | @ -24,10 +24,7 @@ namespace MoonWorks.Audio | ||||||
| 					switch (audioTween.Property) | 					switch (audioTween.Property) | ||||||
| 					{ | 					{ | ||||||
| 						case AudioTweenProperty.Pan: | 						case AudioTweenProperty.Pan: | ||||||
| 							if (voice is SendableVoice pannableVoice) | 							audioTween.StartValue = voice.Pan; | ||||||
| 							{ |  | ||||||
| 								audioTween.StartValue = pannableVoice.Pan; |  | ||||||
| 							} |  | ||||||
| 							break; | 							break; | ||||||
| 
 | 
 | ||||||
| 						case AudioTweenProperty.Pitch: | 						case AudioTweenProperty.Pitch: | ||||||
|  | @ -43,10 +40,7 @@ namespace MoonWorks.Audio | ||||||
| 							break; | 							break; | ||||||
| 
 | 
 | ||||||
| 						case AudioTweenProperty.Reverb: | 						case AudioTweenProperty.Reverb: | ||||||
| 							if (voice is SendableVoice reverbableVoice) | 							audioTween.StartValue = voice.Reverb; | ||||||
| 							{ |  | ||||||
| 								audioTween.StartValue = reverbableVoice.Reverb; |  | ||||||
| 							} |  | ||||||
| 							break; | 							break; | ||||||
| 					} | 					} | ||||||
| 
 | 
 | ||||||
|  | @ -139,10 +133,7 @@ namespace MoonWorks.Audio | ||||||
| 			switch (audioTween.Property) | 			switch (audioTween.Property) | ||||||
| 			{ | 			{ | ||||||
| 				case AudioTweenProperty.Pan: | 				case AudioTweenProperty.Pan: | ||||||
| 					if (audioTween.Voice is SendableVoice pannableVoice) | 					audioTween.Voice.Pan = value; | ||||||
| 					{ |  | ||||||
| 						pannableVoice.Pan = value; |  | ||||||
| 					} |  | ||||||
| 					break; | 					break; | ||||||
| 
 | 
 | ||||||
| 				case AudioTweenProperty.Pitch: | 				case AudioTweenProperty.Pitch: | ||||||
|  | @ -158,10 +149,7 @@ namespace MoonWorks.Audio | ||||||
| 					break; | 					break; | ||||||
| 
 | 
 | ||||||
| 				case AudioTweenProperty.Reverb: | 				case AudioTweenProperty.Reverb: | ||||||
| 					if (audioTween.Voice is SendableVoice reverbableVoice) | 					audioTween.Voice.Reverb = value; | ||||||
| 					{ |  | ||||||
| 						reverbableVoice.Reverb = value; |  | ||||||
| 					} |  | ||||||
| 					break; | 					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) | 			lock (StateLock) | ||||||
| 			{ | 			{ | ||||||
| 				FAudio.FAudioSourceVoice_SubmitSourceBuffer( | 				Submit(sound); | ||||||
| 					Handle, |  | ||||||
| 					ref sound.Handle, |  | ||||||
| 					IntPtr.Zero |  | ||||||
| 				); |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -2,7 +2,10 @@ using System; | ||||||
| 
 | 
 | ||||||
| namespace MoonWorks.Audio | namespace MoonWorks.Audio | ||||||
| { | { | ||||||
| 	public class SourceVoice : SendableVoice | 	/// <summary> | ||||||
|  | 	/// Emits audio from submitted audio buffers. | ||||||
|  | 	/// </summary> | ||||||
|  | 	public class SourceVoice : Voice | ||||||
| 	{ | 	{ | ||||||
| 		private Format format; | 		private Format format; | ||||||
| 		public Format Format => format; | 		public Format Format => format; | ||||||
|  | @ -55,7 +58,7 @@ namespace MoonWorks.Audio | ||||||
| 
 | 
 | ||||||
| 			 FAudio.FAudio_CreateSourceVoice( | 			 FAudio.FAudio_CreateSourceVoice( | ||||||
| 				device.Handle, | 				device.Handle, | ||||||
| 				out var Handle, | 				out handle, | ||||||
| 				ref fAudioFormat, | 				ref fAudioFormat, | ||||||
| 				FAudio.FAUDIO_VOICE_USEFILTER, | 				FAudio.FAUDIO_VOICE_USEFILTER, | ||||||
| 				FAudio.FAUDIO_DEFAULT_FREQ_RATIO, | 				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) | 		public void Play(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW) | ||||||
| 		{ | 		{ | ||||||
| 			lock (StateLock) | 			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) | 		public void Pause(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW) | ||||||
| 		{ | 		{ | ||||||
| 			lock (StateLock) | 			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) | 		public void ExitLoop(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW) | ||||||
| 		{ | 		{ | ||||||
| 			lock (StateLock) | 			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) | 		public void Stop(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW) | ||||||
| 		{ | 		{ | ||||||
| 			lock (StateLock) | 			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( | 			FAudio.FAudioSourceVoice_SubmitSourceBuffer( | ||||||
| 				Handle, | 				Handle, | ||||||
|  | @ -113,31 +160,27 @@ namespace MoonWorks.Audio | ||||||
| 			); | 			); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// FIXME: maybe this is bad | 		public void Submit(StreamingSound streamingSound) | ||||||
| 		// NOTE: SourceVoices obtained this way will be returned to the voice pool when stopped! |  | ||||||
| 		public static SourceVoice ObtainSourceVoice(AudioDevice device, Format format) |  | ||||||
| 		{ | 		{ | ||||||
| 			return device.ObtainSourceVoice(format); | 
 | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// intended for short-lived sound effects | 		/// <summary> | ||||||
| 		public static SourceVoice PlayStaticSound(AudioDevice device, StaticSound sound, SubmixVoice sendVoice = null) | 		/// 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) | 		/// <summary> | ||||||
| 			{ | 		/// Returns this source voice to the voice pool. | ||||||
| 				voice.SetOutputVoice(device.MasteringVoice); | 		/// </summary> | ||||||
| 			} | 		public void Return() | ||||||
| 			else | 		{ | ||||||
| 			{ | 			Stop(); | ||||||
| 				voice.SetOutputVoice(sendVoice); | 			Reset(); | ||||||
| 			} | 			Device.Return(this); | ||||||
| 
 |  | ||||||
| 			voice.SubmitBuffer(sound.Handle); |  | ||||||
| 			voice.Play(); |  | ||||||
| 
 |  | ||||||
| 			return voice; |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		protected override unsafe void Destroy() | 		protected override unsafe void Destroy() | ||||||
|  |  | ||||||
|  | @ -1,5 +1,4 @@ | ||||||
| using System; | using System; | ||||||
| using System.Collections.Generic; |  | ||||||
| using System.IO; | using System.IO; | ||||||
| using System.Runtime.InteropServices; | using System.Runtime.InteropServices; | ||||||
| 
 | 
 | ||||||
|  | @ -7,17 +6,11 @@ namespace MoonWorks.Audio | ||||||
| { | { | ||||||
| 	public class StaticSound : AudioResource | 	public class StaticSound : AudioResource | ||||||
| 	{ | 	{ | ||||||
| 		internal FAudio.FAudioBuffer Handle; | 		internal FAudio.FAudioBuffer Buffer; | ||||||
| 
 | 
 | ||||||
| 		private Format format; | 		private Format format; | ||||||
| 		public Format 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; | 		private bool OwnsBuffer; | ||||||
| 
 | 
 | ||||||
| 		public static unsafe StaticSound LoadOgg(AudioDevice device, string filePath) | 		public static unsafe StaticSound LoadOgg(AudioDevice device, string filePath) | ||||||
|  | @ -253,10 +246,9 @@ namespace MoonWorks.Audio | ||||||
| 			uint bufferLengthInBytes, | 			uint bufferLengthInBytes, | ||||||
| 			bool ownsBuffer) : base(device) | 			bool ownsBuffer) : base(device) | ||||||
| 		{ | 		{ | ||||||
| 			// TODO: should we wrap the format struct to make it nicer? |  | ||||||
| 			this.format = format; | 			this.format = format; | ||||||
| 
 | 
 | ||||||
| 			Handle = new FAudio.FAudioBuffer | 			Buffer = new FAudio.FAudioBuffer | ||||||
| 			{ | 			{ | ||||||
| 				Flags = FAudio.FAUDIO_END_OF_STREAM, | 				Flags = FAudio.FAUDIO_END_OF_STREAM, | ||||||
| 				pContext = IntPtr.Zero, | 				pContext = IntPtr.Zero, | ||||||
|  | @ -269,66 +261,11 @@ namespace MoonWorks.Audio | ||||||
| 			OwnsBuffer = ownsBuffer; | 			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() | 		protected override unsafe void Destroy() | ||||||
| 		{ | 		{ | ||||||
| 			foreach (var instance in UsedInstances) |  | ||||||
| 			{ |  | ||||||
| 				instance.Free(); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			foreach (var instance in AvailableInstances) |  | ||||||
| 			{ |  | ||||||
| 				instance.Dispose(); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			AvailableInstances.Clear(); |  | ||||||
| 
 |  | ||||||
| 			if (OwnsBuffer) | 			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 | namespace MoonWorks.Audio | ||||||
| { | { | ||||||
| 	public class SubmixVoice : SendableVoice, IReceivableVoice | 	public class SubmixVoice : Voice | ||||||
| 	{ | 	{ | ||||||
| 		public SubmixVoice( | 		public SubmixVoice( | ||||||
| 			AudioDevice device, | 			AudioDevice device, | ||||||
|  |  | ||||||
|  | @ -1,9 +1,10 @@ | ||||||
| using System; | using System; | ||||||
|  | using System.Runtime.InteropServices; | ||||||
| using EasingFunction = System.Func<float, float>; | using EasingFunction = System.Func<float, float>; | ||||||
| 
 | 
 | ||||||
| namespace MoonWorks.Audio | namespace MoonWorks.Audio | ||||||
| { | { | ||||||
| 	public abstract class Voice : AudioResource | 	public abstract unsafe class Voice : AudioResource | ||||||
| 	{ | 	{ | ||||||
| 		protected IntPtr handle; | 		protected IntPtr handle; | ||||||
| 		public IntPtr Handle => handle; | 		public IntPtr Handle => handle; | ||||||
|  | @ -11,6 +12,11 @@ namespace MoonWorks.Audio | ||||||
| 		public uint SourceChannelCount { get; } | 		public uint SourceChannelCount { get; } | ||||||
| 		public uint DestinationChannelCount { get; } | 		public uint DestinationChannelCount { get; } | ||||||
| 
 | 
 | ||||||
|  | 		private SubmixVoice OutputVoice; | ||||||
|  | 		private ReverbEffect ReverbEffect; | ||||||
|  | 
 | ||||||
|  | 		byte* pMatrixCoefficients; | ||||||
|  | 
 | ||||||
| 		public bool Is3D { get; protected set; } | 		public bool Is3D { get; protected set; } | ||||||
| 
 | 
 | ||||||
| 		private float dopplerFactor; | 		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; | 		private float volume = 1; | ||||||
| 		public float Volume | 		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_FREQUENCY = 1f; | ||||||
| 		private const float MAX_FILTER_ONEOVERQ = 1.5f; | 		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) | 		public Voice(AudioDevice device, uint sourceChannelCount, uint destinationChannelCount) : base(device) | ||||||
| 		{ | 		{ | ||||||
| 			SourceChannelCount = sourceChannelCount; | 			SourceChannelCount = sourceChannelCount; | ||||||
| 			DestinationChannelCount = destinationChannelCount; | 			DestinationChannelCount = destinationChannelCount; | ||||||
|  | 			OutputVoice = device.MasteringVoice; | ||||||
|  | 			nuint memsize = 4 * sourceChannelCount * destinationChannelCount; | ||||||
|  | 			pMatrixCoefficients = (byte*) NativeMemory.AllocZeroed(memsize); | ||||||
|  | 			SetPanMatrixCoefficients(); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public void SetPitch(float targetValue) | 		public void SetPitch(float targetValue) | ||||||
|  | @ -209,6 +294,159 @@ namespace MoonWorks.Audio | ||||||
| 			FilterOneOverQ = targetValue; | 			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() | 		private void UpdatePitch() | ||||||
| 		{ | 		{ | ||||||
| 			float doppler; | 			float doppler; | ||||||
|  | @ -231,6 +469,7 @@ namespace MoonWorks.Audio | ||||||
| 
 | 
 | ||||||
| 		protected unsafe override void Destroy() | 		protected unsafe override void Destroy() | ||||||
| 		{ | 		{ | ||||||
|  | 			NativeMemory.Free(pMatrixCoefficients); | ||||||
| 			FAudio.FAudioVoice_DestroyVoice(Handle); | 			FAudio.FAudioVoice_DestroyVoice(Handle); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue