forked from MoonsideGames/MoonWorks
				
			Audio Improvements (#47)
- Audio is now processed on a background thread instead of the main thread - Audio tick rate is now ~200Hz - MoonWorks.Math.Easings class completely rewritten to be easier to understand and use - SoundInstance properties no longer call into FAudio unless the value actually changed - SoundInstance property values can now be interpolated over time (tweens) - SoundInstance tweens can be delayed - SoundInstance sets a sane filter frequency default when switching filter type - StreamingSound classes can be designated to update automatically on the audio thread or manually - StreamingSound buffer consumption should now set Stopped state in a more sane way - Added ReverbEffect, which creates a submix voice for a reverb effect - SoundInstance can apply a ReverbEffect, which enables the Reverb property - Audio resource tracking improvements - Some tweaks to VideoPlayer to make its behavior more consistent Reviewed-on: MoonsideGames/MoonWorks#47remotes/1695061714407202320/main
							parent
							
								
									f8b14ea94f
								
							
						
					
					
						commit
						1f0e3b5040
					
				|  | @ -1,6 +1,6 @@ | ||||||
| using System; | using System; | ||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
| using System.Runtime.InteropServices; | using System.Threading; | ||||||
| 
 | 
 | ||||||
| namespace MoonWorks.Audio | namespace MoonWorks.Audio | ||||||
| { | { | ||||||
|  | @ -26,13 +26,25 @@ namespace MoonWorks.Audio | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		private readonly List<WeakReference<AudioResource>> resources = new List<WeakReference<AudioResource>>(); | 		private readonly HashSet<WeakReference> resources = new HashSet<WeakReference>(); | ||||||
| 		private readonly List<WeakReference<StreamingSound>> streamingSounds = new List<WeakReference<StreamingSound>>(); | 		private readonly HashSet<WeakReference> autoUpdateStreamingSoundReferences = new HashSet<WeakReference>(); | ||||||
|  | 
 | ||||||
|  | 		private AudioTweenManager AudioTweenManager; | ||||||
|  | 
 | ||||||
|  | 		private const int Step = 200; | ||||||
|  | 		private TimeSpan UpdateInterval; | ||||||
|  | 		private System.Diagnostics.Stopwatch TickStopwatch = new System.Diagnostics.Stopwatch(); | ||||||
|  | 		private long previousTickTime; | ||||||
|  | 		private Thread Thread; | ||||||
|  | 		private AutoResetEvent WakeSignal; | ||||||
|  | 		internal readonly object StateLock = new object(); | ||||||
| 
 | 
 | ||||||
| 		private bool IsDisposed; | 		private bool IsDisposed; | ||||||
| 
 | 
 | ||||||
| 		public unsafe AudioDevice() | 		public unsafe AudioDevice() | ||||||
| 		{ | 		{ | ||||||
|  | 			UpdateInterval = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / Step); | ||||||
|  | 
 | ||||||
| 			FAudio.FAudioCreate(out var handle, 0, FAudio.FAUDIO_DEFAULT_PROCESSOR); | 			FAudio.FAudioCreate(out var handle, 0, FAudio.FAUDIO_DEFAULT_PROCESSOR); | ||||||
| 			Handle = handle; | 			Handle = handle; | ||||||
| 
 | 
 | ||||||
|  | @ -90,8 +102,8 @@ namespace MoonWorks.Audio | ||||||
| 			) != 0) | 			) != 0) | ||||||
| 			{ | 			{ | ||||||
| 				Logger.LogError("No mastering voice found!"); | 				Logger.LogError("No mastering voice found!"); | ||||||
| 				Handle = IntPtr.Zero; |  | ||||||
| 				FAudio.FAudio_Release(Handle); | 				FAudio.FAudio_Release(Handle); | ||||||
|  | 				Handle = IntPtr.Zero; | ||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | @ -105,22 +117,52 @@ namespace MoonWorks.Audio | ||||||
| 				SpeedOfSound, | 				SpeedOfSound, | ||||||
| 				Handle3D | 				Handle3D | ||||||
| 			); | 			); | ||||||
|  | 
 | ||||||
|  | 			AudioTweenManager = new AudioTweenManager(); | ||||||
|  | 
 | ||||||
|  | 			Logger.LogInfo("Setting up audio thread..."); | ||||||
|  | 			WakeSignal = new AutoResetEvent(true); | ||||||
|  | 
 | ||||||
|  | 			Thread = new Thread(ThreadMain); | ||||||
|  | 			Thread.IsBackground = true; | ||||||
|  | 			Thread.Start(); | ||||||
|  | 
 | ||||||
