From cee218a2dcdf8824c68778d0e0662a08e041e7fe Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Tue, 2 May 2023 16:23:52 -0700 Subject: [PATCH] implement StreamingSoundQoa --- lib/FAudio | 2 +- src/Audio/StreamingSoundQoa.cs | 103 +++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 src/Audio/StreamingSoundQoa.cs diff --git a/lib/FAudio b/lib/FAudio index c42b681..4f696ab 160000 --- a/lib/FAudio +++ b/lib/FAudio @@ -1 +1 @@ -Subproject commit c42b6814c2550bd757b25b9387c0881eb4860af7 +Subproject commit 4f696ab0735927872b8227d3f335040c53876e3a diff --git a/src/Audio/StreamingSoundQoa.cs b/src/Audio/StreamingSoundQoa.cs new file mode 100644 index 0000000..7550592 --- /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; + + protected override int BUFFER_SIZE { get; } + public override bool AutoUpdate => true; + + uint Channels; + uint FrameSize; + 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((char*) fileDataPtr, fileDataSpan.Length); + 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 frameSize, out var channels, out var sampleRate, out var totalSamplesPerChannel); + + return new StreamingSoundQoa( + device, + (IntPtr) fileDataPtr, + qoaHandle, + channels, + sampleRate, + frameSize, + totalSamplesPerChannel + ); + } + + internal unsafe StreamingSoundQoa( + AudioDevice device, + IntPtr fileDataPtr, // MUST BE A NATIVE MEMORY HANDLE!! + IntPtr qoaHandle, + uint channels, + uint samplesPerSecond, + uint frameSize, + uint totalSamplesPerChannel + ) : base( + device, + 1, + 16, + (ushort) (2 * channels), + (ushort) channels, + samplesPerSecond + ) { + FileDataPtr = fileDataPtr; + QoaHandle = qoaHandle; + Channels = channels; + FrameSize = frameSize; + 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); + } + } + } +}