From cee218a2dcdf8824c68778d0e0662a08e041e7fe Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Tue, 2 May 2023 16:23:52 -0700 Subject: [PATCH 1/8] 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); + } + } + } +} -- 2.25.1 From a869a0e958fd307a2883d111489ea7c49793baf6 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Thu, 4 May 2023 00:56:45 -0700 Subject: [PATCH 2/8] more QOA testing --- lib/FAudio | 2 +- src/Audio/StreamingSoundQoa.cs | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/FAudio b/lib/FAudio index 4f696ab..a016259 160000 --- a/lib/FAudio +++ b/lib/FAudio @@ -1 +1 @@ -Subproject commit 4f696ab0735927872b8227d3f335040c53876e3a +Subproject commit a016259994797f96db2bda75c90b359258e99d09 diff --git a/src/Audio/StreamingSoundQoa.cs b/src/Audio/StreamingSoundQoa.cs index 7550592..266c51f 100644 --- a/src/Audio/StreamingSoundQoa.cs +++ b/src/Audio/StreamingSoundQoa.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Runtime.InteropServices; @@ -13,7 +13,7 @@ namespace MoonWorks.Audio public override bool AutoUpdate => true; uint Channels; - uint FrameSize; + uint SamplesPerChannelPerFrame; uint TotalSamplesPerChannel; public unsafe static StreamingSoundQoa Load(AudioDevice device, string filePath) @@ -24,7 +24,7 @@ namespace MoonWorks.Audio fileStream.ReadExactly(fileDataSpan); fileStream.Close(); - var qoaHandle = FAudio.qoa_open((char*) fileDataPtr, fileDataSpan.Length); + var qoaHandle = FAudio.qoa_open((char*) fileDataPtr, (uint) fileDataSpan.Length); if (qoaHandle == 0) { NativeMemory.Free(fileDataPtr); @@ -32,7 +32,8 @@ namespace MoonWorks.Audio throw new AudioLoadException("Error opening QOA file!"); } - FAudio.qoa_attributes(qoaHandle, out var frameSize, out var channels, out var sampleRate, out var totalSamplesPerChannel); + // FIXME: return samples per frame, not the byte size of the frame + FAudio.qoa_attributes(qoaHandle, out var channels, out var sampleRate, out var samplesPerChannelPerFrame, out var totalSamplesPerChannel); return new StreamingSoundQoa( device, @@ -40,7 +41,7 @@ namespace MoonWorks.Audio qoaHandle, channels, sampleRate, - frameSize, + samplesPerChannelPerFrame, totalSamplesPerChannel ); } @@ -51,7 +52,7 @@ namespace MoonWorks.Audio IntPtr qoaHandle, uint channels, uint samplesPerSecond, - uint frameSize, + uint samplesPerChannelPerFrame, uint totalSamplesPerChannel ) : base( device, @@ -64,8 +65,10 @@ namespace MoonWorks.Audio FileDataPtr = fileDataPtr; QoaHandle = qoaHandle; Channels = channels; - FrameSize = frameSize; + SamplesPerChannelPerFrame = samplesPerChannelPerFrame; TotalSamplesPerChannel = totalSamplesPerChannel; + + BUFFER_SIZE = (int) (samplesPerChannelPerFrame * Channels * sizeof(short)); } public override void Seek(uint sampleFrame) @@ -81,7 +84,7 @@ namespace MoonWorks.Audio ) { var lengthInShorts = bufferLengthInBytes / sizeof(short); - // NOTE: this function returns samples per channel! */ + // NOTE: this function returns samples per channel! var samples = FAudio.qoa_decode_next_frame(QoaHandle, (short*) buffer); var sampleCount = samples * Channels; -- 2.25.1 From 6cd44c6f24ae740d2fbc7ba814567b73e74f7267 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Thu, 4 May 2023 11:06:38 -0700 Subject: [PATCH 3/8] fix streaming audio buffer size initialization --- src/Audio/StreamingSound.cs | 14 ++++++++------ src/Audio/StreamingSoundOgg.cs | 8 ++++---- src/Audio/StreamingSoundQoa.cs | 6 ++---- src/Audio/StreamingSoundSeekable.cs | 20 ++++++++++++++++++-- src/Video/StreamingSoundTheora.cs | 8 +++++--- 5 files changed, 37 insertions(+), 19 deletions(-) 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 index 266c51f..012ca16 100644 --- a/src/Audio/StreamingSoundQoa.cs +++ b/src/Audio/StreamingSoundQoa.cs @@ -9,7 +9,6 @@ namespace MoonWorks.Audio private IntPtr QoaHandle; private IntPtr FileDataPtr; - protected override int BUFFER_SIZE { get; } public override bool AutoUpdate => true; uint Channels; @@ -60,15 +59,14 @@ namespace MoonWorks.Audio 16, (ushort) (2 * channels), (ushort) channels, - samplesPerSecond + samplesPerSecond, + samplesPerChannelPerFrame * channels * sizeof(short) ) { FileDataPtr = fileDataPtr; QoaHandle = qoaHandle; Channels = channels; SamplesPerChannelPerFrame = samplesPerChannelPerFrame; TotalSamplesPerChannel = totalSamplesPerChannel; - - BUFFER_SIZE = (int) (samplesPerChannelPerFrame * Channels * sizeof(short)); } public override void Seek(uint sampleFrame) diff --git a/src/Audio/StreamingSoundSeekable.cs b/src/Audio/StreamingSoundSeekable.cs index cf8beb4..6ee7915 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; } -- 2.25.1 From 8829b7bcc3ad9c27af6502c9753d34502a35635b Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Thu, 4 May 2023 11:51:51 -0700 Subject: [PATCH 4/8] add StaticSound.FromQOA --- src/Audio/StaticSound.cs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/Audio/StaticSound.cs b/src/Audio/StaticSound.cs index da7a44d..d2566a3 100644 --- a/src/Audio/StaticSound.cs +++ b/src/Audio/StaticSound.cs @@ -197,6 +197,39 @@ 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((char*) fileDataPtr, (uint) 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 channels, out var samplerate, out var samples_per_channel_per_frame, out var total_samples_per_channel); + + var buffer = FAudio.qoa_load(qoaHandle); + + return new StaticSound( + device, + 1, + 16, + (ushort) (channels * 2), + (ushort) channels, + samplerate, + (nint) buffer, + total_samples_per_channel * channels * sizeof(short), + true + ); + } + public StaticSound( AudioDevice device, ushort formatTag, -- 2.25.1 From c284037c62614489f2511c1e2356be2ab39f11e9 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Thu, 4 May 2023 12:11:24 -0700 Subject: [PATCH 5/8] QOA data open API tweak --- lib/FAudio | 2 +- src/Audio/StaticSound.cs | 11 ++++++++--- src/Audio/StreamingSoundQoa.cs | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/FAudio b/lib/FAudio index a016259..f065a55 160000 --- a/lib/FAudio +++ b/lib/FAudio @@ -1 +1 @@ -Subproject commit a016259994797f96db2bda75c90b359258e99d09 +Subproject commit f065a5519fa2a1020eb971c0da66c877f2a97f18 diff --git a/src/Audio/StaticSound.cs b/src/Audio/StaticSound.cs index d2566a3..9cae8eb 100644 --- a/src/Audio/StaticSound.cs +++ b/src/Audio/StaticSound.cs @@ -205,7 +205,7 @@ namespace MoonWorks.Audio fileStream.ReadExactly(fileDataSpan); fileStream.Close(); - var qoaHandle = FAudio.qoa_open((char*) fileDataPtr, (uint) fileDataSpan.Length); + var qoaHandle = FAudio.qoa_open_memory((char*) fileDataPtr, (uint) fileDataSpan.Length); if (qoaHandle == 0) { NativeMemory.Free(fileDataPtr); @@ -215,7 +215,12 @@ namespace MoonWorks.Audio FAudio.qoa_attributes(qoaHandle, out var channels, out var samplerate, out var samples_per_channel_per_frame, out var total_samples_per_channel); - var buffer = FAudio.qoa_load(qoaHandle); + 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, @@ -225,7 +230,7 @@ namespace MoonWorks.Audio (ushort) channels, samplerate, (nint) buffer, - total_samples_per_channel * channels * sizeof(short), + bufferLengthInBytes, true ); } diff --git a/src/Audio/StreamingSoundQoa.cs b/src/Audio/StreamingSoundQoa.cs index 012ca16..9322e0e 100644 --- a/src/Audio/StreamingSoundQoa.cs +++ b/src/Audio/StreamingSoundQoa.cs @@ -23,7 +23,7 @@ namespace MoonWorks.Audio fileStream.ReadExactly(fileDataSpan); fileStream.Close(); - var qoaHandle = FAudio.qoa_open((char*) fileDataPtr, (uint) fileDataSpan.Length); + var qoaHandle = FAudio.qoa_open_memory((char*) fileDataPtr, (uint) fileDataSpan.Length); if (qoaHandle == 0) { NativeMemory.Free(fileDataPtr); -- 2.25.1 From 56b33c6f9b366e7de1ee91551c783f246342364e Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Fri, 5 May 2023 15:19:35 -0700 Subject: [PATCH 6/8] FAudio qoa API update --- lib/FAudio | 2 +- src/Audio/StaticSound.cs | 2 +- src/Audio/StreamingSoundQoa.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/FAudio b/lib/FAudio index f065a55..aaf2568 160000 --- a/lib/FAudio +++ b/lib/FAudio @@ -1 +1 @@ -Subproject commit f065a5519fa2a1020eb971c0da66c877f2a97f18 +Subproject commit aaf2568c3e5b202c5cfbd74734386e69f204482c diff --git a/src/Audio/StaticSound.cs b/src/Audio/StaticSound.cs index 9cae8eb..11d98df 100644 --- a/src/Audio/StaticSound.cs +++ b/src/Audio/StaticSound.cs @@ -205,7 +205,7 @@ namespace MoonWorks.Audio fileStream.ReadExactly(fileDataSpan); fileStream.Close(); - var qoaHandle = FAudio.qoa_open_memory((char*) fileDataPtr, (uint) fileDataSpan.Length); + var qoaHandle = FAudio.qoa_open_from_memory((char*) fileDataPtr, (uint) fileDataSpan.Length, 0); if (qoaHandle == 0) { NativeMemory.Free(fileDataPtr); diff --git a/src/Audio/StreamingSoundQoa.cs b/src/Audio/StreamingSoundQoa.cs index 9322e0e..ab5683a 100644 --- a/src/Audio/StreamingSoundQoa.cs +++ b/src/Audio/StreamingSoundQoa.cs @@ -23,7 +23,7 @@ namespace MoonWorks.Audio fileStream.ReadExactly(fileDataSpan); fileStream.Close(); - var qoaHandle = FAudio.qoa_open_memory((char*) fileDataPtr, (uint) fileDataSpan.Length); + var qoaHandle = FAudio.qoa_open_from_memory((char*) fileDataPtr, (uint) fileDataSpan.Length, 0); if (qoaHandle == 0) { NativeMemory.Free(fileDataPtr); -- 2.25.1 From 1e0b1b093a045f9aab9ed3a4b90e01d2de4e33a7 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Fri, 5 May 2023 15:23:29 -0700 Subject: [PATCH 7/8] fix indentation --- src/Audio/StreamingSoundSeekable.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Audio/StreamingSoundSeekable.cs b/src/Audio/StreamingSoundSeekable.cs index 6ee7915..9f0f718 100644 --- a/src/Audio/StreamingSoundSeekable.cs +++ b/src/Audio/StreamingSoundSeekable.cs @@ -13,13 +13,13 @@ namespace MoonWorks.Audio uint samplesPerSecond, uint bufferSize ) : base( - device, - formatTag, - bitsPerSample, - blockAlign, - channels, - samplesPerSecond, - bufferSize + device, + formatTag, + bitsPerSample, + blockAlign, + channels, + samplesPerSecond, + bufferSize ) { } -- 2.25.1 From 7ee4c1908228ee4e0bb0b6179f9a535dbcdfe378 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Fri, 5 May 2023 15:24:05 -0700 Subject: [PATCH 8/8] remove FIXME note --- src/Audio/StreamingSoundQoa.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Audio/StreamingSoundQoa.cs b/src/Audio/StreamingSoundQoa.cs index ab5683a..178c989 100644 --- a/src/Audio/StreamingSoundQoa.cs +++ b/src/Audio/StreamingSoundQoa.cs @@ -31,7 +31,6 @@ namespace MoonWorks.Audio throw new AudioLoadException("Error opening QOA file!"); } - // FIXME: return samples per frame, not the byte size of the frame FAudio.qoa_attributes(qoaHandle, out var channels, out var sampleRate, out var samplesPerChannelPerFrame, out var totalSamplesPerChannel); return new StreamingSoundQoa( -- 2.25.1