|  | 			TickStopwatch.Start(); | ||||||
|  | 			previousTickTime = 0; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		internal void Update() | 		private void ThreadMain() | ||||||
| 		{ | 		{ | ||||||
| 			for (var i = streamingSounds.Count - 1; i >= 0; i--) | 			while (!IsDisposed) | ||||||
| 			{ | 			{ | ||||||
| 				var weakReference = streamingSounds[i]; | 				lock (StateLock) | ||||||
| 				if (weakReference.TryGetTarget(out var streamingSound)) | 				{ | ||||||
|  | 					ThreadMainTick(); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				WakeSignal.WaitOne(UpdateInterval); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		private void ThreadMainTick() | ||||||
|  | 		{ | ||||||
|  | 			long tickDelta = TickStopwatch.Elapsed.Ticks - previousTickTime; | ||||||
|  | 			previousTickTime = TickStopwatch.Elapsed.Ticks; | ||||||
|  | 			float elapsedSeconds = (float) tickDelta / System.TimeSpan.TicksPerSecond; | ||||||
|  | 
 | ||||||
|  | 			foreach (var weakReference in autoUpdateStreamingSoundReferences) | ||||||
|  | 			{ | ||||||
|  | 				if (weakReference.Target is StreamingSound streamingSound) | ||||||
| 				{ | 				{ | ||||||
| 					streamingSound.Update(); | 					streamingSound.Update(); | ||||||
| 				} | 				} | ||||||
| 				else | 				else | ||||||
| 				{ | 				{ | ||||||
| 					streamingSounds.RemoveAt(i); | 					autoUpdateStreamingSoundReferences.Remove(weakReference); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | 
 | ||||||
|  | 			AudioTweenManager.Update(elapsedSeconds); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public void SyncPlay() | 		public void SyncPlay() | ||||||
|  | @ -128,40 +170,95 @@ namespace MoonWorks.Audio | ||||||
| 			FAudio.FAudio_CommitChanges(Handle, 1); | 			FAudio.FAudio_CommitChanges(Handle, 1); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		internal void AddDynamicSoundInstance(StreamingSound instance) | 		internal void CreateTween( | ||||||
|  | 			SoundInstance soundInstance, | ||||||
|  | 			AudioTweenProperty property, | ||||||
|  | 			System.Func<float, float> easingFunction, | ||||||
|  | 			float start, | ||||||
|  | 			float end, | ||||||
|  | 			float duration, | ||||||
|  | 			float delayTime | ||||||
|  | 		) { | ||||||
|  | 			lock (StateLock) | ||||||
| 			{ | 			{ | ||||||
| 			streamingSounds.Add(new WeakReference<StreamingSound>(instance)); | 				AudioTweenManager.CreateTween( | ||||||
| 		} | 					soundInstance, | ||||||
| 
 | 					property, | ||||||
| 		internal void AddResourceReference(WeakReference<AudioResource> resourceReference) | 					easingFunction, | ||||||
| 		{ | 					start, | ||||||
| 			lock (resources) | 					end, | ||||||
| 			{ | 					duration, | ||||||
| 				resources.Add(resourceReference); | 					delayTime | ||||||
|  | 				); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		internal void RemoveResourceReference(WeakReference<AudioResource> resourceReference) | 		internal void ClearTweens( | ||||||
|  | 			WeakReference soundReference, | ||||||
|  | 			AudioTweenProperty property | ||||||
|  | 		) { | ||||||
|  | 			lock (StateLock) | ||||||
| 			{ | 			{ | ||||||
| 			lock (resources) | 				AudioTweenManager.ClearTweens(soundReference, property); | ||||||
| 			{ |  | ||||||
| 				resources.Remove(resourceReference); |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		internal void WakeThread() | ||||||
|  | 		{ | ||||||
|  | 			WakeSignal.Set(); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		internal void AddResourceReference(AudioResource resource) | ||||||
|  | 		{ | ||||||
|  | 			lock (StateLock) | ||||||
|  | 			{ | ||||||
|  | 				resources.Add(resource.weakReference); | ||||||
|  | 
 | ||||||
|  | 				if (resource is StreamingSound streamingSound && streamingSound.AutoUpdate) | ||||||
|  | 				{ | ||||||
|  | 					AddAutoUpdateStreamingSoundInstance(streamingSound); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		internal void RemoveResourceReference(AudioResource resource) | ||||||
|  | 		{ | ||||||
|  | 			lock (StateLock) | ||||||
|  | 			{ | ||||||
|  | 				resources.Remove(resource.weakReference); | ||||||
|  | 
 | ||||||
|  | 				if (resource is StreamingSound streamingSound && streamingSound.AutoUpdate) | ||||||
|  | 				{ | ||||||
|  | 					RemoveAutoUpdateStreamingSoundInstance(streamingSound); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		private void AddAutoUpdateStreamingSoundInstance(StreamingSound instance) | ||||||
|  | 		{ | ||||||
|  | 			autoUpdateStreamingSoundReferences.Add(instance.weakReference); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		private void RemoveAutoUpdateStreamingSoundInstance(StreamingSound instance) | ||||||
|  | 		{ | ||||||
|  | 			autoUpdateStreamingSoundReferences.Remove(instance.weakReference); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		protected virtual void Dispose(bool disposing) | 		protected virtual void Dispose(bool disposing) | ||||||
| 		{ | 		{ | ||||||
| 			if (!IsDisposed) | 			if (!IsDisposed) | ||||||
|  | 			{ | ||||||
|  | 				lock (StateLock) | ||||||
| 				{ | 				{ | ||||||
| 					if (disposing) | 					if (disposing) | ||||||
| 					{ | 					{ | ||||||
| 					for (var i = resources.Count - 1; i >= 0; i--) | 						foreach (var weakReference in resources) | ||||||
| 						{ | 						{ | ||||||
| 						var weakReference = resources[i]; | 							var target = weakReference.Target; | ||||||
| 
 | 
 | ||||||
| 						if (weakReference.TryGetTarget(out var resource)) | 							if (target != null) | ||||||
| 							{ | 							{ | ||||||
| 							resource.Dispose(); | 								(target as IDisposable).Dispose(); | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
| 						resources.Clear(); | 						resources.Clear(); | ||||||
|  | @ -173,8 +270,8 @@ namespace MoonWorks.Audio | ||||||
| 					IsDisposed = true; | 					IsDisposed = true; | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 		// TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources |  | ||||||
| 		~AudioDevice() | 		~AudioDevice() | ||||||
| 		{ | 		{ | ||||||
| 			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | 			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||||||
|  |  | ||||||
|  | @ -8,14 +8,14 @@ namespace MoonWorks.Audio | ||||||
| 
 | 
 | ||||||
| 		public bool IsDisposed { get; private set; } | 		public bool IsDisposed { get; private set; } | ||||||
| 
 | 
 | ||||||
| 		private WeakReference<AudioResource> selfReference; | 		internal WeakReference weakReference; | ||||||
| 
 | 
 | ||||||
| 		public AudioResource(AudioDevice device) | 		public AudioResource(AudioDevice device) | ||||||
| 		{ | 		{ | ||||||
| 			Device = device; | 			Device = device; | ||||||
| 
 | 
 | ||||||
| 			selfReference = new WeakReference<AudioResource>(this); | 			weakReference = new WeakReference(this); | ||||||
| 			Device.AddResourceReference(selfReference); | 			Device.AddResourceReference(this); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		protected abstract void Destroy(); | 		protected abstract void Destroy(); | ||||||
|  | @ -26,10 +26,10 @@ namespace MoonWorks.Audio | ||||||
| 			{ | 			{ | ||||||
| 				Destroy(); | 				Destroy(); | ||||||
| 
 | 
 | ||||||
| 				if (selfReference != null) | 				if (weakReference != null) | ||||||
| 				{ | 				{ | ||||||
| 					Device.RemoveResourceReference(selfReference); | 					Device.RemoveResourceReference(this); | ||||||
| 					selfReference = null; | 					weakReference = null; | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				IsDisposed = true; | 				IsDisposed = true; | ||||||
|  |  | ||||||
|  | @ -0,0 +1,57 @@ | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using EasingFunction = System.Func<float, float>; | ||||||
|  | 
 | ||||||
|  | namespace MoonWorks.Audio | ||||||
|  | { | ||||||
|  | 	internal enum AudioTweenProperty | ||||||
|  | 	{ | ||||||
|  | 		Pan, | ||||||
|  | 		Pitch, | ||||||
|  | 		Volume, | ||||||
|  | 		FilterFrequency, | ||||||
|  | 		Reverb | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	internal class AudioTween | ||||||
|  | 	{ | ||||||
|  | 		public System.WeakReference SoundInstanceReference; | ||||||
|  | 		public AudioTweenProperty Property; | ||||||
|  | 		public EasingFunction EasingFunction; | ||||||
|  | 		public float Time; | ||||||
|  | 		public float StartValue; | ||||||
|  | 		public float EndValue; | ||||||
|  | 		public float DelayTime; | ||||||
|  | 		public float Duration; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	internal class AudioTweenPool | ||||||
|  | 	{ | ||||||
|  | 		private Queue<AudioTween> Tweens = new Queue<AudioTween>(16); | ||||||
|  | 
 | ||||||
|  | 		public AudioTweenPool() | ||||||
|  | 		{ | ||||||
|  | 			for (int i = 0; i < 16; i += 1) | ||||||
|  | 			{ | ||||||
|  | 				Tweens.Enqueue(new AudioTween()); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public AudioTween Obtain() | ||||||
|  | 		{ | ||||||
|  | 			if (Tweens.Count > 0) | ||||||
|  | 			{ | ||||||
|  | 				var tween = Tweens.Dequeue(); | ||||||
|  | 				return tween; | ||||||
|  | 			} | ||||||
|  | 			else | ||||||
|  | 			{ | ||||||
|  | 				return new AudioTween(); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void Free(AudioTween tween) | ||||||
|  | 		{ | ||||||
|  | 			Tweens.Enqueue(tween); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,169 @@ | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | 
 | ||||||
|  | namespace MoonWorks.Audio | ||||||
|  | { | ||||||
|  | 	internal class AudioTweenManager | ||||||
|  | 	{ | ||||||
|  | 		private AudioTweenPool AudioTweenPool = new AudioTweenPool(); | ||||||
|  | 		private readonly Dictionary<(WeakReference, AudioTweenProperty), AudioTween> AudioTweens = new Dictionary<(WeakReference, AudioTweenProperty), AudioTween>(); | ||||||
|  | 		private readonly List<AudioTween> DelayedAudioTweens = new List<AudioTween>(); | ||||||
|  | 
 | ||||||
|  | 		public void Update(float elapsedSeconds) | ||||||
|  | 		{ | ||||||
|  | 			for (var i = DelayedAudioTweens.Count - 1; i >= 0; i--) | ||||||
|  | 			{ | ||||||
|  | 				var audioTween = DelayedAudioTweens[i]; | ||||||
|  | 				if (audioTween.SoundInstanceReference.Target is SoundInstance soundInstance) | ||||||
|  | 				{ | ||||||
|  | 					audioTween.Time += elapsedSeconds; | ||||||
|  | 
 | ||||||
|  | 					if (audioTween.Time >= audioTween.DelayTime) | ||||||
|  | 					{ | ||||||
|  | 						// set the tween start value to the current value of the property | ||||||
|  | 						switch (audioTween.Property) | ||||||
|  | 						{ | ||||||
|  | 							case AudioTweenProperty.Pan: | ||||||
|  | 								audioTween.StartValue = soundInstance.Pan; | ||||||
|  | 								break; | ||||||
|  | 
 | ||||||
|  | 							case AudioTweenProperty.Pitch: | ||||||
|  | 								audioTween.StartValue = soundInstance.Pitch; | ||||||
|  | 								break; | ||||||
|  | 
 | ||||||
|  | 							case AudioTweenProperty.Volume: | ||||||
|  | 								audioTween.StartValue = soundInstance.Volume; | ||||||
|  | 								break; | ||||||
|  | 
 | ||||||
|  | 							case AudioTweenProperty.FilterFrequency: | ||||||
|  | 								audioTween.StartValue = soundInstance.FilterFrequency; | ||||||
|  | 								break; | ||||||
|  | 
 | ||||||
|  | 							case AudioTweenProperty.Reverb: | ||||||
|  | 								audioTween.StartValue = soundInstance.Reverb; | ||||||
|  | 								break; | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
|  | 						audioTween.Time = 0; | ||||||
|  | 						DelayedAudioTweens.RemoveAt(i); | ||||||
|  | 
 | ||||||
|  | 						AddTween(audioTween); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 				else | ||||||
|  | 				{ | ||||||
|  | 					AudioTweenPool.Free(audioTween); | ||||||
|  | 					DelayedAudioTweens.RemoveAt(i); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			foreach (var (key, audioTween) in AudioTweens) | ||||||
|  | 			{ | ||||||
|  | 				bool finished = true; | ||||||
|  | 				if (audioTween.SoundInstanceReference.Target is SoundInstance soundInstance) | ||||||
|  | 				{ | ||||||
|  | 					finished = UpdateAudioTween(audioTween, soundInstance, elapsedSeconds); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if (finished) | ||||||
|  | 				{ | ||||||
|  | 					AudioTweenPool.Free(audioTween); | ||||||
|  | 					AudioTweens.Remove(key); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void CreateTween( | ||||||
|  | 			SoundInstance soundInstance, | ||||||
|  | 			AudioTweenProperty property, | ||||||
|  | 			System.Func<float, float> easingFunction, | ||||||
|  | 			float start, | ||||||
|  | 			float end, | ||||||
|  | 			float duration, | ||||||
|  | 			float delayTime | ||||||
|  | 		) { | ||||||
|  | 			var tween = AudioTweenPool.Obtain(); | ||||||
|  | 			tween.SoundInstanceReference = soundInstance.weakReference; | ||||||
|  | 			tween.Property = property; | ||||||
|  | 			tween.EasingFunction = easingFunction; | ||||||
|  | 			tween.StartValue = start; | ||||||
|  | 			tween.EndValue = end; | ||||||
|  | 			tween.Duration = duration; | ||||||
|  | 			tween.Time = 0; | ||||||
|  | 			tween.DelayTime = delayTime; | ||||||
|  | 
 | ||||||
|  | 			if (delayTime == 0) | ||||||
|  | 			{ | ||||||
|  | 				AddTween(tween); | ||||||
|  | 			} | ||||||
|  | 			else | ||||||
|  | 			{ | ||||||
|  | 				DelayedAudioTweens.Add(tween); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void ClearTweens(WeakReference soundReference, AudioTweenProperty property) | ||||||
|  | 		{ | ||||||
|  | 			AudioTweens.Remove((soundReference, property)); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		private void AddTween( | ||||||
|  | 			AudioTween audioTween | ||||||
|  | 		) { | ||||||
|  | 			// if a tween with the same sound and property already exists, get rid of it | ||||||
|  | 			if (AudioTweens.TryGetValue((audioTween.SoundInstanceReference, audioTween.Property), out var currentTween)) | ||||||
|  | 			{ | ||||||
|  | 				AudioTweenPool.Free(currentTween); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			AudioTweens[(audioTween.SoundInstanceReference, audioTween.Property)] = audioTween; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		private static bool UpdateAudioTween(AudioTween audioTween, SoundInstance soundInstance, float delta) | ||||||
|  | 		{ | ||||||
|  | 			float value; | ||||||
|  | 			audioTween.Time += delta; | ||||||
|  | 
 | ||||||
|  | 			var finished = audioTween.Time >= audioTween.Duration; | ||||||
|  | 			if (finished) | ||||||
|  | 			{ | ||||||
|  | 				value = audioTween.EndValue; | ||||||
|  | 			} | ||||||
|  | 			else | ||||||
|  | 			{ | ||||||
|  | 				value = MoonWorks.Math.Easing.Interp( | ||||||
|  | 					audioTween.StartValue, | ||||||
|  | 					audioTween.EndValue, | ||||||
|  | 					audioTween.Time, | ||||||
|  | 					audioTween.Duration, | ||||||
|  | 					audioTween.EasingFunction | ||||||
|  | 				); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			switch (audioTween.Property) | ||||||
|  | 			{ | ||||||
|  | 				case AudioTweenProperty.Pan: | ||||||
|  | 					soundInstance.Pan = value; | ||||||
|  | 					break; | ||||||
|  | 
 | ||||||
|  | 				case AudioTweenProperty.Pitch: | ||||||
|  | 					soundInstance.Pitch = value; | ||||||
|  | 					break; | ||||||
|  | 
 | ||||||
|  | 				case AudioTweenProperty.Volume: | ||||||
|  | 					soundInstance.Volume = value; | ||||||
|  | 					break; | ||||||
|  | 
 | ||||||
|  | 				case AudioTweenProperty.FilterFrequency: | ||||||
|  | 					soundInstance.FilterFrequency = value; | ||||||
|  | 					break; | ||||||
|  | 
 | ||||||
|  | 				case AudioTweenProperty.Reverb: | ||||||
|  | 					soundInstance.Reverb = value; | ||||||
|  | 					break; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			return finished; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -4,14 +4,12 @@ using System.Runtime.InteropServices; | ||||||
| namespace MoonWorks.Audio | namespace MoonWorks.Audio | ||||||
| { | { | ||||||
| 	// sound instances can send their audio to this voice to add reverb | 	// sound instances can send their audio to this voice to add reverb | ||||||
| 	public unsafe class ReverbEffect : IDisposable | 	public unsafe class ReverbEffect : AudioResource | ||||||
| 	{ | 	{ | ||||||
| 		private IntPtr voice; | 		private IntPtr voice; | ||||||
| 		public IntPtr Voice => voice; | 		public IntPtr Voice => voice; | ||||||
| 
 | 
 | ||||||
| 		private bool disposedValue; | 		public ReverbEffect(AudioDevice audioDevice) : base(audioDevice) | ||||||
| 
 |  | ||||||
| 		public ReverbEffect(AudioDevice audioDevice) |  | ||||||
| 		{ | 		{ | ||||||
| 			/* Init reverb */ | 			/* Init reverb */ | ||||||
| 
 | 
 | ||||||
|  | @ -97,32 +95,9 @@ namespace MoonWorks.Audio | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		protected virtual void Dispose(bool disposing) | 		protected override void Destroy() | ||||||
| 		{ | 		{ | ||||||
| 			if (!disposedValue) | 			FAudio.FAudioVoice_DestroyVoice(Voice); | ||||||
| 			{ |  | ||||||
| 				if (disposing) |  | ||||||
| 				{ |  | ||||||
| 					// TODO: dispose managed state (managed objects) |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				FAudio.FAudioVoice_DestroyVoice(voice); |  | ||||||
| 
 |  | ||||||
| 				disposedValue = true; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		~ReverbEffect() |  | ||||||
| 		{ |  | ||||||
| 		    // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method |  | ||||||
| 		    Dispose(disposing: false); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void Dispose() |  | ||||||
| 		{ |  | ||||||
| 			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method |  | ||||||
| 			Dispose(disposing: true); |  | ||||||
| 			GC.SuppressFinalize(this); |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,12 +1,15 @@ | ||||||
| using System; | using System; | ||||||
| using System.Runtime.InteropServices; | using System.Runtime.InteropServices; | ||||||
|  | using EasingFunction = System.Func<float, float>; | ||||||
| 
 | 
 | ||||||
| namespace MoonWorks.Audio | namespace MoonWorks.Audio | ||||||
| { | { | ||||||
| 	public abstract class SoundInstance : AudioResource | 	public abstract class SoundInstance : AudioResource | ||||||
| 	{ | 	{ | ||||||
| 		internal IntPtr Voice; | 		internal IntPtr Voice; | ||||||
| 		internal FAudio.FAudioWaveFormatEx Format; | 
 | ||||||
|  | 		private FAudio.FAudioWaveFormatEx format; | ||||||
|  | 		public FAudio.FAudioWaveFormatEx Format => format; | ||||||
| 
 | 
 | ||||||
| 		protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings; | 		protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings; | ||||||
| 
 | 
 | ||||||
|  | @ -21,7 +24,10 @@ namespace MoonWorks.Audio | ||||||
| 		public float Pan | 		public float Pan | ||||||
| 		{ | 		{ | ||||||
| 			get => pan; | 			get => pan; | ||||||
| 			set | 			internal set | ||||||
|  | 			{ | ||||||
|  | 				value = Math.MathHelper.Clamp(value, -1f, 1f); | ||||||
|  | 				if (pan != value) | ||||||
| 				{ | 				{ | ||||||
| 					pan = value; | 					pan = value; | ||||||
| 
 | 
 | ||||||
|  | @ -47,28 +53,37 @@ namespace MoonWorks.Audio | ||||||
| 					); | 					); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 		private float pitch = 0; | 		private float pitch = 0; | ||||||
| 		public float Pitch | 		public float Pitch | ||||||
| 		{ | 		{ | ||||||
| 			get => pitch; | 			get => pitch; | ||||||
| 			set | 			internal set | ||||||
| 			{ | 			{ | ||||||
| 				pitch = Math.MathHelper.Clamp(value, -1f, 1f); | 				value = Math.MathHelper.Clamp(value, -1f, 1f); | ||||||
|  | 				if (pitch != value) | ||||||
|  | 				{ | ||||||
|  | 					pitch = value; | ||||||
| 					UpdatePitch(); | 					UpdatePitch(); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 		private float volume = 1; | 		private float volume = 1; | ||||||
| 		public float Volume | 		public float Volume | ||||||
| 		{ | 		{ | ||||||
| 			get => volume; | 			get => volume; | ||||||
| 			set | 			internal set | ||||||
|  | 			{ | ||||||
|  | 				value = Math.MathHelper.Max(0, value); | ||||||
|  | 				if (volume != value) | ||||||
| 				{ | 				{ | ||||||
| 					volume = value; | 					volume = value; | ||||||
| 					FAudio.FAudioVoice_SetVolume(Voice, volume, 0); | 					FAudio.FAudioVoice_SetVolume(Voice, volume, 0); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 		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; | ||||||
|  | @ -80,12 +95,14 @@ namespace MoonWorks.Audio | ||||||
| 			OneOverQ = 1f | 			OneOverQ = 1f | ||||||
| 		}; | 		}; | ||||||
| 
 | 
 | ||||||
| 		private float FilterFrequency | 		public float FilterFrequency | ||||||
| 		{ | 		{ | ||||||
| 			get => filterParameters.Frequency; | 			get => filterParameters.Frequency; | ||||||
| 			set | 			internal set | ||||||
| 			{ | 			{ | ||||||
| 				value = System.Math.Clamp(value, 0.01f, MAX_FILTER_FREQUENCY); | 				value = System.Math.Clamp(value, 0.01f, MAX_FILTER_FREQUENCY); | ||||||
|  | 				if (filterParameters.Frequency != value) | ||||||
|  | 				{ | ||||||
| 					filterParameters.Frequency = value; | 					filterParameters.Frequency = value; | ||||||
| 
 | 
 | ||||||
| 					FAudio.FAudioVoice_SetFilterParameters( | 					FAudio.FAudioVoice_SetFilterParameters( | ||||||
|  | @ -95,13 +112,16 @@ namespace MoonWorks.Audio | ||||||
| 					); | 					); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 		private float FilterOneOverQ | 		public float FilterOneOverQ | ||||||
| 		{ | 		{ | ||||||
| 			get => filterParameters.OneOverQ; | 			get => filterParameters.OneOverQ; | ||||||
| 			set | 			internal set | ||||||
| 			{ | 			{ | ||||||
| 				value = System.Math.Clamp(value, 0.01f, MAX_FILTER_ONEOVERQ); | 				value = System.Math.Clamp(value, 0.01f, MAX_FILTER_ONEOVERQ); | ||||||
|  | 				if (filterParameters.OneOverQ != value) | ||||||
|  | 				{ | ||||||
| 					filterParameters.OneOverQ = value; | 					filterParameters.OneOverQ = value; | ||||||
| 
 | 
 | ||||||
| 					FAudio.FAudioVoice_SetFilterParameters( | 					FAudio.FAudioVoice_SetFilterParameters( | ||||||
|  | @ -111,12 +131,15 @@ namespace MoonWorks.Audio | ||||||
| 					); | 					); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 		private FilterType filterType; | 		private FilterType filterType; | ||||||
| 		public FilterType FilterType | 		public FilterType FilterType | ||||||
| 		{ | 		{ | ||||||
| 			get => filterType; | 			get => filterType; | ||||||
| 			set | 			set | ||||||
|  | 			{ | ||||||
|  | 				if (filterType != value) | ||||||
| 				{ | 				{ | ||||||
| 					filterType = value; | 					filterType = value; | ||||||
| 
 | 
 | ||||||
|  | @ -133,6 +156,7 @@ namespace MoonWorks.Audio | ||||||
| 
 | 
 | ||||||
| 						case FilterType.LowPass: | 						case FilterType.LowPass: | ||||||
| 							filterParameters.Type = FAudio.FAudioFilterType.FAudioLowPassFilter; | 							filterParameters.Type = FAudio.FAudioFilterType.FAudioLowPassFilter; | ||||||
|  | 							filterParameters.Frequency = 1f; | ||||||
| 							break; | 							break; | ||||||
| 
 | 
 | ||||||
| 						case FilterType.BandPass: | 						case FilterType.BandPass: | ||||||
|  | @ -141,6 +165,7 @@ namespace MoonWorks.Audio | ||||||
| 
 | 
 | ||||||
| 						case FilterType.HighPass: | 						case FilterType.HighPass: | ||||||
| 							filterParameters.Type = FAudio.FAudioFilterType.FAudioHighPassFilter; | 							filterParameters.Type = FAudio.FAudioFilterType.FAudioHighPassFilter; | ||||||
|  | 							filterParameters.Frequency = 0f; | ||||||
| 							break; | 							break; | ||||||
| 					} | 					} | ||||||
| 
 | 
 | ||||||
|  | @ -151,14 +176,18 @@ namespace MoonWorks.Audio | ||||||
| 					); | 					); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 		private float reverb; | 		private float reverb; | ||||||
| 		public unsafe float Reverb | 		public unsafe float Reverb | ||||||
| 		{ | 		{ | ||||||
| 			get => reverb; | 			get => reverb; | ||||||
| 			set | 			internal set | ||||||
| 			{ | 			{ | ||||||
| 				if (ReverbEffect != null) | 				if (ReverbEffect != null) | ||||||
|  | 				{ | ||||||
|  | 					value = MathF.Max(0, value); | ||||||
|  | 					if (reverb != value) | ||||||
| 					{ | 					{ | ||||||
| 						reverb = value; | 						reverb = value; | ||||||
| 
 | 
 | ||||||
|  | @ -178,6 +207,7 @@ namespace MoonWorks.Audio | ||||||
| 							0 | 							0 | ||||||
| 						); | 						); | ||||||
| 					} | 					} | ||||||
|  | 				} | ||||||
| 
 | 
 | ||||||
| 				#if DEBUG | 				#if DEBUG | ||||||
| 				if (ReverbEffect == null) | 				if (ReverbEffect == null) | ||||||
|  | @ -188,7 +218,7 @@ namespace MoonWorks.Audio | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public SoundInstance( | 		public unsafe SoundInstance( | ||||||
| 			AudioDevice device, | 			AudioDevice device, | ||||||
| 			ushort formatTag, | 			ushort formatTag, | ||||||
| 			ushort bitsPerSample, | 			ushort bitsPerSample, | ||||||
|  | @ -197,7 +227,7 @@ namespace MoonWorks.Audio | ||||||
| 			uint samplesPerSecond | 			uint samplesPerSecond | ||||||
| 		) : base(device) | 		) : base(device) | ||||||
| 		{ | 		{ | ||||||
| 			var format = new FAudio.FAudioWaveFormatEx | 			format = new FAudio.FAudioWaveFormatEx | ||||||
| 			{ | 			{ | ||||||
| 				wFormatTag = formatTag, | 				wFormatTag = formatTag, | ||||||
| 				wBitsPerSample = bitsPerSample, | 				wBitsPerSample = bitsPerSample, | ||||||
|  | @ -207,12 +237,10 @@ namespace MoonWorks.Audio | ||||||
| 				nAvgBytesPerSec = blockAlign * samplesPerSecond | 				nAvgBytesPerSec = blockAlign * samplesPerSecond | ||||||
| 			}; | 			}; | ||||||
| 
 | 
 | ||||||
| 			Format = format; |  | ||||||
| 
 |  | ||||||
| 			FAudio.FAudio_CreateSourceVoice( | 			FAudio.FAudio_CreateSourceVoice( | ||||||
| 				Device.Handle, | 				Device.Handle, | ||||||
| 				out Voice, | 				out Voice, | ||||||
| 				ref Format, | 				ref format, | ||||||
| 				FAudio.FAUDIO_VOICE_USEFILTER, | 				FAudio.FAUDIO_VOICE_USEFILTER, | ||||||
| 				FAudio.FAUDIO_DEFAULT_FREQ_RATIO, | 				FAudio.FAUDIO_DEFAULT_FREQ_RATIO, | ||||||
| 				IntPtr.Zero, | 				IntPtr.Zero, | ||||||
|  | @ -277,6 +305,91 @@ namespace MoonWorks.Audio | ||||||
| 			ReverbEffect = reverbEffect; | 			ReverbEffect = reverbEffect; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		public void SetPan(float targetValue) | ||||||
|  | 		{ | ||||||
|  | 			Pan = targetValue; | ||||||
|  | 			Device.ClearTweens(weakReference, AudioTweenProperty.Pan); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void SetPan(float targetValue, float duration, EasingFunction easingFunction) | ||||||
|  | 		{ | ||||||
|  | 			Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, 0); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void SetPan(float targetValue, float delayTime, float duration, EasingFunction easingFunction) | ||||||
|  | 		{ | ||||||
|  | 			Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, delayTime); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void SetPitch(float targetValue) | ||||||
|  | 		{ | ||||||
|  | 			Pitch = targetValue; | ||||||
|  | 			Device.ClearTweens(weakReference, AudioTweenProperty.Pitch); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void SetPitch(float targetValue, float duration, EasingFunction easingFunction) | ||||||
|  | 		{ | ||||||
|  | 			Device.CreateTween(this, AudioTweenProperty.Pitch, easingFunction, Pan, targetValue, duration, 0); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void SetPitch(float targetValue, float delayTime, float duration, EasingFunction easingFunction) | ||||||
|  | 		{ | ||||||
|  | 			Device.CreateTween(this, AudioTweenProperty.Pitch, easingFunction, Pan, targetValue, duration, delayTime); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void SetVolume(float targetValue) | ||||||
|  | 		{ | ||||||
|  | 			Volume = targetValue; | ||||||
|  | 			Device.ClearTweens(weakReference, AudioTweenProperty.Volume); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void SetVolume(float targetValue, float duration, EasingFunction easingFunction) | ||||||
|  | 		{ | ||||||
|  | 			Device.CreateTween(this, AudioTweenProperty.Volume, easingFunction, Volume, targetValue, duration, 0); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void SetVolume(float targetValue, float delayTime, float duration, EasingFunction easingFunction) | ||||||
|  | 		{ | ||||||
|  | 			Device.CreateTween(this, AudioTweenProperty.Volume, easingFunction, Volume, targetValue, duration, delayTime); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void SetFilterFrequency(float targetValue) | ||||||
|  | 		{ | ||||||
|  | 			FilterFrequency = targetValue; | ||||||
|  | 			Device.ClearTweens(weakReference, AudioTweenProperty.FilterFrequency); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void SetFilterFrequency(float targetValue, float duration, EasingFunction easingFunction) | ||||||
|  | 		{ | ||||||
|  | 			Device.CreateTween(this, AudioTweenProperty.FilterFrequency, easingFunction, FilterFrequency, targetValue, duration, 0); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void SetFilterFrequency(float targetValue, float delayTime, float duration, EasingFunction easingFunction) | ||||||
|  | 		{ | ||||||
|  | 			Device.CreateTween(this, AudioTweenProperty.FilterFrequency, easingFunction, FilterFrequency, targetValue, duration, delayTime); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void SetFilterOneOverQ(float targetValue) | ||||||
|  | 		{ | ||||||
|  | 			FilterOneOverQ = targetValue; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void SetReverb(float targetValue) | ||||||
|  | 		{ | ||||||
|  | 			Reverb = targetValue; | ||||||
|  | 			Device.ClearTweens(weakReference, AudioTweenProperty.Reverb); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void SetReverb(float targetValue, float duration, EasingFunction easingFunction) | ||||||
|  | 		{ | ||||||
|  | 			Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, 0); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void SetReverb(float targetValue, float delayTime, float duration, EasingFunction easingFunction) | ||||||
|  | 		{ | ||||||
|  | 			Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, delayTime); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		public abstract void Play(); | 		public abstract void Play(); | ||||||
| 		public abstract void QueueSyncPlay(); | 		public abstract void QueueSyncPlay(); | ||||||
| 		public abstract void Pause(); | 		public abstract void Pause(); | ||||||
|  | @ -297,14 +410,12 @@ namespace MoonWorks.Audio | ||||||
| 			); | 			); | ||||||
| 
 | 
 | ||||||
| 			dspSettings.pMatrixCoefficients = (nint) NativeMemory.Alloc(memsize); | 			dspSettings.pMatrixCoefficients = (nint) NativeMemory.Alloc(memsize); | ||||||
| 			unsafe |  | ||||||
| 			{ |  | ||||||
| 			byte* memPtr = (byte*) dspSettings.pMatrixCoefficients; | 			byte* memPtr = (byte*) dspSettings.pMatrixCoefficients; | ||||||
| 			for (uint i = 0; i < memsize; i += 1) | 			for (uint i = 0; i < memsize; i += 1) | ||||||
| 			{ | 			{ | ||||||
| 				memPtr[i] = 0; | 				memPtr[i] = 0; | ||||||
| 			} | 			} | ||||||
| 			} | 
 | ||||||
| 			SetPanMatrixCoefficients(); | 			SetPanMatrixCoefficients(); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -10,11 +10,21 @@ namespace MoonWorks.Audio | ||||||
| 	/// </summary> | 	/// </summary> | ||||||
| 	public abstract class StreamingSound : SoundInstance | 	public abstract class StreamingSound : SoundInstance | ||||||
| 	{ | 	{ | ||||||
|  | 		// How big should each buffer we consume be? | ||||||
|  | 		protected abstract int BUFFER_SIZE { get; } | ||||||
|  | 
 | ||||||
|  | 		// Should the AudioDevice thread automatically update this class? | ||||||
|  | 		public abstract bool AutoUpdate { get; } | ||||||
|  | 
 | ||||||
|  | 		// Are we actively consuming buffers? | ||||||
|  | 		protected bool ConsumingBuffers = false; | ||||||
|  | 
 | ||||||
| 		private const int BUFFER_COUNT = 3; | 		private const int BUFFER_COUNT = 3; | ||||||
| 		private readonly IntPtr[] buffers; | 		private readonly IntPtr[] buffers; | ||||||
| 		private int nextBufferIndex = 0; | 		private int nextBufferIndex = 0; | ||||||
| 		private uint queuedBufferCount = 0; | 		private uint queuedBufferCount = 0; | ||||||
| 		protected abstract int BUFFER_SIZE { get; } | 
 | ||||||
|  | 		private readonly object StateLock = new object(); | ||||||
| 
 | 
 | ||||||
| 		public unsafe StreamingSound( | 		public unsafe StreamingSound( | ||||||
| 			AudioDevice device, | 			AudioDevice device, | ||||||
|  | @ -25,8 +35,6 @@ namespace MoonWorks.Audio | ||||||
| 			uint samplesPerSecond | 			uint samplesPerSecond | ||||||
| 		) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond) | 		) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond) | ||||||
| 		{ | 		{ | ||||||
| 			device.AddDynamicSoundInstance(this); |  | ||||||
| 
 |  | ||||||
| 			buffers = new IntPtr[BUFFER_COUNT]; | 			buffers = new IntPtr[BUFFER_COUNT]; | ||||||
| 			for (int i = 0; i < BUFFER_COUNT; i += 1) | 			for (int i = 0; i < BUFFER_COUNT; i += 1) | ||||||
| 			{ | 			{ | ||||||
|  | @ -45,6 +53,8 @@ namespace MoonWorks.Audio | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		private void PlayUsingOperationSet(uint operationSet) | 		private void PlayUsingOperationSet(uint operationSet) | ||||||
|  | 		{ | ||||||
|  | 			lock (StateLock) | ||||||
| 			{ | 			{ | ||||||
| 				if (State == SoundState.Playing) | 				if (State == SoundState.Playing) | ||||||
| 				{ | 				{ | ||||||
|  | @ -53,40 +63,65 @@ namespace MoonWorks.Audio | ||||||
| 
 | 
 | ||||||
| 				State = SoundState.Playing; | 				State = SoundState.Playing; | ||||||
| 
 | 
 | ||||||
| 			Update(); | 				ConsumingBuffers = true; | ||||||
|  | 				QueueBuffers(); | ||||||
| 				FAudio.FAudioSourceVoice_Start(Voice, 0, operationSet); | 				FAudio.FAudioSourceVoice_Start(Voice, 0, operationSet); | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 		public override void Pause() | 		public override void Pause() | ||||||
|  | 		{ | ||||||
|  | 			lock (StateLock) | ||||||
| 			{ | 			{ | ||||||
| 				if (State == SoundState.Playing) | 				if (State == SoundState.Playing) | ||||||
| 				{ | 				{ | ||||||
|  | 					ConsumingBuffers = false; | ||||||
| 					FAudio.FAudioSourceVoice_Stop(Voice, 0, 0); | 					FAudio.FAudioSourceVoice_Stop(Voice, 0, 0); | ||||||
| 					State = SoundState.Paused; | 					State = SoundState.Paused; | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 		public override void Stop() | 		public override void Stop() | ||||||
| 		{ | 		{ | ||||||
|  | 			lock (StateLock) | ||||||
|  | 			{ | ||||||
|  | 				ConsumingBuffers = false; | ||||||
| 				State = SoundState.Stopped; | 				State = SoundState.Stopped; | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 		public override void StopImmediate() | 		public override void StopImmediate() | ||||||
| 		{ | 		{ | ||||||
|  | 			lock (StateLock) | ||||||
|  | 			{ | ||||||
|  | 				ConsumingBuffers = false; | ||||||
| 				FAudio.FAudioSourceVoice_Stop(Voice, 0, 0); | 				FAudio.FAudioSourceVoice_Stop(Voice, 0, 0); | ||||||
| 				FAudio.FAudioSourceVoice_FlushSourceBuffers(Voice); | 				FAudio.FAudioSourceVoice_FlushSourceBuffers(Voice); | ||||||
| 				ClearBuffers(); | 				ClearBuffers(); | ||||||
| 
 | 
 | ||||||
| 				State = SoundState.Stopped; | 				State = SoundState.Stopped; | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 		internal unsafe void Update() | 		internal unsafe void Update() | ||||||
|  | 		{ | ||||||
|  | 			lock (StateLock) | ||||||
|  | 			{ | ||||||
|  | 				if (!IsDisposed) | ||||||
| 				{ | 				{ | ||||||
| 					if (State != SoundState.Playing) | 					if (State != SoundState.Playing) | ||||||
| 					{ | 					{ | ||||||
| 						return; | 						return; | ||||||
| 					} | 					} | ||||||
| 
 | 
 | ||||||
|  | 					QueueBuffers(); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		protected void QueueBuffers() | ||||||
|  | 		{ | ||||||
| 			FAudio.FAudioSourceVoice_GetState( | 			FAudio.FAudioSourceVoice_GetState( | ||||||
| 				Voice, | 				Voice, | ||||||
| 				out var state, | 				out var state, | ||||||
|  | @ -95,16 +130,18 @@ namespace MoonWorks.Audio | ||||||
| 
 | 
 | ||||||
| 			queuedBufferCount = state.BuffersQueued; | 			queuedBufferCount = state.BuffersQueued; | ||||||
| 
 | 
 | ||||||
| 			QueueBuffers(); | 			if (ConsumingBuffers) | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		protected void QueueBuffers() |  | ||||||
| 			{ | 			{ | ||||||
| 				for (int i = 0; i < BUFFER_COUNT - queuedBufferCount; i += 1) | 				for (int i = 0; i < BUFFER_COUNT - queuedBufferCount; i += 1) | ||||||
| 				{ | 				{ | ||||||
| 					AddBuffer(); | 					AddBuffer(); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | 			else if (queuedBufferCount == 0) | ||||||
|  | 			{ | ||||||
|  | 				Stop(); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 		protected unsafe void ClearBuffers() | 		protected unsafe void ClearBuffers() | ||||||
| 		{ | 		{ | ||||||
|  | @ -124,6 +161,8 @@ namespace MoonWorks.Audio | ||||||
| 				out bool reachedEnd | 				out bool reachedEnd | ||||||
| 			); | 			); | ||||||
| 
 | 
 | ||||||
|  | 			if (filledLengthInBytes > 0) | ||||||
|  | 			{ | ||||||
| 				FAudio.FAudioBuffer buf = new FAudio.FAudioBuffer | 				FAudio.FAudioBuffer buf = new FAudio.FAudioBuffer | ||||||
| 				{ | 				{ | ||||||
| 					AudioBytes = (uint) filledLengthInBytes, | 					AudioBytes = (uint) filledLengthInBytes, | ||||||
|  | @ -142,19 +181,16 @@ namespace MoonWorks.Audio | ||||||
| 				); | 				); | ||||||
| 
 | 
 | ||||||
| 				queuedBufferCount += 1; | 				queuedBufferCount += 1; | ||||||
|  | 			} | ||||||
| 
 | 
 | ||||||
| 			/* We have reached the end of the file, what do we do? */ |  | ||||||
| 			if (reachedEnd) | 			if (reachedEnd) | ||||||
| 			{ | 			{ | ||||||
|  | 				/* We have reached the end of the data, what do we do? */ | ||||||
|  | 				ConsumingBuffers = false; | ||||||
| 				OnReachedEnd(); | 				OnReachedEnd(); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		protected virtual void OnReachedEnd() |  | ||||||
| 		{ |  | ||||||
| 			Stop(); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		protected unsafe abstract void FillBuffer( | 		protected unsafe abstract void FillBuffer( | ||||||
| 			void* buffer, | 			void* buffer, | ||||||
| 			int bufferLengthInBytes, /* in bytes */ | 			int bufferLengthInBytes, /* in bytes */ | ||||||
|  | @ -162,7 +198,13 @@ namespace MoonWorks.Audio | ||||||
| 			out bool reachedEnd | 			out bool reachedEnd | ||||||
| 		); | 		); | ||||||
| 
 | 
 | ||||||
|  | 		protected abstract void OnReachedEnd(); | ||||||
|  | 
 | ||||||
| 		protected unsafe override void Destroy() | 		protected unsafe override void Destroy() | ||||||
|  | 		{ | ||||||
|  | 			lock (StateLock) | ||||||
|  | 			{ | ||||||
|  | 				if (!IsDisposed) | ||||||
| 				{ | 				{ | ||||||
| 					StopImmediate(); | 					StopImmediate(); | ||||||
| 
 | 
 | ||||||
|  | @ -172,4 +214,6 @@ namespace MoonWorks.Audio | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -11,6 +11,7 @@ namespace MoonWorks.Audio | ||||||
| 		private FAudio.stb_vorbis_info Info; | 		private FAudio.stb_vorbis_info Info; | ||||||
| 
 | 
 | ||||||
| 		protected override int BUFFER_SIZE => 32768; | 		protected override int BUFFER_SIZE => 32768; | ||||||
|  | 		public override bool AutoUpdate => true; | ||||||
| 
 | 
 | ||||||
| 		public unsafe static StreamingSoundOgg Load(AudioDevice device, string filePath) | 		public unsafe static StreamingSoundOgg Load(AudioDevice device, string filePath) | ||||||
| 		{ | 		{ | ||||||
|  | @ -35,7 +36,7 @@ namespace MoonWorks.Audio | ||||||
| 			); | 			); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		internal StreamingSoundOgg( | 		internal unsafe StreamingSoundOgg( | ||||||
| 			AudioDevice device, | 			AudioDevice device, | ||||||
| 			IntPtr fileDataPtr, // MUST BE A NATIVE MEMORY HANDLE!! | 			IntPtr fileDataPtr, // MUST BE A NATIVE MEMORY HANDLE!! | ||||||
| 			IntPtr vorbisHandle, | 			IntPtr vorbisHandle, | ||||||
|  | @ -47,8 +48,7 @@ namespace MoonWorks.Audio | ||||||
| 			(ushort) (4 * info.channels), | 			(ushort) (4 * info.channels), | ||||||
| 			(ushort) info.channels, | 			(ushort) info.channels, | ||||||
| 			info.sample_rate | 			info.sample_rate | ||||||
| 		) | 		) { | ||||||
| 		{ |  | ||||||
| 			FileDataPtr = fileDataPtr; | 			FileDataPtr = fileDataPtr; | ||||||
| 			VorbisHandle = vorbisHandle; | 			VorbisHandle = vorbisHandle; | ||||||
| 			Info = info; | 			Info = info; | ||||||
|  | @ -64,8 +64,7 @@ namespace MoonWorks.Audio | ||||||
| 			int bufferLengthInBytes, | 			int bufferLengthInBytes, | ||||||
| 			out int filledLengthInBytes, | 			out int filledLengthInBytes, | ||||||
| 			out bool reachedEnd | 			out bool reachedEnd | ||||||
| 		) | 		) { | ||||||
| 		{ |  | ||||||
| 			var lengthInFloats = bufferLengthInBytes / sizeof(float); | 			var lengthInFloats = bufferLengthInBytes / sizeof(float); | ||||||
| 
 | 
 | ||||||
| 			/* NOTE: this function returns samples per channel, not total samples */ | 			/* NOTE: this function returns samples per channel, not total samples */ | ||||||
|  | @ -82,9 +81,14 @@ namespace MoonWorks.Audio | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		protected unsafe override void Destroy() | 		protected unsafe override void Destroy() | ||||||
|  | 		{ | ||||||
|  | 			base.Destroy(); | ||||||
|  | 
 | ||||||
|  | 			if (!IsDisposed) | ||||||
| 			{ | 			{ | ||||||
| 				FAudio.stb_vorbis_close(VorbisHandle); | 				FAudio.stb_vorbis_close(VorbisHandle); | ||||||
| 				NativeMemory.Free((void*) FileDataPtr); | 				NativeMemory.Free((void*) FileDataPtr); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -14,12 +14,9 @@ namespace MoonWorks.Audio | ||||||
| 		{ | 		{ | ||||||
| 			if (Loop) | 			if (Loop) | ||||||
| 			{ | 			{ | ||||||
|  | 				ConsumingBuffers = true; | ||||||
| 				Seek(0); | 				Seek(0); | ||||||
| 			} | 			} | ||||||
| 			else |  | ||||||
| 			{ |  | ||||||
| 				Stop(); |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -168,9 +168,8 @@ namespace MoonWorks | ||||||
| 				while (accumulatedUpdateTime >= Timestep) | 				while (accumulatedUpdateTime >= Timestep) | ||||||
| 				{ | 				{ | ||||||
| 					Inputs.Update(); | 					Inputs.Update(); | ||||||
| 					AudioDevice.Update(); |  | ||||||
| 
 |  | ||||||
| 					Update(Timestep); | 					Update(Timestep); | ||||||
|  | 					AudioDevice.WakeThread(); | ||||||
| 
 | 
 | ||||||
| 					accumulatedUpdateTime -= Timestep; | 					accumulatedUpdateTime -= Timestep; | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | @ -18,7 +18,7 @@ namespace MoonWorks.Graphics | ||||||
| 
 | 
 | ||||||
| 		public bool IsDisposed { get; private set; } | 		public bool IsDisposed { get; private set; } | ||||||
| 
 | 
 | ||||||
| 		private readonly List<WeakReference<GraphicsResource>> resources = new List<WeakReference<GraphicsResource>>(); | 		private readonly HashSet<WeakReference<GraphicsResource>> resources = new HashSet<WeakReference<GraphicsResource>>(); | ||||||
| 
 | 
 | ||||||
| 		public GraphicsDevice( | 		public GraphicsDevice( | ||||||
| 			Backend preferredBackend, | 			Backend preferredBackend, | ||||||
|  | @ -237,10 +237,9 @@ namespace MoonWorks.Graphics | ||||||
| 				{ | 				{ | ||||||
| 					lock (resources) | 					lock (resources) | ||||||
| 					{ | 					{ | ||||||
| 						for (var i = resources.Count - 1; i >= 0; i--) | 						foreach (var weakReference in resources) | ||||||
| 						{ | 						{ | ||||||
| 							var resource = resources[i]; | 							if (weakReference.TryGetTarget(out var target)) | ||||||
| 							if (resource.TryGetTarget(out var target)) |  | ||||||
| 							{ | 							{ | ||||||
| 								target.Dispose(); | 								target.Dispose(); | ||||||
| 							} | 							} | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ namespace MoonWorks.Graphics | ||||||
| 		public bool IsDisposed { get; private set; } | 		public bool IsDisposed { get; private set; } | ||||||
| 		protected abstract Action<IntPtr, IntPtr> QueueDestroyFunction { get; } | 		protected abstract Action<IntPtr, IntPtr> QueueDestroyFunction { get; } | ||||||
| 
 | 
 | ||||||
| 		private WeakReference<GraphicsResource> selfReference; | 		internal WeakReference<GraphicsResource> weakReference; | ||||||
| 
 | 
 | ||||||
| 		public GraphicsResource(GraphicsDevice device, bool trackResource = true) | 		public GraphicsResource(GraphicsDevice device, bool trackResource = true) | ||||||
| 		{ | 		{ | ||||||
|  | @ -18,8 +18,8 @@ namespace MoonWorks.Graphics | ||||||
| 
 | 
 | ||||||
| 			if (trackResource) | 			if (trackResource) | ||||||
| 			{ | 			{ | ||||||
| 				selfReference = new WeakReference<GraphicsResource>(this); | 				weakReference = new WeakReference<GraphicsResource>(this); | ||||||
| 				Device.AddResourceReference(selfReference); | 				Device.AddResourceReference(weakReference); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -27,11 +27,11 @@ namespace MoonWorks.Graphics | ||||||
| 		{ | 		{ | ||||||
| 			if (!IsDisposed) | 			if (!IsDisposed) | ||||||
| 			{ | 			{ | ||||||
| 				if (selfReference != null) | 				if (weakReference != null) | ||||||
| 				{ | 				{ | ||||||
| 					QueueDestroyFunction(Device.Handle, Handle); | 					QueueDestroyFunction(Device.Handle, Handle); | ||||||
| 					Device.RemoveResourceReference(selfReference); | 					Device.RemoveResourceReference(weakReference); | ||||||
| 					selfReference = null; | 					weakReference = null; | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				IsDisposed = true; | 				IsDisposed = true; | ||||||
|  |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -7,6 +7,8 @@ namespace MoonWorks.Video | ||||||
| 	{ | 	{ | ||||||
| 		private IntPtr VideoHandle; | 		private IntPtr VideoHandle; | ||||||
| 		protected override int BUFFER_SIZE => 8192; | 		protected override int BUFFER_SIZE => 8192; | ||||||
|  | 		// Theorafile is not thread safe, so let's update on the main thread. | ||||||
|  | 		public override bool AutoUpdate => false; | ||||||
| 
 | 
 | ||||||
| 		internal StreamingSoundTheora( | 		internal StreamingSoundTheora( | ||||||
| 			AudioDevice device, | 			AudioDevice device, | ||||||
|  | @ -32,6 +34,11 @@ namespace MoonWorks.Video | ||||||
| 		) { | 		) { | ||||||
| 			var lengthInFloats = bufferLengthInBytes / sizeof(float); | 			var lengthInFloats = bufferLengthInBytes / sizeof(float); | ||||||
| 
 | 
 | ||||||
|  | 			// FIXME: this gets gnarly with theorafile being not thread safe | ||||||
|  | 			// is there some way we could just manually update in VideoPlayer | ||||||
|  | 			// instead of going through AudioDevice? | ||||||
|  | 			lock (Device.StateLock) | ||||||
|  | 			{ | ||||||
| 				int samples = Theorafile.tf_readaudio( | 				int samples = Theorafile.tf_readaudio( | ||||||
| 					VideoHandle, | 					VideoHandle, | ||||||
| 					(IntPtr) buffer, | 					(IntPtr) buffer, | ||||||
|  | @ -42,4 +49,7 @@ namespace MoonWorks.Video | ||||||
| 				reachedEnd = Theorafile.tf_eos(VideoHandle) == 1; | 				reachedEnd = Theorafile.tf_eos(VideoHandle) == 1; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 
 | ||||||
|  | 		protected override void OnReachedEnd() { } | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -27,7 +27,7 @@ namespace MoonWorks.Video | ||||||
| 		private int yWidth; | 		private int yWidth; | ||||||
| 		private int yHeight; | 		private int yHeight; | ||||||
| 
 | 
 | ||||||
| 		private bool disposed; | 		private bool IsDisposed; | ||||||
| 
 | 
 | ||||||
| 		public Video(string filename) | 		public Video(string filename) | ||||||
| 		{ | 		{ | ||||||
|  | @ -89,7 +89,7 @@ namespace MoonWorks.Video | ||||||
| 
 | 
 | ||||||
| 		protected virtual void Dispose(bool disposing) | 		protected virtual void Dispose(bool disposing) | ||||||
| 		{ | 		{ | ||||||
| 			if (!disposed) | 			if (!IsDisposed) | ||||||
| 			{ | 			{ | ||||||
| 				if (disposing) | 				if (disposing) | ||||||
| 				{ | 				{ | ||||||
|  | @ -100,7 +100,7 @@ namespace MoonWorks.Video | ||||||
| 				Theorafile.tf_close(ref Handle); | 				Theorafile.tf_close(ref Handle); | ||||||
| 				NativeMemory.Free(videoData); | 				NativeMemory.Free(videoData); | ||||||
| 
 | 
 | ||||||
| 				disposed = true; | 				IsDisposed = true; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| using System; | using System; | ||||||
| using System.Diagnostics; | using System.Diagnostics; | ||||||
| using System.Runtime.InteropServices; | using System.Runtime.InteropServices; | ||||||
| using MoonWorks.Audio; | using MoonWorks.Audio; | ||||||
|  | @ -130,6 +130,8 @@ namespace MoonWorks.Video | ||||||
| 
 | 
 | ||||||
| 		public void Play() | 		public void Play() | ||||||
| 		{ | 		{ | ||||||
|  | 			if (Video == null) { return; } | ||||||
|  | 
 | ||||||
| 			if (State == VideoState.Playing) | 			if (State == VideoState.Playing) | ||||||
| 			{ | 			{ | ||||||
| 				return; | 				return; | ||||||
|  | @ -147,6 +149,8 @@ namespace MoonWorks.Video | ||||||
| 
 | 
 | ||||||
| 		public void Pause() | 		public void Pause() | ||||||
| 		{ | 		{ | ||||||
|  | 			if (Video == null) { return; } | ||||||
|  | 
 | ||||||
| 			if (State != VideoState.Playing) | 			if (State != VideoState.Playing) | ||||||
| 			{ | 			{ | ||||||
| 				return; | 				return; | ||||||
|  | @ -164,6 +168,8 @@ namespace MoonWorks.Video | ||||||
| 
 | 
 | ||||||
| 		public void Stop() | 		public void Stop() | ||||||
| 		{ | 		{ | ||||||
|  | 			if (Video == null) { return; } | ||||||
|  | 
 | ||||||
| 			if (State == VideoState.Stopped) | 			if (State == VideoState.Stopped) | ||||||
| 			{ | 			{ | ||||||
| 				return; | 				return; | ||||||
|  | @ -172,20 +178,32 @@ namespace MoonWorks.Video | ||||||
| 			timer.Stop(); | 			timer.Stop(); | ||||||
| 			timer.Reset(); | 			timer.Reset(); | ||||||
| 
 | 
 | ||||||
| 			Theorafile.tf_reset(Video.Handle); |  | ||||||
| 			lastTimestamp = 0; | 			lastTimestamp = 0; | ||||||
| 			timeElapsed = 0; | 			timeElapsed = 0; | ||||||
| 
 | 
 | ||||||
| 			if (audioStream != null) | 			DestroyAudioStream(); | ||||||
| 			{ | 
 | ||||||
| 				audioStream.StopImmediate(); | 			Theorafile.tf_reset(Video.Handle); | ||||||
| 				audioStream.Dispose(); |  | ||||||
| 				audioStream = null; |  | ||||||
| 			} |  | ||||||
| 
 | 
 | ||||||
| 			State = VideoState.Stopped; | 			State = VideoState.Stopped; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		public void Unload() | ||||||
|  | 		{ | ||||||
|  | 			Stop(); | ||||||
|  | 			Video = null; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void Update() | ||||||
|  | 		{ | ||||||
|  | 			if (Video == null) { return; } | ||||||
|  | 
 | ||||||
|  | 			if (audioStream != null) | ||||||
|  | 			{ | ||||||
|  | 				audioStream.Update(); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		public void Render() | 		public void Render() | ||||||
| 		{ | 		{ | ||||||
| 			if (Video == null || State == VideoState.Stopped) | 			if (Video == null || State == VideoState.Stopped) | ||||||
|  | @ -203,7 +221,8 @@ namespace MoonWorks.Video | ||||||
| 					Video.Handle, | 					Video.Handle, | ||||||
| 					(IntPtr) yuvData, | 					(IntPtr) yuvData, | ||||||
| 					thisFrame - currentFrame | 					thisFrame - currentFrame | ||||||
| 				) == 1 || currentFrame == -1) { | 				) == 1 || currentFrame == -1) | ||||||
|  | 				{ | ||||||
| 					UpdateRenderTexture(); | 					UpdateRenderTexture(); | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
|  | @ -216,12 +235,7 @@ namespace MoonWorks.Video | ||||||
| 				timer.Stop(); | 				timer.Stop(); | ||||||
| 				timer.Reset(); | 				timer.Reset(); | ||||||
| 
 | 
 | ||||||
| 				if (audioStream != null) | 				DestroyAudioStream(); | ||||||
| 				{ |  | ||||||
| 					audioStream.Stop(); |  | ||||||
| 					audioStream.Dispose(); |  | ||||||
| 					audioStream = null; |  | ||||||
| 				} |  | ||||||
| 
 | 
 | ||||||
| 				Theorafile.tf_reset(Video.Handle); | 				Theorafile.tf_reset(Video.Handle); | ||||||
| 
 | 
 | ||||||
|  | @ -299,14 +313,27 @@ namespace MoonWorks.Video | ||||||
| 			// Grab the first bit of audio. We're trying to start the decoding ASAP. | 			// Grab the first bit of audio. We're trying to start the decoding ASAP. | ||||||
| 			if (AudioDevice != null && Theorafile.tf_hasaudio(Video.Handle) == 1) | 			if (AudioDevice != null && Theorafile.tf_hasaudio(Video.Handle) == 1) | ||||||
| 			{ | 			{ | ||||||
|  | 				DestroyAudioStream(); | ||||||
|  | 
 | ||||||
| 				int channels, sampleRate; | 				int channels, sampleRate; | ||||||
| 				Theorafile.tf_audioinfo(Video.Handle, out channels, out sampleRate); | 				Theorafile.tf_audioinfo(Video.Handle, out channels, out sampleRate); | ||||||
|  | 
 | ||||||
| 				audioStream = new StreamingSoundTheora(AudioDevice, Video.Handle, channels, (uint) sampleRate); | 				audioStream = new StreamingSoundTheora(AudioDevice, Video.Handle, channels, (uint) sampleRate); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			currentFrame = -1; | 			currentFrame = -1; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		private void DestroyAudioStream() | ||||||
|  | 		{ | ||||||
|  | 			if (audioStream != null) | ||||||
|  | 			{ | ||||||
|  | 				audioStream.StopImmediate(); | ||||||
|  | 				audioStream.Dispose(); | ||||||
|  | 				audioStream = null; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		protected virtual void Dispose(bool disposing) | 		protected virtual void Dispose(bool disposing) | ||||||
| 		{ | 		{ | ||||||
| 			if (!disposed) | 			if (!disposed) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue