2023-08-03 19:54:02 +00:00
|
|
|
using System;
|
|
|
|
using System.IO;
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
|
|
|
namespace MoonWorks.Audio
|
|
|
|
{
|
|
|
|
/// <summary>
|
|
|
|
/// Streamable audio in Ogg format.
|
|
|
|
/// </summary>
|
|
|
|
public class AudioDataOgg : AudioDataStreamable
|
|
|
|
{
|
|
|
|
private IntPtr FileDataPtr = IntPtr.Zero;
|
|
|
|
private IntPtr VorbisHandle = IntPtr.Zero;
|
|
|
|
|
|
|
|
private string FilePath;
|
|
|
|
|
|
|
|
public override bool Loaded => VorbisHandle != IntPtr.Zero;
|
|
|
|
public override uint DecodeBufferSize => 32768;
|
|
|
|
|
|
|
|
public AudioDataOgg(AudioDevice device, string filePath) : base(device)
|
|
|
|
{
|
|
|
|
FilePath = filePath;
|
|
|
|
|
|
|
|
var handle = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero);
|
|
|
|
|
|
|
|
if (error != 0)
|
|
|
|
{
|
2023-09-20 00:14:48 +00:00
|
|
|
throw new InvalidOperationException("Error loading file!");
|
2023-08-03 19:54:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var info = FAudio.stb_vorbis_get_info(handle);
|
|
|
|
|
|
|
|
Format = new Format
|
|
|
|
{
|
|
|
|
Tag = FormatTag.IEEE_FLOAT,
|
|
|
|
BitsPerSample = 32,
|
|
|
|
Channels = (ushort) info.channels,
|
|
|
|
SampleRate = info.sample_rate
|
|
|
|
};
|
|
|
|
|
|
|
|
FAudio.stb_vorbis_close(handle);
|
|
|
|
}
|
|
|
|
|
|
|
|
public override unsafe void Decode(void* buffer, int bufferLengthInBytes, out int filledLengthInBytes, out bool reachedEnd)
|
|
|
|
{
|
|
|
|
var lengthInFloats = bufferLengthInBytes / sizeof(float);
|
|
|
|
|
|
|
|
/* NOTE: this function returns samples per channel, not total samples */
|
|
|
|
var samples = FAudio.stb_vorbis_get_samples_float_interleaved(
|
|
|
|
VorbisHandle,
|
|
|
|
Format.Channels,
|
|
|
|
(IntPtr) buffer,
|
|
|
|
lengthInFloats
|
|
|
|
);
|
|
|
|
|
|
|
|
var sampleCount = samples * Format.Channels;
|
|
|
|
reachedEnd = sampleCount < lengthInFloats;
|
|
|
|
filledLengthInBytes = sampleCount * sizeof(float);
|
|
|
|
}
|
|
|
|
|
2023-09-19 20:19:41 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Prepares the Ogg data for streaming.
|
|
|
|
/// </summary>
|
2023-08-03 19:54:02 +00:00
|
|
|
public override unsafe void Load()
|
|
|
|
{
|
|
|
|
if (!Loaded)
|
|
|
|
{
|
|
|
|
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);
|
2023-09-20 00:14:48 +00:00
|
|
|
throw new InvalidOperationException("Error opening OGG file!");
|
2023-08-03 19:54:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public override void Seek(uint sampleFrame)
|
|
|
|
{
|
|
|
|
FAudio.stb_vorbis_seek(VorbisHandle, sampleFrame);
|
|
|
|
}
|
|
|
|
|
2023-09-19 20:19:41 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Unloads the Ogg data, freeing resources.
|
|
|
|
/// </summary>
|
2023-12-09 00:33:52 +00:00
|
|
|
public override unsafe void Unload()
|
2023-08-03 19:54:02 +00:00
|
|
|
{
|
2023-12-09 00:33:52 +00:00
|
|
|
if (Loaded)
|
|
|
|
{
|
|
|
|
FAudio.stb_vorbis_close(VorbisHandle);
|
|
|
|
NativeMemory.Free((void*) FileDataPtr);
|
|
|
|
|
|
|
|
VorbisHandle = IntPtr.Zero;
|
|
|
|
FileDataPtr = IntPtr.Zero;
|
|
|
|
}
|
2023-08-03 19:54:02 +00:00
|
|
|
}
|
|
|
|
|
2023-09-19 20:19:41 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Loads an entire ogg file into an AudioBuffer. Useful for static audio.
|
|
|
|
/// </summary>
|
2023-11-21 02:56:22 +00:00
|
|
|
public static unsafe AudioBuffer CreateBuffer(AudioDevice device, string filePath)
|
2023-08-03 19:54:02 +00:00
|
|
|
{
|
|
|
|
var filePointer = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero);
|
|
|
|
|
|
|
|
if (error != 0)
|
|
|
|
{
|
2023-09-20 00:14:48 +00:00
|
|
|
throw new InvalidOperationException("Error loading file!");
|
2023-08-03 19:54:02 +00:00
|
|
|
}
|
|
|
|
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<float>();
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|