From 0500d949306e665a572cdac39790b0302adc71c7 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Wed, 2 Aug 2023 17:47:44 -0700 Subject: [PATCH] more audio data and voice restructuring --- src/Audio/AudioBuffer.cs | 56 ++++ src/Audio/AudioDataOgg.cs | 43 ++- src/Audio/AudioDataQoa.cs | 40 ++- .../{AudioData.cs => AudioDataStreamable.cs} | 24 +- src/Audio/AudioDataWav.cs | 95 ++++++ src/Audio/PersistentVoice.cs | 25 ++ src/Audio/SoundSequence.cs | 11 +- src/Audio/SourceVoice.cs | 41 ++- src/Audio/StaticSound.cs | 272 ------------------ src/Audio/StaticVoice.cs | 54 ---- src/Audio/StreamingVoice.cs | 25 +- src/Audio/TransientVoice.cs | 28 ++ 12 files changed, 342 insertions(+), 372 deletions(-) create mode 100644 src/Audio/AudioBuffer.cs rename src/Audio/{AudioData.cs => AudioDataStreamable.cs} (80%) create mode 100644 src/Audio/AudioDataWav.cs create mode 100644 src/Audio/PersistentVoice.cs delete mode 100644 src/Audio/StaticSound.cs delete mode 100644 src/Audio/StaticVoice.cs create mode 100644 src/Audio/TransientVoice.cs diff --git a/src/Audio/AudioBuffer.cs b/src/Audio/AudioBuffer.cs new file mode 100644 index 0000000..306a078 --- /dev/null +++ b/src/Audio/AudioBuffer.cs @@ -0,0 +1,56 @@ +using System; +using System.Runtime.InteropServices; + +namespace MoonWorks.Audio +{ + public class AudioBuffer : AudioResource + { + IntPtr BufferDataPtr; + uint BufferDataLength; + private bool OwnsBufferData; + + public Format Format { get; } + + public AudioBuffer( + AudioDevice device, + Format format, + IntPtr bufferPtr, + uint bufferLengthInBytes, + bool ownsBufferData) : base(device) + { + Format = format; + BufferDataPtr = bufferPtr; + BufferDataLength = bufferLengthInBytes; + OwnsBufferData = ownsBufferData; + } + + public AudioBuffer CreateSubBuffer(int offset, uint length) + { + return new AudioBuffer(Device, Format, BufferDataPtr + offset, length, false); + } + + public FAudio.FAudioBuffer ToFAudioBuffer(bool loop = false) + { + return new FAudio.FAudioBuffer + { + Flags = FAudio.FAUDIO_END_OF_STREAM, + pContext = IntPtr.Zero, + pAudioData = BufferDataPtr, + AudioBytes = BufferDataLength, + PlayBegin = 0, + PlayLength = 0, + LoopBegin = 0, + LoopLength = 0, + LoopCount = loop ? FAudio.FAUDIO_LOOP_INFINITE : 0 + }; + } + + protected override unsafe void Destroy() + { + if (OwnsBufferData) + { + NativeMemory.Free((void*) BufferDataPtr); + } + } + } +} diff --git a/src/Audio/AudioDataOgg.cs b/src/Audio/AudioDataOgg.cs index dbb988d..d7f1a21 100644 --- a/src/Audio/AudioDataOgg.cs +++ b/src/Audio/AudioDataOgg.cs @@ -4,7 +4,7 @@ using System.Runtime.InteropServices; namespace MoonWorks.Audio { - public class AudioDataOgg : AudioData + public class AudioDataOgg : AudioDataStreamable { private IntPtr FileDataPtr = IntPtr.Zero; private IntPtr VorbisHandle = IntPtr.Zero; @@ -14,7 +14,7 @@ namespace MoonWorks.Audio public override bool Loaded => VorbisHandle != IntPtr.Zero; public override uint DecodeBufferSize => 32768; - public AudioDataOgg(string filePath) + public AudioDataOgg(AudioDevice device, string filePath) : base(device) { FilePath = filePath; @@ -92,5 +92,44 @@ namespace MoonWorks.Audio FileDataPtr = IntPtr.Zero; } } + + public unsafe static AudioBuffer CreateBuffer(AudioDevice device, string filePath) + { + var filePointer = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero); + + if (error != 0) + { + throw new AudioLoadException("Error loading file!"); + } + var info = FAudio.stb_vorbis_get_info(filePointer); + var lengthInFloats = + FAudio.stb_vorbis_stream_length_in_samples(filePointer) * info.channels; + var lengthInBytes = lengthInFloats * Marshal.SizeOf(); + var buffer = NativeMemory.Alloc((nuint) lengthInBytes); + + FAudio.stb_vorbis_get_samples_float_interleaved( + filePointer, + info.channels, + (nint) buffer, + (int) lengthInFloats + ); + + FAudio.stb_vorbis_close(filePointer); + + var format = new Format + { + Tag = FormatTag.IEEE_FLOAT, + BitsPerSample = 32, + Channels = (ushort) info.channels, + SampleRate = info.sample_rate + }; + + return new AudioBuffer( + device, + format, + (nint) buffer, + (uint) lengthInBytes, + true); + } } } diff --git a/src/Audio/AudioDataQoa.cs b/src/Audio/AudioDataQoa.cs index 5322095..02a5c43 100644 --- a/src/Audio/AudioDataQoa.cs +++ b/src/Audio/AudioDataQoa.cs @@ -4,7 +4,7 @@ using System.Runtime.InteropServices; namespace MoonWorks.Audio { - public class AudioDataQoa : AudioData + public class AudioDataQoa : AudioDataStreamable { private IntPtr QoaHandle = IntPtr.Zero; private IntPtr FileDataPtr = IntPtr.Zero; @@ -18,7 +18,7 @@ namespace MoonWorks.Audio private uint decodeBufferSize; public override uint DecodeBufferSize => decodeBufferSize; - public AudioDataQoa(string filePath) + public AudioDataQoa(AudioDevice device, string filePath) : base(device) { FilePath = filePath; @@ -102,6 +102,42 @@ namespace MoonWorks.Audio } } + public unsafe static AudioBuffer CreateBuffer(AudioDevice device, string filePath) + { + using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read); + var fileDataPtr = NativeMemory.Alloc((nuint) fileStream.Length); + var fileDataSpan = new Span(fileDataPtr, (int) fileStream.Length); + fileStream.ReadExactly(fileDataSpan); + fileStream.Close(); + + var qoaHandle = FAudio.qoa_open_from_memory((char*) fileDataPtr, (uint) fileDataSpan.Length, 0); + if (qoaHandle == 0) + { + NativeMemory.Free(fileDataPtr); + Logger.LogError("Error opening QOA file!"); + throw new AudioLoadException("Error opening QOA file!"); + } + + FAudio.qoa_attributes(qoaHandle, out var channels, out var samplerate, out var samples_per_channel_per_frame, out var total_samples_per_channel); + + var bufferLengthInBytes = total_samples_per_channel * channels * sizeof(short); + var buffer = NativeMemory.Alloc(bufferLengthInBytes); + FAudio.qoa_decode_entire(qoaHandle, (short*) buffer); + + FAudio.qoa_close(qoaHandle); + NativeMemory.Free(fileDataPtr); + + var format = new Format + { + Tag = FormatTag.PCM, + BitsPerSample = 16, + Channels = (ushort) channels, + SampleRate = samplerate + }; + + return new AudioBuffer(device, format, (nint) buffer, bufferLengthInBytes, true); + } + private static unsafe UInt64 ReverseEndianness(UInt64 value) { byte* bytes = (byte*) &value; diff --git a/src/Audio/AudioData.cs b/src/Audio/AudioDataStreamable.cs similarity index 80% rename from src/Audio/AudioData.cs rename to src/Audio/AudioDataStreamable.cs index 9998179..aab413c 100644 --- a/src/Audio/AudioData.cs +++ b/src/Audio/AudioDataStreamable.cs @@ -1,19 +1,25 @@ -using System; - namespace MoonWorks.Audio { - public abstract class AudioData + public abstract class AudioDataStreamable : AudioResource { public Format Format { get; protected set; } + public abstract bool Loaded { get; } public abstract uint DecodeBufferSize { get; } - public abstract bool Loaded { get; } + protected AudioDataStreamable(AudioDevice device) : base(device) + { + } /// - /// Loads the raw audio data into memory. + /// Loads the raw audio data into memory to prepare it for stream decoding. /// public abstract void Load(); + /// + /// Unloads the raw audio data from memory. + /// + public abstract void Unload(); + /// /// Seeks to the given sample frame. /// @@ -28,9 +34,9 @@ namespace MoonWorks.Audio /// Whether the end of the data was reached on this decode. public abstract unsafe void Decode(void* buffer, int bufferLengthInBytes, out int filledLengthInBytes, out bool reachedEnd); - /// - /// Unloads the raw audio data from memory. - /// - public abstract void Unload(); + protected override void Destroy() + { + Unload(); + } } } diff --git a/src/Audio/AudioDataWav.cs b/src/Audio/AudioDataWav.cs new file mode 100644 index 0000000..5e415ca --- /dev/null +++ b/src/Audio/AudioDataWav.cs @@ -0,0 +1,95 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace MoonWorks.Audio +{ + public static class AudioDataWav + { + // mostly borrowed from https://github.com/FNA-XNA/FNA/blob/b71b4a35ae59970ff0070dea6f8620856d8d4fec/src/Audio/SoundEffect.cs#L385 + public unsafe static AudioBuffer CreateBuffer(AudioDevice device, string filePath) + { + // WaveFormatEx data + ushort wFormatTag; + ushort nChannels; + uint nSamplesPerSec; + uint nAvgBytesPerSec; + ushort nBlockAlign; + ushort wBitsPerSample; + + using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read); + using var reader = new BinaryReader(stream); + + // RIFF Signature + string signature = new string(reader.ReadChars(4)); + if (signature != "RIFF") + { + throw new NotSupportedException("Specified stream is not a wave file."); + } + + reader.ReadUInt32(); // Riff Chunk Size + + string wformat = new string(reader.ReadChars(4)); + if (wformat != "WAVE") + { + throw new NotSupportedException("Specified stream is not a wave file."); + } + + // WAVE Header + string format_signature = new string(reader.ReadChars(4)); + while (format_signature != "fmt ") + { + reader.ReadBytes(reader.ReadInt32()); + format_signature = new string(reader.ReadChars(4)); + } + + int format_chunk_size = reader.ReadInt32(); + + wFormatTag = reader.ReadUInt16(); + nChannels = reader.ReadUInt16(); + nSamplesPerSec = reader.ReadUInt32(); + nAvgBytesPerSec = reader.ReadUInt32(); + nBlockAlign = reader.ReadUInt16(); + wBitsPerSample = reader.ReadUInt16(); + + // Reads residual bytes + if (format_chunk_size > 16) + { + reader.ReadBytes(format_chunk_size - 16); + } + + // data Signature + string data_signature = new string(reader.ReadChars(4)); + while (data_signature.ToLowerInvariant() != "data") + { + reader.ReadBytes(reader.ReadInt32()); + data_signature = new string(reader.ReadChars(4)); + } + if (data_signature != "data") + { + throw new NotSupportedException("Specified wave file is not supported."); + } + + int waveDataLength = reader.ReadInt32(); + var waveDataBuffer = NativeMemory.Alloc((nuint) waveDataLength); + var waveDataSpan = new Span(waveDataBuffer, waveDataLength); + stream.ReadExactly(waveDataSpan); + + var format = new Format + { + Tag = (FormatTag) wFormatTag, + BitsPerSample = wBitsPerSample, + Channels = nChannels, + SampleRate = nSamplesPerSec + }; + + return new AudioBuffer( + device, + format, + (nint) waveDataBuffer, + (uint) waveDataLength, + true + ); + } + } +} diff --git a/src/Audio/PersistentVoice.cs b/src/Audio/PersistentVoice.cs new file mode 100644 index 0000000..7b54395 --- /dev/null +++ b/src/Audio/PersistentVoice.cs @@ -0,0 +1,25 @@ +namespace MoonWorks.Audio +{ + public class PersistentVoice : SourceVoice, IPoolable + { + public PersistentVoice(AudioDevice device, Format format) : base(device, format) + { + } + + public static PersistentVoice Create(AudioDevice device, Format format) + { + return new PersistentVoice(device, format); + } + + /// + /// Adds an AudioBuffer to the voice queue. + /// The voice processes and plays back the buffers in its queue in the order that they were submitted. + /// + /// The buffer to submit to the voice. + /// Whether the voice should loop this buffer. + public void Submit(AudioBuffer buffer, bool loop = false) + { + Submit(buffer.ToFAudioBuffer(loop)); + } + } +} diff --git a/src/Audio/SoundSequence.cs b/src/Audio/SoundSequence.cs index dece5df..d88177a 100644 --- a/src/Audio/SoundSequence.cs +++ b/src/Audio/SoundSequence.cs @@ -12,7 +12,7 @@ namespace MoonWorks.Audio } - public SoundSequence(AudioDevice device, StaticSound templateSound) : base(device, templateSound.Format) + public SoundSequence(AudioDevice device, AudioBuffer templateSound) : base(device, templateSound.Format) { } @@ -21,7 +21,6 @@ namespace MoonWorks.Audio { lock (StateLock) { - if (IsDisposed) { return; } if (State != SoundState.Playing) { return; } if (NeedSoundThreshold > 0) @@ -37,18 +36,18 @@ namespace MoonWorks.Audio } } - public void EnqueueSound(StaticSound sound) + public void EnqueueSound(AudioBuffer buffer) { #if DEBUG - if (!(sound.Format == Format)) + if (!(buffer.Format == Format)) { - Logger.LogWarn("Playlist audio format mismatch!"); + Logger.LogWarn("Sound sequence audio format mismatch!"); } #endif lock (StateLock) { - Submit(sound.Buffer); + Submit(buffer.ToFAudioBuffer()); } } } diff --git a/src/Audio/SourceVoice.cs b/src/Audio/SourceVoice.cs index 69fe34c..7ef90e6 100644 --- a/src/Audio/SourceVoice.cs +++ b/src/Audio/SourceVoice.cs @@ -5,13 +5,15 @@ namespace MoonWorks.Audio /// /// Emits audio from submitted audio buffers. /// - public class SourceVoice : Voice + public abstract class SourceVoice : Voice { private Format format; public Format Format => format; - protected object StateLock = new object(); - + /// + /// The number of buffers queued in the voice. + /// This includes the currently playing voice! + /// public uint BuffersQueued { get @@ -45,6 +47,8 @@ namespace MoonWorks.Audio } } + protected object StateLock = new object(); + public SourceVoice( AudioDevice device, Format format @@ -124,20 +128,13 @@ namespace MoonWorks.Audio } /// - /// Adds an FAudio buffer to the voice queue. + /// Adds an AudioBuffer to the voice queue. /// The voice processes and plays back the buffers in its queue in the order that they were submitted. /// /// The buffer to submit to the voice. - public void Submit(FAudio.FAudioBuffer buffer) + public void Submit(AudioBuffer buffer) { - lock (StateLock) - { - FAudio.FAudioSourceVoice_SubmitSourceBuffer( - Handle, - ref buffer, - IntPtr.Zero - ); - } + Submit(buffer.ToFAudioBuffer()); } /// @@ -152,9 +149,27 @@ namespace MoonWorks.Audio /// /// Called automatically by AudioDevice in the audio thread. + /// Don't call this yourself! You might regret it! /// public virtual void Update() { } + /// + /// Adds an FAudio buffer to the voice queue. + /// The voice processes and plays back the buffers in its queue in the order that they were submitted. + /// + /// The buffer to submit to the voice. + protected void Submit(FAudio.FAudioBuffer buffer) + { + lock (StateLock) + { + FAudio.FAudioSourceVoice_SubmitSourceBuffer( + Handle, + ref buffer, + IntPtr.Zero + ); + } + } + protected override unsafe void Destroy() { Stop(); diff --git a/src/Audio/StaticSound.cs b/src/Audio/StaticSound.cs deleted file mode 100644 index 9c29142..0000000 --- a/src/Audio/StaticSound.cs +++ /dev/null @@ -1,272 +0,0 @@ -using System; -using System.IO; -using System.Runtime.InteropServices; - -namespace MoonWorks.Audio -{ - public class StaticSound : AudioResource - { - internal FAudio.FAudioBuffer Buffer; - - private Format format; - public Format Format => format; - - private bool OwnsBuffer; - - public static unsafe StaticSound LoadOgg(AudioDevice device, string filePath) - { - var filePointer = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero); - - if (error != 0) - { - throw new AudioLoadException("Error loading file!"); - } - var info = FAudio.stb_vorbis_get_info(filePointer); - var lengthInFloats = - FAudio.stb_vorbis_stream_length_in_samples(filePointer) * info.channels; - var lengthInBytes = lengthInFloats * Marshal.SizeOf(); - var buffer = NativeMemory.Alloc((nuint) lengthInBytes); - - FAudio.stb_vorbis_get_samples_float_interleaved( - filePointer, - info.channels, - (nint) buffer, - (int) lengthInFloats - ); - - FAudio.stb_vorbis_close(filePointer); - - var format = new Format - { - Tag = FormatTag.IEEE_FLOAT, - BitsPerSample = 32, - Channels = (ushort) info.channels, - SampleRate = info.sample_rate - }; - - return new StaticSound( - device, - format, - (nint) buffer, - (uint) lengthInBytes, - true); - } - - // mostly borrowed from https://github.com/FNA-XNA/FNA/blob/b71b4a35ae59970ff0070dea6f8620856d8d4fec/src/Audio/SoundEffect.cs#L385 - public static unsafe StaticSound LoadWav(AudioDevice device, string filePath) - { - // WaveFormatEx data - ushort wFormatTag; - ushort nChannels; - uint nSamplesPerSec; - uint nAvgBytesPerSec; - ushort nBlockAlign; - ushort wBitsPerSample; - int samplerLoopStart = 0; - int samplerLoopEnd = 0; - - using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read); - using var reader = new BinaryReader(stream); - - // RIFF Signature - string signature = new string(reader.ReadChars(4)); - if (signature != "RIFF") - { - throw new NotSupportedException("Specified stream is not a wave file."); - } - - reader.ReadUInt32(); // Riff Chunk Size - - string wformat = new string(reader.ReadChars(4)); - if (wformat != "WAVE") - { - throw new NotSupportedException("Specified stream is not a wave file."); - } - - // WAVE Header - string format_signature = new string(reader.ReadChars(4)); - while (format_signature != "fmt ") - { - reader.ReadBytes(reader.ReadInt32()); - format_signature = new string(reader.ReadChars(4)); - } - - int format_chunk_size = reader.ReadInt32(); - - wFormatTag = reader.ReadUInt16(); - nChannels = reader.ReadUInt16(); - nSamplesPerSec = reader.ReadUInt32(); - nAvgBytesPerSec = reader.ReadUInt32(); - nBlockAlign = reader.ReadUInt16(); - wBitsPerSample = reader.ReadUInt16(); - - // Reads residual bytes - if (format_chunk_size > 16) - { - reader.ReadBytes(format_chunk_size - 16); - } - - // data Signature - string data_signature = new string(reader.ReadChars(4)); - while (data_signature.ToLowerInvariant() != "data") - { - reader.ReadBytes(reader.ReadInt32()); - data_signature = new string(reader.ReadChars(4)); - } - if (data_signature != "data") - { - throw new NotSupportedException("Specified wave file is not supported."); - } - - int waveDataLength = reader.ReadInt32(); - var waveDataBuffer = NativeMemory.Alloc((nuint) waveDataLength); - var waveDataSpan = new Span(waveDataBuffer, waveDataLength); - stream.ReadExactly(waveDataSpan); - - // Scan for other chunks - while (reader.PeekChar() != -1) - { - char[] chunkIDChars = reader.ReadChars(4); - if (chunkIDChars.Length < 4) - { - break; // EOL! - } - byte[] chunkSizeBytes = reader.ReadBytes(4); - if (chunkSizeBytes.Length < 4) - { - break; // EOL! - } - string chunk_signature = new string(chunkIDChars); - int chunkDataSize = BitConverter.ToInt32(chunkSizeBytes, 0); - if (chunk_signature == "smpl") // "smpl", Sampler Chunk Found - { - reader.ReadUInt32(); // Manufacturer - reader.ReadUInt32(); // Product - reader.ReadUInt32(); // Sample Period - reader.ReadUInt32(); // MIDI Unity Note - reader.ReadUInt32(); // MIDI Pitch Fraction - reader.ReadUInt32(); // SMPTE Format - reader.ReadUInt32(); // SMPTE Offset - uint numSampleLoops = reader.ReadUInt32(); - int samplerData = reader.ReadInt32(); - - for (int i = 0; i < numSampleLoops; i += 1) - { - reader.ReadUInt32(); // Cue Point ID - reader.ReadUInt32(); // Type - int start = reader.ReadInt32(); - int end = reader.ReadInt32(); - reader.ReadUInt32(); // Fraction - reader.ReadUInt32(); // Play Count - - if (i == 0) // Grab loopStart and loopEnd from first sample loop - { - samplerLoopStart = start; - samplerLoopEnd = end; - } - } - - if (samplerData != 0) // Read Sampler Data if it exists - { - reader.ReadBytes(samplerData); - } - } - else // Read unwanted chunk data and try again - { - reader.ReadBytes(chunkDataSize); - } - } - // End scan - - var format = new Format - { - Tag = (FormatTag) wFormatTag, - BitsPerSample = wBitsPerSample, - Channels = nChannels, - SampleRate = nSamplesPerSec - }; - - var sound = new StaticSound( - device, - format, - (nint) waveDataBuffer, - (uint) waveDataLength, - true - ); - - return sound; - } - - public static unsafe StaticSound FromQOA(AudioDevice device, string path) - { - var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read); - var fileDataPtr = NativeMemory.Alloc((nuint) fileStream.Length); - var fileDataSpan = new Span(fileDataPtr, (int) fileStream.Length); - fileStream.ReadExactly(fileDataSpan); - fileStream.Close(); - - var qoaHandle = FAudio.qoa_open_from_memory((char*) fileDataPtr, (uint) fileDataSpan.Length, 0); - if (qoaHandle == 0) - { - NativeMemory.Free(fileDataPtr); - Logger.LogError("Error opening QOA file!"); - throw new AudioLoadException("Error opening QOA file!"); - } - - FAudio.qoa_attributes(qoaHandle, out var channels, out var samplerate, out var samples_per_channel_per_frame, out var total_samples_per_channel); - - var bufferLengthInBytes = total_samples_per_channel * channels * sizeof(short); - var buffer = NativeMemory.Alloc(bufferLengthInBytes); - FAudio.qoa_decode_entire(qoaHandle, (short*) buffer); - - FAudio.qoa_close(qoaHandle); - NativeMemory.Free(fileDataPtr); - - var format = new Format - { - Tag = FormatTag.PCM, - BitsPerSample = 16, - Channels = (ushort) channels, - SampleRate = samplerate - }; - - return new StaticSound( - device, - format, - (nint) buffer, - bufferLengthInBytes, - true - ); - } - - public StaticSound( - AudioDevice device, - Format format, - IntPtr bufferPtr, - uint bufferLengthInBytes, - bool ownsBuffer) : base(device) - { - this.format = format; - - Buffer = new FAudio.FAudioBuffer - { - Flags = FAudio.FAUDIO_END_OF_STREAM, - pContext = IntPtr.Zero, - pAudioData = bufferPtr, - AudioBytes = bufferLengthInBytes, - PlayBegin = 0, - PlayLength = 0 - }; - - OwnsBuffer = ownsBuffer; - } - - protected override unsafe void Destroy() - { - if (OwnsBuffer) - { - NativeMemory.Free((void*) Buffer.pAudioData); - } - } - } -} diff --git a/src/Audio/StaticVoice.cs b/src/Audio/StaticVoice.cs deleted file mode 100644 index 57600df..0000000 --- a/src/Audio/StaticVoice.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace MoonWorks.Audio -{ - public class StaticVoice : SourceVoice, IPoolable - { - /// - /// Indicates if the voice should return to the voice pool when the voice is idle. - /// If you set this and then hold on to the voice reference there will be problems! - /// - public bool DeactivateWhenIdle { get; set; } - - public static StaticVoice Create(AudioDevice device, Format format) - { - return new StaticVoice(device, format); - } - - public StaticVoice(AudioDevice device, Format format) : base(device, format) - { - } - - public override void Update() - { - lock (StateLock) - { - if (DeactivateWhenIdle) - { - if (BuffersQueued == 0) - { - Return(); - } - } - } - } - - /// - /// Adds a static sound to the voice queue. - /// The voice processes and plays back the buffers in its queue in the order that they were submitted. - /// - /// The sound to submit to the voice. - /// Designates that the voice will loop the submitted buffer. - public void Submit(StaticSound sound, bool loop = false) - { - if (loop) - { - sound.Buffer.LoopCount = FAudio.FAUDIO_LOOP_INFINITE; - } - else - { - sound.Buffer.LoopCount = 0; - } - - Submit(sound.Buffer); - } - } -} diff --git a/src/Audio/StreamingVoice.cs b/src/Audio/StreamingVoice.cs index 3acfbe1..03dcd0a 100644 --- a/src/Audio/StreamingVoice.cs +++ b/src/Audio/StreamingVoice.cs @@ -12,19 +12,19 @@ namespace MoonWorks.Audio public bool Loop { get; set; } - public AudioData AudioData { get; protected set; } - - public static StreamingVoice Create(AudioDevice device, Format format) - { - return new StreamingVoice(device, format); - } + public AudioDataStreamable AudioData { get; protected set; } public unsafe StreamingVoice(AudioDevice device, Format format) : base(device, format) { buffers = new IntPtr[BUFFER_COUNT]; } - public void Load(AudioData data) + public static StreamingVoice Create(AudioDevice device, Format format) + { + return new StreamingVoice(device, format); + } + + public void Load(AudioDataStreamable data) { lock (StateLock) { @@ -58,15 +58,12 @@ namespace MoonWorks.Audio { lock (StateLock) { - if (!IsDisposed) + if (AudioData == null || State != SoundState.Playing) { - if (AudioData == null || State != SoundState.Playing) - { - return; - } - - QueueBuffers(); + return; } + + QueueBuffers(); } } diff --git a/src/Audio/TransientVoice.cs b/src/Audio/TransientVoice.cs new file mode 100644 index 0000000..1dbc4c2 --- /dev/null +++ b/src/Audio/TransientVoice.cs @@ -0,0 +1,28 @@ +namespace MoonWorks.Audio +{ + /// + /// These voices are intended for playing one-off sound effects that don't have a long term reference. + /// + public class TransientVoice : SourceVoice, IPoolable + { + static TransientVoice IPoolable.Create(AudioDevice device, Format format) + { + return new TransientVoice(device, format); + } + + public TransientVoice(AudioDevice device, Format format) : base(device, format) + { + } + + public override void Update() + { + lock (StateLock) + { + if (BuffersQueued == 0) + { + Return(); + } + } + } + } +}