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.Collections.Generic; | ||||
| using System.Runtime.InteropServices; | ||||
| using System.Threading; | ||||
| 
 | ||||
| namespace MoonWorks.Audio | ||||
| { | ||||
|  | @ -26,13 +26,25 @@ namespace MoonWorks.Audio | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		private readonly List<WeakReference<AudioResource>> resources = new List<WeakReference<AudioResource>>(); | ||||
| 		private readonly List<WeakReference<StreamingSound>> streamingSounds = new List<WeakReference<StreamingSound>>(); | ||||
| 		private readonly HashSet<WeakReference> resources = new HashSet<WeakReference>(); | ||||
| 		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; | ||||
| 
 | ||||
| 		public unsafe AudioDevice() | ||||
| 		{ | ||||
| 			UpdateInterval = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / Step); | ||||
| 
 | ||||
| 			FAudio.FAudioCreate(out var handle, 0, FAudio.FAUDIO_DEFAULT_PROCESSOR); | ||||
| 			Handle = handle; | ||||
| 
 | ||||
|  | @ -90,8 +102,8 @@ namespace MoonWorks.Audio | |||
| 			) != 0) | ||||
| 			{ | ||||
| 				Logger.LogError("No mastering voice found!"); | ||||
| 				Handle = IntPtr.Zero; | ||||
| 				FAudio.FAudio_Release(Handle); | ||||
| 				Handle = IntPtr.Zero; | ||||
| 				return; | ||||
| 			} | ||||
| 
 | ||||
|  | @ -105,22 +117,52 @@ namespace MoonWorks.Audio | |||
| 				SpeedOfSound, | ||||
| 				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]; | ||||
| 				if (weakReference.TryGetTarget(out var streamingSound)) | ||||
| 				lock (StateLock) | ||||
| 				{ | ||||
| 					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(); | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					streamingSounds.RemoveAt(i); | ||||
| 					autoUpdateStreamingSoundReferences.Remove(weakReference); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			AudioTweenManager.Update(elapsedSeconds); | ||||
| 		} | ||||
| 
 | ||||
| 		public void SyncPlay() | ||||
|  | @ -128,40 +170,95 @@ namespace MoonWorks.Audio | |||
| 			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)); | ||||
| 		} | ||||
| 
 | ||||
| 		internal void AddResourceReference(WeakReference<AudioResource> resourceReference) | ||||
| 		{ | ||||
| 			lock (resources) | ||||
| 			{ | ||||
| 				resources.Add(resourceReference); | ||||
| 				AudioTweenManager.CreateTween( | ||||
| 					soundInstance, | ||||
| 					property, | ||||
| 					easingFunction, | ||||
| 					start, | ||||
| 					end, | ||||
| 					duration, | ||||
| 					delayTime | ||||
| 				); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		internal void RemoveResourceReference(WeakReference<AudioResource> resourceReference) | ||||
| 		internal void ClearTweens( | ||||
| 			WeakReference soundReference, | ||||
| 			AudioTweenProperty property | ||||
| 		) { | ||||
| 			lock (StateLock) | ||||
| 			{ | ||||
| 			lock (resources) | ||||
| 			{ | ||||
| 				resources.Remove(resourceReference); | ||||
| 				AudioTweenManager.ClearTweens(soundReference, property); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		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) | ||||
| 		{ | ||||
| 			if (!IsDisposed) | ||||
| 			{ | ||||
| 				lock (StateLock) | ||||
| 				{ | ||||
| 					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(); | ||||
|  | @ -173,8 +270,8 @@ namespace MoonWorks.Audio | |||
| 					IsDisposed = true; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources | ||||
| 		~AudioDevice() | ||||
| 		{ | ||||
| 			// 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; } | ||||
| 
 | ||||
| 		private WeakReference<AudioResource> selfReference; | ||||
| 		internal WeakReference weakReference; | ||||
| 
 | ||||
| 		public AudioResource(AudioDevice device) | ||||
| 		{ | ||||
| 			Device = device; | ||||
| 
 | ||||
| 			selfReference = new WeakReference<AudioResource>(this); | ||||
| 			Device.AddResourceReference(selfReference); | ||||
| 			weakReference = new WeakReference(this); | ||||
| 			Device.AddResourceReference(this); | ||||
| 		} | ||||
| 
 | ||||
| 		protected abstract void Destroy(); | ||||
|  | @ -26,10 +26,10 @@ namespace MoonWorks.Audio | |||
| 			{ | ||||
| 				Destroy(); | ||||
| 
 | ||||
| 				if (selfReference != null) | ||||
| 				if (weakReference != null) | ||||
| 				{ | ||||
| 					Device.RemoveResourceReference(selfReference); | ||||
| 					selfReference = null; | ||||
| 					Device.RemoveResourceReference(this); | ||||
| 					weakReference = null; | ||||
| 				} | ||||
| 
 | ||||
| 				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 | ||||
| { | ||||
| 	// 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; | ||||
| 		public IntPtr Voice => voice; | ||||
| 
 | ||||
| 		private bool disposedValue; | ||||
| 
 | ||||
| 		public ReverbEffect(AudioDevice audioDevice) | ||||
| 		public ReverbEffect(AudioDevice audioDevice) : base(audioDevice) | ||||
| 		{ | ||||
| 			/* Init reverb */ | ||||
| 
 | ||||
|  | @ -97,32 +95,9 @@ namespace MoonWorks.Audio | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		protected virtual void Dispose(bool disposing) | ||||
| 		protected override void Destroy() | ||||
| 		{ | ||||
| 			if (!disposedValue) | ||||
| 			{ | ||||
| 				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); | ||||
| 			FAudio.FAudioVoice_DestroyVoice(Voice); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -1,12 +1,15 @@ | |||
| using System; | ||||
| using System.Runtime.InteropServices; | ||||
| using EasingFunction = System.Func<float, float>; | ||||
| 
 | ||||
| namespace MoonWorks.Audio | ||||
| { | ||||
| 	public abstract class SoundInstance : AudioResource | ||||
| 	{ | ||||
| 		internal IntPtr Voice; | ||||
| 		internal FAudio.FAudioWaveFormatEx Format; | ||||
| 
 | ||||
| 		private FAudio.FAudioWaveFormatEx format; | ||||
| 		public FAudio.FAudioWaveFormatEx Format => format; | ||||
| 
 | ||||
| 		protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings; | ||||
| 
 | ||||
|  | @ -21,7 +24,10 @@ namespace MoonWorks.Audio | |||
| 		public float Pan | ||||
| 		{ | ||||
| 			get => pan; | ||||
| 			set | ||||
| 			internal set | ||||
| 			{ | ||||
| 				value = Math.MathHelper.Clamp(value, -1f, 1f); | ||||
| 				if (pan != value) | ||||
| 				{ | ||||
| 					pan = value; | ||||
| 
 | ||||
|  | @ -47,28 +53,37 @@ namespace MoonWorks.Audio | |||
| 					); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		private float pitch = 0; | ||||
| 		public float 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(); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		private float volume = 1; | ||||
| 		public float Volume | ||||
| 		{ | ||||
| 			get => volume; | ||||
| 			set | ||||
| 			internal set | ||||
| 			{ | ||||
| 				value = Math.MathHelper.Max(0, value); | ||||
| 				if (volume != value) | ||||
| 				{ | ||||
| 					volume = value; | ||||
| 					FAudio.FAudioVoice_SetVolume(Voice, volume, 0); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		private const float MAX_FILTER_FREQUENCY = 1f; | ||||
| 		private const float MAX_FILTER_ONEOVERQ = 1.5f; | ||||
|  | @ -80,12 +95,14 @@ namespace MoonWorks.Audio | |||
| 			OneOverQ = 1f | ||||
| 		}; | ||||
| 
 | ||||
| 		private float FilterFrequency | ||||
| 		public float FilterFrequency | ||||
| 		{ | ||||
| 			get => filterParameters.Frequency; | ||||
| 			set | ||||
| 			internal set | ||||
| 			{ | ||||
| 				value = System.Math.Clamp(value, 0.01f, MAX_FILTER_FREQUENCY); | ||||
| 				if (filterParameters.Frequency != value) | ||||
| 				{ | ||||
| 					filterParameters.Frequency = value; | ||||
| 
 | ||||
| 					FAudio.FAudioVoice_SetFilterParameters( | ||||
|  | @ -95,13 +112,16 @@ namespace MoonWorks.Audio | |||
| 					); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		private float FilterOneOverQ | ||||
| 		public float FilterOneOverQ | ||||
| 		{ | ||||
| 			get => filterParameters.OneOverQ; | ||||
| 			set | ||||
| 			internal set | ||||
| 			{ | ||||
| 				value = System.Math.Clamp(value, 0.01f, MAX_FILTER_ONEOVERQ); | ||||
| 				if (filterParameters.OneOverQ != value) | ||||
| 				{ | ||||
| 					filterParameters.OneOverQ = value; | ||||
| 
 | ||||
| 					FAudio.FAudioVoice_SetFilterParameters( | ||||
|  | @ -111,12 +131,15 @@ namespace MoonWorks.Audio | |||
| 					); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		private FilterType filterType; | ||||
| 		public FilterType FilterType | ||||
| 		{ | ||||
| 			get => filterType; | ||||
| 			set | ||||
| 			{ | ||||
| 				if (filterType != value) | ||||
| 				{ | ||||
| 					filterType = value; | ||||
| 
 | ||||
|  | @ -133,6 +156,7 @@ namespace MoonWorks.Audio | |||
| 
 | ||||
| 						case FilterType.LowPass: | ||||
| 							filterParameters.Type = FAudio.FAudioFilterType.FAudioLowPassFilter; | ||||
| 							filterParameters.Frequency = 1f; | ||||
| 							break; | ||||
| 
 | ||||
| 						case FilterType.BandPass: | ||||
|  | @ -141,6 +165,7 @@ namespace MoonWorks.Audio | |||
| 
 | ||||
| 						case FilterType.HighPass: | ||||
| 							filterParameters.Type = FAudio.FAudioFilterType.FAudioHighPassFilter; | ||||
| 							filterParameters.Frequency = 0f; | ||||
| 							break; | ||||
| 					} | ||||
| 
 | ||||
|  | @ -151,14 +176,18 @@ namespace MoonWorks.Audio | |||
| 					); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		private float reverb; | ||||
| 		public unsafe float Reverb | ||||
| 		{ | ||||
| 			get => reverb; | ||||
| 			set | ||||
| 			internal set | ||||
| 			{ | ||||
| 				if (ReverbEffect != null) | ||||
| 				{ | ||||
| 					value = MathF.Max(0, value); | ||||
| 					if (reverb != value) | ||||
| 					{ | ||||
| 						reverb = value; | ||||
| 
 | ||||
|  | @ -178,6 +207,7 @@ namespace MoonWorks.Audio | |||
| 							0 | ||||
| 						); | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				#if DEBUG | ||||
| 				if (ReverbEffect == null) | ||||
|  | @ -188,7 +218,7 @@ namespace MoonWorks.Audio | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public SoundInstance( | ||||
| 		public unsafe SoundInstance( | ||||
| 			AudioDevice device, | ||||
| 			ushort formatTag, | ||||
| 			ushort bitsPerSample, | ||||
|  | @ -197,7 +227,7 @@ namespace MoonWorks.Audio | |||
| 			uint samplesPerSecond | ||||
| 		) : base(device) | ||||
| 		{ | ||||
| 			var format = new FAudio.FAudioWaveFormatEx | ||||
| 			format = new FAudio.FAudioWaveFormatEx | ||||
| 			{ | ||||
| 				wFormatTag = formatTag, | ||||
| 				wBitsPerSample = bitsPerSample, | ||||
|  | @ -207,12 +237,10 @@ namespace MoonWorks.Audio | |||
| 				nAvgBytesPerSec = blockAlign * samplesPerSecond | ||||
| 			}; | ||||
| 
 | ||||
| 			Format = format; | ||||
| 
 | ||||
| 			FAudio.FAudio_CreateSourceVoice( | ||||
| 				Device.Handle, | ||||
| 				out Voice, | ||||
| 				ref Format, | ||||
| 				ref format, | ||||
| 				FAudio.FAUDIO_VOICE_USEFILTER, | ||||
| 				FAudio.FAUDIO_DEFAULT_FREQ_RATIO, | ||||
| 				IntPtr.Zero, | ||||
|  | @ -277,6 +305,91 @@ namespace MoonWorks.Audio | |||
| 			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 QueueSyncPlay(); | ||||
| 		public abstract void Pause(); | ||||
|  | @ -297,14 +410,12 @@ namespace MoonWorks.Audio | |||
| 			); | ||||
| 
 | ||||
| 			dspSettings.pMatrixCoefficients = (nint) NativeMemory.Alloc(memsize); | ||||
| 			unsafe | ||||
| 			{ | ||||
| 			byte* memPtr = (byte*) dspSettings.pMatrixCoefficients; | ||||
| 			for (uint i = 0; i < memsize; i += 1) | ||||
| 			{ | ||||
| 				memPtr[i] = 0; | ||||
| 			} | ||||
| 			} | ||||
| 
 | ||||
| 			SetPanMatrixCoefficients(); | ||||
| 		} | ||||
| 
 | ||||
|  |  | |||
|  | @ -10,11 +10,21 @@ namespace MoonWorks.Audio | |||
| 	/// </summary> | ||||
| 	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 readonly IntPtr[] buffers; | ||||
| 		private int nextBufferIndex = 0; | ||||
| 		private uint queuedBufferCount = 0; | ||||
| 		protected abstract int BUFFER_SIZE { get; } | ||||
| 
 | ||||
| 		private readonly object StateLock = new object(); | ||||
| 
 | ||||
| 		public unsafe StreamingSound( | ||||
| 			AudioDevice device, | ||||
|  | @ -25,8 +35,6 @@ namespace MoonWorks.Audio | |||
| 			uint samplesPerSecond | ||||
| 		) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond) | ||||
| 		{ | ||||
| 			device.AddDynamicSoundInstance(this); | ||||
| 
 | ||||
| 			buffers = new IntPtr[BUFFER_COUNT]; | ||||
| 			for (int i = 0; i < BUFFER_COUNT; i += 1) | ||||
| 			{ | ||||
|  | @ -45,6 +53,8 @@ namespace MoonWorks.Audio | |||
| 		} | ||||
| 
 | ||||
| 		private void PlayUsingOperationSet(uint operationSet) | ||||
| 		{ | ||||
| 			lock (StateLock) | ||||
| 			{ | ||||
| 				if (State == SoundState.Playing) | ||||
| 				{ | ||||
|  | @ -53,40 +63,65 @@ namespace MoonWorks.Audio | |||
| 
 | ||||
| 				State = SoundState.Playing; | ||||
| 
 | ||||
| 			Update(); | ||||
| 				ConsumingBuffers = true; | ||||
| 				QueueBuffers(); | ||||
| 				FAudio.FAudioSourceVoice_Start(Voice, 0, operationSet); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public override void Pause() | ||||
| 		{ | ||||
| 			lock (StateLock) | ||||
| 			{ | ||||
| 				if (State == SoundState.Playing) | ||||
| 				{ | ||||
| 					ConsumingBuffers = false; | ||||
| 					FAudio.FAudioSourceVoice_Stop(Voice, 0, 0); | ||||
| 					State = SoundState.Paused; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public override void Stop() | ||||
| 		{ | ||||
| 			lock (StateLock) | ||||
| 			{ | ||||
| 				ConsumingBuffers = false; | ||||
| 				State = SoundState.Stopped; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public override void StopImmediate() | ||||
| 		{ | ||||
| 			lock (StateLock) | ||||
| 			{ | ||||
| 				ConsumingBuffers = false; | ||||
| 				FAudio.FAudioSourceVoice_Stop(Voice, 0, 0); | ||||
| 				FAudio.FAudioSourceVoice_FlushSourceBuffers(Voice); | ||||
| 				ClearBuffers(); | ||||
| 
 | ||||
| 				State = SoundState.Stopped; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		internal unsafe void Update() | ||||
| 		{ | ||||
| 			lock (StateLock) | ||||
| 			{ | ||||
| 				if (!IsDisposed) | ||||
| 				{ | ||||
| 					if (State != SoundState.Playing) | ||||
| 					{ | ||||
| 						return; | ||||
| 					} | ||||
| 
 | ||||
| 					QueueBuffers(); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		protected void QueueBuffers() | ||||
| 		{ | ||||
| 			FAudio.FAudioSourceVoice_GetState( | ||||
| 				Voice, | ||||
| 				out var state, | ||||
|  | @ -95,16 +130,18 @@ namespace MoonWorks.Audio | |||
| 
 | ||||
| 			queuedBufferCount = state.BuffersQueued; | ||||
| 
 | ||||
| 			QueueBuffers(); | ||||
| 		} | ||||
| 
 | ||||
| 		protected void QueueBuffers() | ||||
| 			if (ConsumingBuffers) | ||||
| 			{ | ||||
| 				for (int i = 0; i < BUFFER_COUNT - queuedBufferCount; i += 1) | ||||
| 				{ | ||||
| 					AddBuffer(); | ||||
| 				} | ||||
| 			} | ||||
| 			else if (queuedBufferCount == 0) | ||||
| 			{ | ||||
| 				Stop(); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		protected unsafe void ClearBuffers() | ||||
| 		{ | ||||
|  | @ -124,6 +161,8 @@ namespace MoonWorks.Audio | |||
| 				out bool reachedEnd | ||||
| 			); | ||||
| 
 | ||||
| 			if (filledLengthInBytes > 0) | ||||
| 			{ | ||||
| 				FAudio.FAudioBuffer buf = new FAudio.FAudioBuffer | ||||
| 				{ | ||||
| 					AudioBytes = (uint) filledLengthInBytes, | ||||
|  | @ -142,19 +181,16 @@ namespace MoonWorks.Audio | |||
| 				); | ||||
| 
 | ||||
| 				queuedBufferCount += 1; | ||||
| 			} | ||||
| 
 | ||||
| 			/* We have reached the end of the file, what do we do? */ | ||||
| 			if (reachedEnd) | ||||
| 			{ | ||||
| 				/* We have reached the end of the data, what do we do? */ | ||||
| 				ConsumingBuffers = false; | ||||
| 				OnReachedEnd(); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		protected virtual void OnReachedEnd() | ||||
| 		{ | ||||
| 			Stop(); | ||||
| 		} | ||||
| 
 | ||||
| 		protected unsafe abstract void FillBuffer( | ||||
| 			void* buffer, | ||||
| 			int bufferLengthInBytes, /* in bytes */ | ||||
|  | @ -162,7 +198,13 @@ namespace MoonWorks.Audio | |||
| 			out bool reachedEnd | ||||
| 		); | ||||
| 
 | ||||
| 		protected abstract void OnReachedEnd(); | ||||
| 
 | ||||
| 		protected unsafe override void Destroy() | ||||
| 		{ | ||||
| 			lock (StateLock) | ||||
| 			{ | ||||
| 				if (!IsDisposed) | ||||
| 				{ | ||||
| 					StopImmediate(); | ||||
| 
 | ||||
|  | @ -173,3 +215,5 @@ namespace MoonWorks.Audio | |||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ namespace MoonWorks.Audio | |||
| 		private FAudio.stb_vorbis_info Info; | ||||
| 
 | ||||
| 		protected override int BUFFER_SIZE => 32768; | ||||
| 		public override bool AutoUpdate => true; | ||||
| 
 | ||||
| 		public unsafe static StreamingSoundOgg Load(AudioDevice device, string filePath) | ||||
| 		{ | ||||
|  | @ -35,7 +36,7 @@ namespace MoonWorks.Audio | |||
| 			); | ||||
| 		} | ||||
| 
 | ||||
| 		internal StreamingSoundOgg( | ||||
| 		internal unsafe StreamingSoundOgg( | ||||
| 			AudioDevice device, | ||||
| 			IntPtr fileDataPtr, // MUST BE A NATIVE MEMORY HANDLE!! | ||||
| 			IntPtr vorbisHandle, | ||||
|  | @ -47,8 +48,7 @@ namespace MoonWorks.Audio | |||
| 			(ushort) (4 * info.channels), | ||||
| 			(ushort) info.channels, | ||||
| 			info.sample_rate | ||||
| 		) | ||||
| 		{ | ||||
| 		) { | ||||
| 			FileDataPtr = fileDataPtr; | ||||
| 			VorbisHandle = vorbisHandle; | ||||
| 			Info = info; | ||||
|  | @ -64,8 +64,7 @@ namespace MoonWorks.Audio | |||
| 			int bufferLengthInBytes, | ||||
| 			out int filledLengthInBytes, | ||||
| 			out bool reachedEnd | ||||
| 		) | ||||
| 		{ | ||||
| 		) { | ||||
| 			var lengthInFloats = bufferLengthInBytes / sizeof(float); | ||||
| 
 | ||||
| 			/* NOTE: this function returns samples per channel, not total samples */ | ||||
|  | @ -82,9 +81,14 @@ namespace MoonWorks.Audio | |||
| 		} | ||||
| 
 | ||||
| 		protected unsafe override void Destroy() | ||||
| 		{ | ||||
| 			base.Destroy(); | ||||
| 
 | ||||
| 			if (!IsDisposed) | ||||
| 			{ | ||||
| 				FAudio.stb_vorbis_close(VorbisHandle); | ||||
| 				NativeMemory.Free((void*) FileDataPtr); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -14,12 +14,9 @@ namespace MoonWorks.Audio | |||
| 		{ | ||||
| 			if (Loop) | ||||
| 			{ | ||||
| 				ConsumingBuffers = true; | ||||
| 				Seek(0); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				Stop(); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -168,9 +168,8 @@ namespace MoonWorks | |||
| 				while (accumulatedUpdateTime >= Timestep) | ||||
| 				{ | ||||
| 					Inputs.Update(); | ||||
| 					AudioDevice.Update(); | ||||
| 
 | ||||
| 					Update(Timestep); | ||||
| 					AudioDevice.WakeThread(); | ||||
| 
 | ||||
| 					accumulatedUpdateTime -= Timestep; | ||||
| 				} | ||||
|  |  | |||
|  | @ -18,7 +18,7 @@ namespace MoonWorks.Graphics | |||
| 
 | ||||
| 		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( | ||||
| 			Backend preferredBackend, | ||||
|  | @ -237,10 +237,9 @@ namespace MoonWorks.Graphics | |||
| 				{ | ||||
| 					lock (resources) | ||||
| 					{ | ||||
| 						for (var i = resources.Count - 1; i >= 0; i--) | ||||
| 						foreach (var weakReference in resources) | ||||
| 						{ | ||||
| 							var resource = resources[i]; | ||||
| 							if (resource.TryGetTarget(out var target)) | ||||
| 							if (weakReference.TryGetTarget(out var target)) | ||||
| 							{ | ||||
| 								target.Dispose(); | ||||
| 							} | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ namespace MoonWorks.Graphics | |||
| 		public bool IsDisposed { get; private set; } | ||||
| 		protected abstract Action<IntPtr, IntPtr> QueueDestroyFunction { get; } | ||||
| 
 | ||||
| 		private WeakReference<GraphicsResource> selfReference; | ||||
| 		internal WeakReference<GraphicsResource> weakReference; | ||||
| 
 | ||||
| 		public GraphicsResource(GraphicsDevice device, bool trackResource = true) | ||||
| 		{ | ||||
|  | @ -18,8 +18,8 @@ namespace MoonWorks.Graphics | |||
| 
 | ||||
| 			if (trackResource) | ||||
| 			{ | ||||
| 				selfReference = new WeakReference<GraphicsResource>(this); | ||||
| 				Device.AddResourceReference(selfReference); | ||||
| 				weakReference = new WeakReference<GraphicsResource>(this); | ||||
| 				Device.AddResourceReference(weakReference); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
|  | @ -27,11 +27,11 @@ namespace MoonWorks.Graphics | |||
| 		{ | ||||
| 			if (!IsDisposed) | ||||
| 			{ | ||||
| 				if (selfReference != null) | ||||
| 				if (weakReference != null) | ||||
| 				{ | ||||
| 					QueueDestroyFunction(Device.Handle, Handle); | ||||
| 					Device.RemoveResourceReference(selfReference); | ||||
| 					selfReference = null; | ||||
| 					Device.RemoveResourceReference(weakReference); | ||||
| 					weakReference = null; | ||||
| 				} | ||||
| 
 | ||||
| 				IsDisposed = true; | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -7,6 +7,8 @@ namespace MoonWorks.Video | |||
| 	{ | ||||
| 		private IntPtr VideoHandle; | ||||
| 		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( | ||||
| 			AudioDevice device, | ||||
|  | @ -32,6 +34,11 @@ namespace MoonWorks.Video | |||
| 		) { | ||||
| 			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( | ||||
| 					VideoHandle, | ||||
| 					(IntPtr) buffer, | ||||
|  | @ -42,4 +49,7 @@ namespace MoonWorks.Video | |||
| 				reachedEnd = Theorafile.tf_eos(VideoHandle) == 1; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		protected override void OnReachedEnd() { } | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ namespace MoonWorks.Video | |||
| 		private int yWidth; | ||||
| 		private int yHeight; | ||||
| 
 | ||||
| 		private bool disposed; | ||||
| 		private bool IsDisposed; | ||||
| 
 | ||||
| 		public Video(string filename) | ||||
| 		{ | ||||
|  | @ -89,7 +89,7 @@ namespace MoonWorks.Video | |||
| 
 | ||||
| 		protected virtual void Dispose(bool disposing) | ||||
| 		{ | ||||
| 			if (!disposed) | ||||
| 			if (!IsDisposed) | ||||
| 			{ | ||||
| 				if (disposing) | ||||
| 				{ | ||||
|  | @ -100,7 +100,7 @@ namespace MoonWorks.Video | |||
| 				Theorafile.tf_close(ref Handle); | ||||
| 				NativeMemory.Free(videoData); | ||||
| 
 | ||||
| 				disposed = true; | ||||
| 				IsDisposed = true; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| using System; | ||||
| using System; | ||||
| using System.Diagnostics; | ||||
| using System.Runtime.InteropServices; | ||||
| using MoonWorks.Audio; | ||||
|  | @ -130,6 +130,8 @@ namespace MoonWorks.Video | |||
| 
 | ||||
| 		public void Play() | ||||
| 		{ | ||||
| 			if (Video == null) { return; } | ||||
| 
 | ||||
| 			if (State == VideoState.Playing) | ||||
| 			{ | ||||
| 				return; | ||||
|  | @ -147,6 +149,8 @@ namespace MoonWorks.Video | |||
| 
 | ||||
| 		public void Pause() | ||||
| 		{ | ||||
| 			if (Video == null) { return; } | ||||
| 
 | ||||
| 			if (State != VideoState.Playing) | ||||
| 			{ | ||||
| 				return; | ||||
|  | @ -164,6 +168,8 @@ namespace MoonWorks.Video | |||
| 
 | ||||
| 		public void Stop() | ||||
| 		{ | ||||
| 			if (Video == null) { return; } | ||||
| 
 | ||||
| 			if (State == VideoState.Stopped) | ||||
| 			{ | ||||
| 				return; | ||||
|  | @ -172,20 +178,32 @@ namespace MoonWorks.Video | |||
| 			timer.Stop(); | ||||
| 			timer.Reset(); | ||||
| 
 | ||||
| 			Theorafile.tf_reset(Video.Handle); | ||||
| 			lastTimestamp = 0; | ||||
| 			timeElapsed = 0; | ||||
| 
 | ||||
| 			if (audioStream != null) | ||||
| 			{ | ||||
| 				audioStream.StopImmediate(); | ||||
| 				audioStream.Dispose(); | ||||
| 				audioStream = null; | ||||
| 			} | ||||
| 			DestroyAudioStream(); | ||||
| 
 | ||||
| 			Theorafile.tf_reset(Video.Handle); | ||||
| 
 | ||||
| 			State = VideoState.Stopped; | ||||
| 		} | ||||
| 
 | ||||
| 		public void Unload() | ||||
| 		{ | ||||
| 			Stop(); | ||||
| 			Video = null; | ||||
| 		} | ||||
| 
 | ||||
| 		public void Update() | ||||
| 		{ | ||||
| 			if (Video == null) { return; } | ||||
| 
 | ||||
| 			if (audioStream != null) | ||||
| 			{ | ||||
| 				audioStream.Update(); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public void Render() | ||||
| 		{ | ||||
| 			if (Video == null || State == VideoState.Stopped) | ||||
|  | @ -203,7 +221,8 @@ namespace MoonWorks.Video | |||
| 					Video.Handle, | ||||
| 					(IntPtr) yuvData, | ||||
| 					thisFrame - currentFrame | ||||
| 				) == 1 || currentFrame == -1) { | ||||
| 				) == 1 || currentFrame == -1) | ||||
| 				{ | ||||
| 					UpdateRenderTexture(); | ||||
| 				} | ||||
| 
 | ||||
|  | @ -216,12 +235,7 @@ namespace MoonWorks.Video | |||
| 				timer.Stop(); | ||||
| 				timer.Reset(); | ||||
| 
 | ||||
| 				if (audioStream != null) | ||||
| 				{ | ||||
| 					audioStream.Stop(); | ||||
| 					audioStream.Dispose(); | ||||
| 					audioStream = null; | ||||
| 				} | ||||
| 				DestroyAudioStream(); | ||||
| 
 | ||||
| 				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. | ||||
| 			if (AudioDevice != null && Theorafile.tf_hasaudio(Video.Handle) == 1) | ||||
| 			{ | ||||
| 				DestroyAudioStream(); | ||||
| 
 | ||||
| 				int channels, sampleRate; | ||||
| 				Theorafile.tf_audioinfo(Video.Handle, out channels, out sampleRate); | ||||
| 
 | ||||
| 				audioStream = new StreamingSoundTheora(AudioDevice, Video.Handle, channels, (uint) sampleRate); | ||||
| 			} | ||||
| 
 | ||||
| 			currentFrame = -1; | ||||
| 		} | ||||
| 
 | ||||
| 		private void DestroyAudioStream() | ||||
| 		{ | ||||
| 			if (audioStream != null) | ||||
| 			{ | ||||
| 				audioStream.StopImmediate(); | ||||
| 				audioStream.Dispose(); | ||||
| 				audioStream = null; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		protected virtual void Dispose(bool disposing) | ||||
| 		{ | ||||
| 			if (!disposed) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue