diff --git a/lib/FAudio b/lib/FAudio index c42b681..aaf2568 160000 --- a/lib/FAudio +++ b/lib/FAudio @@ -1 +1 @@ -Subproject commit c42b6814c2550bd757b25b9387c0881eb4860af7 +Subproject commit aaf2568c3e5b202c5cfbd74734386e69f204482c diff --git a/src/Audio/StaticSound.cs b/src/Audio/StaticSound.cs index da7a44d..11d98df 100644 --- a/src/Audio/StaticSound.cs +++ b/src/Audio/StaticSound.cs @@ -197,6 +197,44 @@ namespace MoonWorks.Audio 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); + + return new StaticSound( + device, + 1, + 16, + (ushort) (channels * 2), + (ushort) channels, + samplerate, + (nint) buffer, + bufferLengthInBytes, + true + ); + } + public StaticSound( AudioDevice device, ushort formatTag, diff --git a/src/Audio/StreamingSound.cs b/src/Audio/StreamingSound.cs index 2f3630d..62afec9 100644 --- a/src/Audio/StreamingSound.cs +++ b/src/Audio/StreamingSound.cs @@ -10,9 +10,6 @@ namespace MoonWorks.Audio /// 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; } @@ -20,6 +17,7 @@ namespace MoonWorks.Audio protected bool ConsumingBuffers = false; private const int BUFFER_COUNT = 3; + private nuint BufferSize; private readonly IntPtr[] buffers; private int nextBufferIndex = 0; private uint queuedBufferCount = 0; @@ -32,13 +30,17 @@ namespace MoonWorks.Audio ushort bitsPerSample, ushort blockAlign, ushort channels, - uint samplesPerSecond + uint samplesPerSecond, + uint bufferSize ) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond) { + BufferSize = bufferSize; + System.Console.WriteLine(BufferSize); + buffers = new IntPtr[BUFFER_COUNT]; for (int i = 0; i < BUFFER_COUNT; i += 1) { - buffers[i] = (IntPtr) NativeMemory.Alloc((nuint) BUFFER_SIZE); + buffers[i] = (IntPtr) NativeMemory.Alloc(bufferSize); } } @@ -156,7 +158,7 @@ namespace MoonWorks.Audio FillBuffer( (void*) buffer, - BUFFER_SIZE, + (int) BufferSize, out int filledLengthInBytes, out bool reachedEnd ); diff --git a/src/Audio/StreamingSoundOgg.cs b/src/Audio/StreamingSoundOgg.cs index 027faea..ba04697 100644 --- a/src/Audio/StreamingSoundOgg.cs +++ b/src/Audio/StreamingSoundOgg.cs @@ -9,8 +9,6 @@ namespace MoonWorks.Audio private IntPtr VorbisHandle; private IntPtr FileDataPtr; 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) @@ -43,14 +41,16 @@ namespace MoonWorks.Audio AudioDevice device, IntPtr fileDataPtr, // MUST BE A NATIVE MEMORY HANDLE!! IntPtr vorbisHandle, - FAudio.stb_vorbis_info info + FAudio.stb_vorbis_info info, + uint bufferSize = 32768 ) : base( device, 3, /* float type */ 32, /* size of float */ (ushort) (4 * info.channels), (ushort) info.channels, - info.sample_rate + info.sample_rate, + bufferSize ) { FileDataPtr = fileDataPtr; VorbisHandle = vorbisHandle; diff --git a/src/Audio/StreamingSoundQoa.cs b/src/Audio/StreamingSoundQoa.cs new file mode 100644 index 0000000..178c989 --- /dev/null +++ b/src/Audio/StreamingSoundQoa.cs @@ -0,0 +1,103 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace MoonWorks.Audio +{ + public class StreamingSoundQoa : StreamingSoundSeekable + { + private IntPtr QoaHandle; + private IntPtr FileDataPtr; + + public override bool AutoUpdate => true; + + uint Channels; + uint SamplesPerChannelPerFrame; + uint TotalSamplesPerChannel; + + public unsafe static StreamingSoundQoa Load(AudioDevice device, string filePath) + { + 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 samplesPerChannelPerFrame, out var totalSamplesPerChannel); + + return new StreamingSoundQoa( + device, + (IntPtr) fileDataPtr, + qoaHandle, + channels, + sampleRate, + samplesPerChannelPerFrame, + totalSamplesPerChannel + ); + } + + internal unsafe StreamingSoundQoa( + AudioDevice device, + IntPtr fileDataPtr, // MUST BE A NATIVE MEMORY HANDLE!! + IntPtr qoaHandle, + uint channels, + uint samplesPerSecond, + uint samplesPerChannelPerFrame, + uint totalSamplesPerChannel + ) : base( + device, + 1, + 16, + (ushort) (2 * channels), + (ushort) channels, + samplesPerSecond, + samplesPerChannelPerFrame * channels * sizeof(short) + ) { + FileDataPtr = fileDataPtr; + QoaHandle = qoaHandle; + Channels = channels; + SamplesPerChannelPerFrame = samplesPerChannelPerFrame; + TotalSamplesPerChannel = totalSamplesPerChannel; + } + + public override void Seek(uint sampleFrame) + { + FAudio.qoa_seek_frame(QoaHandle, (int) sampleFrame); + } + + protected override unsafe void FillBuffer( + void* buffer, + int bufferLengthInBytes, + out int filledLengthInBytes, + out bool reachedEnd + ) { + var lengthInShorts = bufferLengthInBytes / sizeof(short); + + // NOTE: this function returns samples per channel! + var samples = FAudio.qoa_decode_next_frame(QoaHandle, (short*) buffer); + + var sampleCount = samples * Channels; + reachedEnd = sampleCount < lengthInShorts; + filledLengthInBytes = (int) (sampleCount * sizeof(short)); + } + + protected override unsafe void Destroy() + { + base.Destroy(); + + if (!IsDisposed) + { + FAudio.qoa_close(QoaHandle); + NativeMemory.Free((void*) FileDataPtr); + } + } + } +} diff --git a/src/Audio/StreamingSoundSeekable.cs b/src/Audio/StreamingSoundSeekable.cs index cf8beb4..9f0f718 100644 --- a/src/Audio/StreamingSoundSeekable.cs +++ b/src/Audio/StreamingSoundSeekable.cs @@ -4,8 +4,24 @@ namespace MoonWorks.Audio { public bool Loop { get; set; } - protected StreamingSoundSeekable(AudioDevice device, ushort formatTag, ushort bitsPerSample, ushort blockAlign, ushort channels, uint samplesPerSecond) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond) - { + protected StreamingSoundSeekable( + AudioDevice device, + ushort formatTag, + ushort bitsPerSample, + ushort blockAlign, + ushort channels, + uint samplesPerSecond, + uint bufferSize + ) : base( + device, + formatTag, + bitsPerSample, + blockAlign, + channels, + samplesPerSecond, + bufferSize + ) { + } public abstract void Seek(uint sampleFrame); diff --git a/src/Video/StreamingSoundTheora.cs b/src/Video/StreamingSoundTheora.cs index 2b111b5..a830c33 100644 --- a/src/Video/StreamingSoundTheora.cs +++ b/src/Video/StreamingSoundTheora.cs @@ -6,7 +6,7 @@ namespace MoonWorks.Video public unsafe class StreamingSoundTheora : StreamingSound { 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; @@ -14,14 +14,16 @@ namespace MoonWorks.Video AudioDevice device, IntPtr videoHandle, int channels, - uint sampleRate + uint sampleRate, + uint bufferSize = 8192 ) : base( device, 3, /* float type */ 32, /* size of float */ (ushort) (4 * channels), (ushort) channels, - sampleRate + sampleRate, + bufferSize ) { VideoHandle = videoHandle; }