Sound instancing rework
							parent
							
								
									537517afb9
								
							
						
					
					
						commit
						5df08727c1
					
				|  | @ -1 +1 @@ | ||||||
| Subproject commit aaf2568c3e5b202c5cfbd74734386e69f204482c | Subproject commit 63071f2c309f6fc2193de1c6b85da0e31df80040 | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| using System; | using System; | ||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
|  | using System.Runtime.InteropServices; | ||||||
| using System.Threading; | using System.Threading; | ||||||
| 
 | 
 | ||||||
| namespace MoonWorks.Audio | namespace MoonWorks.Audio | ||||||
|  | @ -27,7 +28,8 @@ namespace MoonWorks.Audio | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		private readonly HashSet<WeakReference> resources = new HashSet<WeakReference>(); | 		private readonly HashSet<WeakReference> resources = new HashSet<WeakReference>(); | ||||||
| 		private readonly HashSet<WeakReference> autoUpdateStreamingSoundReferences = new HashSet<WeakReference>(); | 		private readonly List<WeakReference> autoUpdateStreamingSoundReferences = new List<WeakReference>(); | ||||||
|  | 		private readonly List<WeakReference> autoFreeStaticSoundInstanceReferences = new List<WeakReference>(); | ||||||
| 
 | 
 | ||||||
| 		private AudioTweenManager AudioTweenManager; | 		private AudioTweenManager AudioTweenManager; | ||||||
| 
 | 
 | ||||||
|  | @ -150,15 +152,35 @@ namespace MoonWorks.Audio | ||||||
| 			previousTickTime = TickStopwatch.Elapsed.Ticks; | 			previousTickTime = TickStopwatch.Elapsed.Ticks; | ||||||
| 			float elapsedSeconds = (float) tickDelta / System.TimeSpan.TicksPerSecond; | 			float elapsedSeconds = (float) tickDelta / System.TimeSpan.TicksPerSecond; | ||||||
| 
 | 
 | ||||||
| 			foreach (var weakReference in autoUpdateStreamingSoundReferences) | 			for (var i = autoUpdateStreamingSoundReferences.Count - 1; i >= 0; i -= 1) | ||||||
| 			{ | 			{ | ||||||
| 				if (weakReference.Target is StreamingSound streamingSound) | 				var weakReference = autoUpdateStreamingSoundReferences[i]; | ||||||
|  | 
 | ||||||
|  | 				if (weakReference.Target is StreamingSound streamingSound && streamingSound.Loaded) | ||||||
| 				{ | 				{ | ||||||
| 					streamingSound.Update(); | 					streamingSound.Update(); | ||||||
| 				} | 				} | ||||||
| 				else | 				else | ||||||
| 				{ | 				{ | ||||||
| 					autoUpdateStreamingSoundReferences.Remove(weakReference); | 					autoUpdateStreamingSoundReferences.RemoveAt(i); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			for (var i = autoFreeStaticSoundInstanceReferences.Count - 1; i >= 0; i -= 1) | ||||||
|  | 			{ | ||||||
|  | 				var weakReference = autoFreeStaticSoundInstanceReferences[i]; | ||||||
|  | 
 | ||||||
|  | 				if (weakReference.Target is StaticSoundInstance staticSoundInstance) | ||||||
|  | 				{ | ||||||
|  | 					if (staticSoundInstance.State == SoundState.Stopped) | ||||||
|  | 					{ | ||||||
|  | 						staticSoundInstance.Free(); | ||||||
|  | 						autoFreeStaticSoundInstanceReferences.RemoveAt(i); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 				else | ||||||
|  | 				{ | ||||||
|  | 					autoFreeStaticSoundInstanceReferences.RemoveAt(i); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | @ -213,11 +235,6 @@ namespace MoonWorks.Audio | ||||||
| 			lock (StateLock) | 			lock (StateLock) | ||||||
| 			{ | 			{ | ||||||
| 				resources.Add(resource.weakReference); | 				resources.Add(resource.weakReference); | ||||||
| 
 |  | ||||||
| 				if (resource is StreamingSound streamingSound && streamingSound.AutoUpdate) |  | ||||||
| 				{ |  | ||||||
| 					AddAutoUpdateStreamingSoundInstance(streamingSound); |  | ||||||
| 				} |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -226,22 +243,17 @@ namespace MoonWorks.Audio | ||||||
| 			lock (StateLock) | 			lock (StateLock) | ||||||
| 			{ | 			{ | ||||||
| 				resources.Remove(resource.weakReference); | 				resources.Remove(resource.weakReference); | ||||||
| 
 |  | ||||||
| 				if (resource is StreamingSound streamingSound && streamingSound.AutoUpdate) |  | ||||||
| 				{ |  | ||||||
| 					RemoveAutoUpdateStreamingSoundInstance(streamingSound); |  | ||||||
| 				} |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		private void AddAutoUpdateStreamingSoundInstance(StreamingSound instance) | 		internal void AddAutoUpdateStreamingSoundInstance(StreamingSound instance) | ||||||
| 		{ | 		{ | ||||||
| 			autoUpdateStreamingSoundReferences.Add(instance.weakReference); | 			autoUpdateStreamingSoundReferences.Add(instance.weakReference); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		private void RemoveAutoUpdateStreamingSoundInstance(StreamingSound instance) | 		internal void AddAutoFreeStaticSoundInstance(StaticSoundInstance instance) | ||||||
| 		{ | 		{ | ||||||
| 			autoUpdateStreamingSoundReferences.Remove(instance.weakReference); | 			autoFreeStaticSoundInstanceReferences.Add(instance.weakReference); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		protected virtual void Dispose(bool disposing) | 		protected virtual void Dispose(bool disposing) | ||||||
|  |  | ||||||
|  | @ -29,6 +29,7 @@ namespace MoonWorks.Audio | ||||||
| 				if (weakReference != null) | 				if (weakReference != null) | ||||||
| 				{ | 				{ | ||||||
| 					Device.RemoveResourceReference(this); | 					Device.RemoveResourceReference(this); | ||||||
|  | 					weakReference.Target = null; | ||||||
| 					weakReference = null; | 					weakReference = null; | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -17,7 +17,8 @@ namespace MoonWorks.Audio | ||||||
| 		public uint LoopStart { get; set; } = 0; | 		public uint LoopStart { get; set; } = 0; | ||||||
| 		public uint LoopLength { get; set; } = 0; | 		public uint LoopLength { get; set; } = 0; | ||||||
| 
 | 
 | ||||||
| 		private Stack<StaticSoundInstance> Instances = new Stack<StaticSoundInstance>(); | 		private Stack<StaticSoundInstance> AvailableInstances = new Stack<StaticSoundInstance>(); | ||||||
|  | 		private HashSet<StaticSoundInstance> UsedInstances = new HashSet<StaticSoundInstance>(); | ||||||
| 
 | 
 | ||||||
| 		private bool OwnsBuffer; | 		private bool OwnsBuffer; | ||||||
| 
 | 
 | ||||||
|  | @ -267,22 +268,25 @@ namespace MoonWorks.Audio | ||||||
| 
 | 
 | ||||||
| 		/// <summary> | 		/// <summary> | ||||||
| 		/// Gets a sound instance from the pool. | 		/// Gets a sound instance from the pool. | ||||||
| 		/// NOTE: If you lose track of instances, you will create garbage collection pressure! | 		/// NOTE: If AutoFree is false, you will have to call StaticSoundInstance.Free() yourself or leak the instance! | ||||||
| 		/// </summary> | 		/// </summary> | ||||||
| 		public StaticSoundInstance GetInstance() | 		public StaticSoundInstance GetInstance(bool autoFree = true) | ||||||
| 		{ | 		{ | ||||||
| 			if (Instances.Count == 0) | 			if (AvailableInstances.Count == 0) | ||||||
| 			{ | 			{ | ||||||
| 				Instances.Push(new StaticSoundInstance(Device, this)); | 				AvailableInstances.Push(new StaticSoundInstance(Device, this, autoFree)); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			return Instances.Pop(); | 			var instance = AvailableInstances.Pop(); | ||||||
|  | 			UsedInstances.Add(instance); | ||||||
|  | 			return instance; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		internal void FreeInstance(StaticSoundInstance instance) | 		internal void FreeInstance(StaticSoundInstance instance) | ||||||
| 		{ | 		{ | ||||||
| 			instance.Reset(); | 			instance.Reset(); | ||||||
| 			Instances.Push(instance); | 			UsedInstances.Remove(instance); | ||||||
|  | 			AvailableInstances.Push(instance); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		protected override unsafe void Destroy() | 		protected override unsafe void Destroy() | ||||||
|  |  | ||||||
|  | @ -32,12 +32,21 @@ namespace MoonWorks.Audio | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		public bool AutoFree { get; } | ||||||
|  | 
 | ||||||
| 		internal StaticSoundInstance( | 		internal StaticSoundInstance( | ||||||
| 			AudioDevice device, | 			AudioDevice device, | ||||||
| 			StaticSound parent | 			StaticSound parent, | ||||||
|  | 			bool autoFree | ||||||
| 		) : base(device, parent.FormatTag, parent.BitsPerSample, parent.BlockAlign, parent.Channels, parent.SamplesPerSecond) | 		) : base(device, parent.FormatTag, parent.BitsPerSample, parent.BlockAlign, parent.Channels, parent.SamplesPerSecond) | ||||||
| 		{ | 		{ | ||||||
| 			Parent = parent; | 			Parent = parent; | ||||||
|  | 			AutoFree = autoFree; | ||||||
|  | 
 | ||||||
|  | 			if (AutoFree) | ||||||
|  | 			{ | ||||||
|  | 				device.AddAutoFreeStaticSoundInstance(this); | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public override void Play() | 		public override void Play() | ||||||
|  | @ -113,9 +122,11 @@ namespace MoonWorks.Audio | ||||||
| 			Parent.Handle.PlayBegin = sampleFrame; | 			Parent.Handle.PlayBegin = sampleFrame; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		// Call this when you no longer need the sound instance. | ||||||
|  | 		// If AutoFree is set, this will automatically be called when the sound instance stops playing. | ||||||
|  | 		// If the sound isn't stopped when you call this, things might get weird! | ||||||
| 		public void Free() | 		public void Free() | ||||||
| 		{ | 		{ | ||||||
| 			StopImmediate(); |  | ||||||
| 			Parent.FreeInstance(this); | 			Parent.FreeInstance(this); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -10,9 +10,6 @@ namespace MoonWorks.Audio | ||||||
| 	/// </summary> | 	/// </summary> | ||||||
| 	public abstract class StreamingSound : SoundInstance | 	public abstract class StreamingSound : SoundInstance | ||||||
| 	{ | 	{ | ||||||
| 		// Should the AudioDevice thread automatically update this class? |  | ||||||
| 		public abstract bool AutoUpdate { get; } |  | ||||||
| 
 |  | ||||||
| 		// Are we actively consuming buffers? | 		// Are we actively consuming buffers? | ||||||
| 		protected bool ConsumingBuffers = false; | 		protected bool ConsumingBuffers = false; | ||||||
| 
 | 
 | ||||||
|  | @ -24,6 +21,10 @@ namespace MoonWorks.Audio | ||||||
| 
 | 
 | ||||||
| 		private readonly object StateLock = new object(); | 		private readonly object StateLock = new object(); | ||||||
| 
 | 
 | ||||||
|  | 		public bool AutoUpdate { get; } | ||||||
|  | 
 | ||||||
|  | 		public abstract bool Loaded { get; } | ||||||
|  | 
 | ||||||
| 		public unsafe StreamingSound( | 		public unsafe StreamingSound( | ||||||
| 			AudioDevice device, | 			AudioDevice device, | ||||||
| 			ushort formatTag, | 			ushort formatTag, | ||||||
|  | @ -31,7 +32,8 @@ namespace MoonWorks.Audio | ||||||
| 			ushort blockAlign, | 			ushort blockAlign, | ||||||
| 			ushort channels, | 			ushort channels, | ||||||
| 			uint samplesPerSecond, | 			uint samplesPerSecond, | ||||||
| 			uint bufferSize | 			uint bufferSize, | ||||||
|  | 			bool autoUpdate // should the AudioDevice thread automatically update this sound? | ||||||
| 		) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond) | 		) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond) | ||||||
| 		{ | 		{ | ||||||
| 			BufferSize = bufferSize; | 			BufferSize = bufferSize; | ||||||
|  | @ -41,6 +43,8 @@ namespace MoonWorks.Audio | ||||||
| 			{ | 			{ | ||||||
| 				buffers[i] = (IntPtr) NativeMemory.Alloc(bufferSize); | 				buffers[i] = (IntPtr) NativeMemory.Alloc(bufferSize); | ||||||
| 			} | 			} | ||||||
|  | 
 | ||||||
|  | 			AutoUpdate = autoUpdate; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public override void Play() | 		public override void Play() | ||||||
|  | @ -57,6 +61,12 @@ namespace MoonWorks.Audio | ||||||
| 		{ | 		{ | ||||||
| 			lock (StateLock) | 			lock (StateLock) | ||||||
| 			{ | 			{ | ||||||
|  | 				if (!Loaded) | ||||||
|  | 				{ | ||||||
|  | 					Logger.LogError("Cannot play StreamingSound before calling Load!"); | ||||||
|  | 					return; | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
| 				if (State == SoundState.Playing) | 				if (State == SoundState.Playing) | ||||||
| 				{ | 				{ | ||||||
| 					return; | 					return; | ||||||
|  | @ -65,6 +75,11 @@ namespace MoonWorks.Audio | ||||||
| 				State = SoundState.Playing; | 				State = SoundState.Playing; | ||||||
| 
 | 
 | ||||||
| 				ConsumingBuffers = true; | 				ConsumingBuffers = true; | ||||||
|  | 				if (AutoUpdate) | ||||||
|  | 				{ | ||||||
|  | 					Device.AddAutoUpdateStreamingSoundInstance(this); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
| 				QueueBuffers(); | 				QueueBuffers(); | ||||||
| 				FAudio.FAudioSourceVoice_Start(Voice, 0, operationSet); | 				FAudio.FAudioSourceVoice_Start(Voice, 0, operationSet); | ||||||
| 			} | 			} | ||||||
|  | @ -192,6 +207,9 @@ namespace MoonWorks.Audio | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		public abstract void Load(); | ||||||
|  | 		public abstract void Unload(); | ||||||
|  | 
 | ||||||
| 		protected unsafe abstract void FillBuffer( | 		protected unsafe abstract void FillBuffer( | ||||||
| 			void* buffer, | 			void* buffer, | ||||||
| 			int bufferLengthInBytes, /* in bytes */ | 			int bufferLengthInBytes, /* in bytes */ | ||||||
|  | @ -208,6 +226,7 @@ namespace MoonWorks.Audio | ||||||
| 				if (!IsDisposed) | 				if (!IsDisposed) | ||||||
| 				{ | 				{ | ||||||
| 					StopImmediate(); | 					StopImmediate(); | ||||||
|  | 					Unload(); | ||||||
| 
 | 
 | ||||||
| 					for (int i = 0; i < BUFFER_COUNT; i += 1) | 					for (int i = 0; i < BUFFER_COUNT; i += 1) | ||||||
| 					{ | 					{ | ||||||
|  |  | ||||||
|  | @ -6,41 +6,38 @@ namespace MoonWorks.Audio | ||||||
| { | { | ||||||
| 	public class StreamingSoundOgg : StreamingSoundSeekable | 	public class StreamingSoundOgg : StreamingSoundSeekable | ||||||
| 	{ | 	{ | ||||||
| 		private IntPtr VorbisHandle; | 		private IntPtr FileDataPtr = IntPtr.Zero; | ||||||
| 		private IntPtr FileDataPtr; | 		private IntPtr VorbisHandle = IntPtr.Zero; | ||||||
| 		private FAudio.stb_vorbis_info Info; | 		private FAudio.stb_vorbis_info Info; | ||||||
| 		public override bool AutoUpdate => true; |  | ||||||
| 
 | 
 | ||||||
| 		public unsafe static StreamingSoundOgg Load(AudioDevice device, string filePath) | 		public override bool Loaded => VorbisHandle != IntPtr.Zero; | ||||||
|  | 		private string FilePath; | ||||||
|  | 
 | ||||||
|  | 		public unsafe static StreamingSoundOgg Create(AudioDevice device, string filePath) | ||||||
| 		{ | 		{ | ||||||
| 			var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read); | 			var handle = FAudio.stb_vorbis_open_filename(filePath, out int error, IntPtr.Zero); | ||||||
| 			var fileDataPtr = NativeMemory.Alloc((nuint) fileStream.Length); |  | ||||||
| 			var fileDataSpan = new Span<byte>(fileDataPtr, (int) fileStream.Length); |  | ||||||
| 			fileStream.ReadExactly(fileDataSpan); |  | ||||||
| 			fileStream.Close(); |  | ||||||
| 
 |  | ||||||
| 			var vorbisHandle = FAudio.stb_vorbis_open_memory((IntPtr) fileDataPtr, fileDataSpan.Length, out int error, IntPtr.Zero); |  | ||||||
| 			if (error != 0) | 			if (error != 0) | ||||||
| 			{ | 			{ | ||||||
| 				NativeMemory.Free(fileDataPtr); |  | ||||||
| 				Logger.LogError("Error opening OGG file!"); |  | ||||||
| 				Logger.LogError("Error: " + error); | 				Logger.LogError("Error: " + error); | ||||||
| 				throw new AudioLoadException("Error opening OGG file!"); | 				throw new AudioLoadException("Error opening ogg file!"); | ||||||
| 			} | 			} | ||||||
| 			var info = FAudio.stb_vorbis_get_info(vorbisHandle); |  | ||||||
| 
 | 
 | ||||||
| 			return new StreamingSoundOgg( | 			var info = FAudio.stb_vorbis_get_info(handle); | ||||||
|  | 
 | ||||||
|  | 			var streamingSound = new StreamingSoundOgg( | ||||||
| 				device, | 				device, | ||||||
| 				(IntPtr) fileDataPtr, | 				filePath, | ||||||
| 				vorbisHandle, |  | ||||||
| 				info | 				info | ||||||
| 			); | 			); | ||||||
|  | 
 | ||||||
|  | 			FAudio.stb_vorbis_close(handle); | ||||||
|  | 
 | ||||||
|  | 			return streamingSound; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		internal unsafe StreamingSoundOgg( | 		internal unsafe StreamingSoundOgg( | ||||||
| 			AudioDevice device, | 			AudioDevice device, | ||||||
| 			IntPtr fileDataPtr, // MUST BE A NATIVE MEMORY HANDLE!! | 			string filePath, | ||||||
| 			IntPtr vorbisHandle, |  | ||||||
| 			FAudio.stb_vorbis_info info, | 			FAudio.stb_vorbis_info info, | ||||||
| 			uint bufferSize = 32768 | 			uint bufferSize = 32768 | ||||||
| 		) : base( | 		) : base( | ||||||
|  | @ -50,11 +47,11 @@ namespace MoonWorks.Audio | ||||||
| 			(ushort) (4 * info.channels), | 			(ushort) (4 * info.channels), | ||||||
| 			(ushort) info.channels, | 			(ushort) info.channels, | ||||||
| 			info.sample_rate, | 			info.sample_rate, | ||||||
| 			bufferSize | 			bufferSize, | ||||||
|  | 			true | ||||||
| 		) { | 		) { | ||||||
| 			FileDataPtr = fileDataPtr; |  | ||||||
| 			VorbisHandle = vorbisHandle; |  | ||||||
| 			Info = info; | 			Info = info; | ||||||
|  | 			FilePath = filePath; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public override void Seek(uint sampleFrame) | 		public override void Seek(uint sampleFrame) | ||||||
|  | @ -62,6 +59,36 @@ namespace MoonWorks.Audio | ||||||
| 			FAudio.stb_vorbis_seek(VorbisHandle, sampleFrame); | 			FAudio.stb_vorbis_seek(VorbisHandle, sampleFrame); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		public override unsafe void Load() | ||||||
|  | 		{ | ||||||
|  | 			var fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.Read); | ||||||
|  | 			FileDataPtr = (nint) NativeMemory.Alloc((nuint) fileStream.Length); | ||||||
|  | 			var fileDataSpan = new Span<byte>((void*) FileDataPtr, (int) fileStream.Length); | ||||||
|  | 			fileStream.ReadExactly(fileDataSpan); | ||||||
|  | 			fileStream.Close(); | ||||||
|  | 
 | ||||||
|  | 			VorbisHandle = FAudio.stb_vorbis_open_memory(FileDataPtr, fileDataSpan.Length, out int error, IntPtr.Zero); | ||||||
|  | 			if (error != 0) | ||||||
|  | 			{ | ||||||
|  | 				NativeMemory.Free((void*) FileDataPtr); | ||||||
|  | 				Logger.LogError("Error opening OGG file!"); | ||||||
|  | 				Logger.LogError("Error: " + error); | ||||||
|  | 				throw new AudioLoadException("Error opening OGG file!"); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public override unsafe void Unload() | ||||||
|  | 		{ | ||||||
|  | 			if (Loaded) | ||||||
|  | 			{ | ||||||
|  | 				FAudio.stb_vorbis_close(VorbisHandle); | ||||||
|  | 				NativeMemory.Free((void*) FileDataPtr); | ||||||
|  | 
 | ||||||
|  | 				VorbisHandle = IntPtr.Zero; | ||||||
|  | 				FileDataPtr = IntPtr.Zero; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		protected unsafe override void FillBuffer( | 		protected unsafe override void FillBuffer( | ||||||
| 			void* buffer, | 			void* buffer, | ||||||
| 			int bufferLengthInBytes, | 			int bufferLengthInBytes, | ||||||
|  | @ -82,16 +109,5 @@ namespace MoonWorks.Audio | ||||||
| 			reachedEnd = sampleCount < lengthInFloats; | 			reachedEnd = sampleCount < lengthInFloats; | ||||||
| 			filledLengthInBytes = sampleCount * sizeof(float); | 			filledLengthInBytes = sampleCount * sizeof(float); | ||||||
| 		} | 		} | ||||||
| 
 |  | ||||||
| 		protected unsafe override void Destroy() |  | ||||||
| 		{ |  | ||||||
| 			base.Destroy(); |  | ||||||
| 
 |  | ||||||
| 			if (!IsDisposed) |  | ||||||
| 			{ |  | ||||||
| 				FAudio.stb_vorbis_close(VorbisHandle); |  | ||||||
| 				NativeMemory.Free((void*) FileDataPtr); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -6,39 +6,31 @@ namespace MoonWorks.Audio | ||||||
| { | { | ||||||
| 	public class StreamingSoundQoa : StreamingSoundSeekable | 	public class StreamingSoundQoa : StreamingSoundSeekable | ||||||
| 	{ | 	{ | ||||||
| 		private IntPtr QoaHandle; | 		private IntPtr QoaHandle = IntPtr.Zero; | ||||||
| 		private IntPtr FileDataPtr; | 		private IntPtr FileDataPtr = IntPtr.Zero; | ||||||
| 
 |  | ||||||
| 		public override bool AutoUpdate => true; |  | ||||||
| 
 | 
 | ||||||
| 		uint Channels; | 		uint Channels; | ||||||
| 		uint SamplesPerChannelPerFrame; | 		uint SamplesPerChannelPerFrame; | ||||||
| 		uint TotalSamplesPerChannel; | 		uint TotalSamplesPerChannel; | ||||||
| 
 | 
 | ||||||
| 		public unsafe static StreamingSoundQoa Load(AudioDevice device, string filePath) | 		public override bool Loaded => QoaHandle != IntPtr.Zero; | ||||||
| 		{ | 		private string FilePath; | ||||||
| 			var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read); |  | ||||||
| 			var fileDataPtr = NativeMemory.Alloc((nuint) fileStream.Length); |  | ||||||
| 			var fileDataSpan = new Span<byte>(fileDataPtr, (int) fileStream.Length); |  | ||||||
| 			fileStream.ReadExactly(fileDataSpan); |  | ||||||
| 			fileStream.Close(); |  | ||||||
| 
 | 
 | ||||||
| 			var qoaHandle = FAudio.qoa_open_from_memory((char*) fileDataPtr, (uint) fileDataSpan.Length, 0); | 		public unsafe static StreamingSoundQoa Create(AudioDevice device, string filePath) | ||||||
| 			if (qoaHandle == 0) | 		{ | ||||||
|  | 			var handle = FAudio.qoa_open_from_filename(filePath); | ||||||
|  | 			if (handle == IntPtr.Zero) | ||||||
| 			{ | 			{ | ||||||
| 				NativeMemory.Free(fileDataPtr); |  | ||||||
| 				Logger.LogError("Error opening QOA file!"); |  | ||||||
| 				throw new AudioLoadException("Error opening QOA file!"); | 				throw new AudioLoadException("Error opening QOA file!"); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			FAudio.qoa_attributes(qoaHandle, out var channels, out var sampleRate, out var samplesPerChannelPerFrame, out var totalSamplesPerChannel); | 			FAudio.qoa_attributes(handle, out var channels, out var samplerate, out var samplesPerChannelPerFrame, out var totalSamplesPerChannel); | ||||||
| 
 | 
 | ||||||
| 			return new StreamingSoundQoa( | 			return new StreamingSoundQoa( | ||||||
| 				device, | 				device, | ||||||
| 				(IntPtr) fileDataPtr, | 				filePath, | ||||||
| 				qoaHandle, |  | ||||||
| 				channels, | 				channels, | ||||||
| 				sampleRate, | 				samplerate, | ||||||
| 				samplesPerChannelPerFrame, | 				samplesPerChannelPerFrame, | ||||||
| 				totalSamplesPerChannel | 				totalSamplesPerChannel | ||||||
| 			); | 			); | ||||||
|  | @ -46,8 +38,7 @@ namespace MoonWorks.Audio | ||||||
| 
 | 
 | ||||||
| 		internal unsafe StreamingSoundQoa( | 		internal unsafe StreamingSoundQoa( | ||||||
| 			AudioDevice device, | 			AudioDevice device, | ||||||
| 			IntPtr fileDataPtr, // MUST BE A NATIVE MEMORY HANDLE!! | 			string filePath, | ||||||
| 			IntPtr qoaHandle, |  | ||||||
| 			uint channels, | 			uint channels, | ||||||
| 			uint samplesPerSecond, | 			uint samplesPerSecond, | ||||||
| 			uint samplesPerChannelPerFrame, | 			uint samplesPerChannelPerFrame, | ||||||
|  | @ -59,13 +50,13 @@ namespace MoonWorks.Audio | ||||||
| 			(ushort) (2 * channels), | 			(ushort) (2 * channels), | ||||||
| 			(ushort) channels, | 			(ushort) channels, | ||||||
| 			samplesPerSecond, | 			samplesPerSecond, | ||||||
| 			samplesPerChannelPerFrame * channels * sizeof(short) | 			samplesPerChannelPerFrame * channels * sizeof(short), | ||||||
|  | 			true | ||||||
| 		) { | 		) { | ||||||
| 			FileDataPtr = fileDataPtr; |  | ||||||
| 			QoaHandle = qoaHandle; |  | ||||||
| 			Channels = channels; | 			Channels = channels; | ||||||
| 			SamplesPerChannelPerFrame = samplesPerChannelPerFrame; | 			SamplesPerChannelPerFrame = samplesPerChannelPerFrame; | ||||||
| 			TotalSamplesPerChannel = totalSamplesPerChannel; | 			TotalSamplesPerChannel = totalSamplesPerChannel; | ||||||
|  | 			FilePath = filePath; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public override void Seek(uint sampleFrame) | 		public override void Seek(uint sampleFrame) | ||||||
|  | @ -73,6 +64,35 @@ namespace MoonWorks.Audio | ||||||
| 			FAudio.qoa_seek_frame(QoaHandle, (int) sampleFrame); | 			FAudio.qoa_seek_frame(QoaHandle, (int) sampleFrame); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		public override unsafe void Load() | ||||||
|  | 		{ | ||||||
|  | 			var fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.Read); | ||||||
|  | 			FileDataPtr = (nint) NativeMemory.Alloc((nuint) fileStream.Length); | ||||||
|  | 			var fileDataSpan = new Span<byte>((void*) FileDataPtr, (int) fileStream.Length); | ||||||
|  | 			fileStream.ReadExactly(fileDataSpan); | ||||||
|  | 			fileStream.Close(); | ||||||
|  | 
 | ||||||
|  | 			QoaHandle = FAudio.qoa_open_from_memory((char*) FileDataPtr, (uint) fileDataSpan.Length, 0); | ||||||
|  | 			if (QoaHandle == IntPtr.Zero) | ||||||
|  | 			{ | ||||||
|  | 				NativeMemory.Free((void*) FileDataPtr); | ||||||
|  | 				Logger.LogError("Error opening QOA file!"); | ||||||
|  | 				throw new AudioLoadException("Error opening QOA file!"); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public override unsafe void Unload() | ||||||
|  | 		{ | ||||||
|  | 			if (Loaded) | ||||||
|  | 			{ | ||||||
|  | 				FAudio.qoa_close(QoaHandle); | ||||||
|  | 				NativeMemory.Free((void*) FileDataPtr); | ||||||
|  | 
 | ||||||
|  | 				QoaHandle = IntPtr.Zero; | ||||||
|  | 				FileDataPtr = IntPtr.Zero; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		protected override unsafe void FillBuffer( | 		protected override unsafe void FillBuffer( | ||||||
| 			void* buffer, | 			void* buffer, | ||||||
| 			int bufferLengthInBytes, | 			int bufferLengthInBytes, | ||||||
|  | @ -88,16 +108,5 @@ namespace MoonWorks.Audio | ||||||
| 			reachedEnd = sampleCount < lengthInShorts; | 			reachedEnd = sampleCount < lengthInShorts; | ||||||
| 			filledLengthInBytes = (int) (sampleCount * sizeof(short)); | 			filledLengthInBytes = (int) (sampleCount * sizeof(short)); | ||||||
| 		} | 		} | ||||||
| 
 |  | ||||||
| 		protected override unsafe void Destroy() |  | ||||||
| 		{ |  | ||||||
| 			base.Destroy(); |  | ||||||
| 
 |  | ||||||
| 			if (!IsDisposed) |  | ||||||
| 			{ |  | ||||||
| 				FAudio.qoa_close(QoaHandle); |  | ||||||
| 				NativeMemory.Free((void*) FileDataPtr); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -11,7 +11,8 @@ namespace MoonWorks.Audio | ||||||
| 			ushort blockAlign, | 			ushort blockAlign, | ||||||
| 			ushort channels, | 			ushort channels, | ||||||
| 			uint samplesPerSecond, | 			uint samplesPerSecond, | ||||||
| 			uint bufferSize | 			uint bufferSize, | ||||||
|  | 			bool autoUpdate | ||||||
| 		) : base( | 		) : base( | ||||||
| 			device, | 			device, | ||||||
| 			formatTag, | 			formatTag, | ||||||
|  | @ -19,7 +20,8 @@ namespace MoonWorks.Audio | ||||||
| 			blockAlign, | 			blockAlign, | ||||||
| 			channels, | 			channels, | ||||||
| 			samplesPerSecond, | 			samplesPerSecond, | ||||||
| 			bufferSize | 			bufferSize, | ||||||
|  | 			autoUpdate | ||||||
| 		) { | 		) { | ||||||
| 
 | 
 | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -31,6 +31,7 @@ namespace MoonWorks.Graphics | ||||||
| 				{ | 				{ | ||||||
| 					QueueDestroyFunction(Device.Handle, Handle); | 					QueueDestroyFunction(Device.Handle, Handle); | ||||||
| 					Device.RemoveResourceReference(weakReference); | 					Device.RemoveResourceReference(weakReference); | ||||||
|  | 					weakReference.SetTarget(null); | ||||||
| 					weakReference = null; | 					weakReference = null; | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,12 +3,11 @@ using MoonWorks.Audio; | ||||||
| 
 | 
 | ||||||
| namespace MoonWorks.Video | namespace MoonWorks.Video | ||||||
| { | { | ||||||
| 	public unsafe class StreamingSoundTheora : StreamingSound | 	// TODO: should we just not handle theora sound? it sucks! | ||||||
|  | 	internal unsafe class StreamingSoundTheora : StreamingSound | ||||||
| 	{ | 	{ | ||||||
| 		private IntPtr VideoHandle; | 		private IntPtr VideoHandle; | ||||||
| 
 | 		public override bool Loaded => true; | ||||||
| 		// 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, | ||||||
|  | @ -23,11 +22,22 @@ namespace MoonWorks.Video | ||||||
| 			(ushort) (4 * channels), | 			(ushort) (4 * channels), | ||||||
| 			(ushort) channels, | 			(ushort) channels, | ||||||
| 			sampleRate, | 			sampleRate, | ||||||
| 			bufferSize | 			bufferSize, | ||||||
|  | 			false // Theorafile is not thread safe, so let's update on the main thread | ||||||
| 		) { | 		) { | ||||||
| 			VideoHandle = videoHandle; | 			VideoHandle = videoHandle; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		public override unsafe void Load() | ||||||
|  | 		{ | ||||||
|  | 			// no-op | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public override unsafe void Unload() | ||||||
|  | 		{ | ||||||
|  | 			// no-op | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		protected override unsafe void FillBuffer( | 		protected override unsafe void FillBuffer( | ||||||
| 			void* buffer, | 			void* buffer, | ||||||
| 			int bufferLengthInBytes, | 			int bufferLengthInBytes, | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue