MoonWorks/src/Audio/AudioDataOgg.cs

148 lines
3.8 KiB
C#
Raw Normal View History

Audio Restructuring (#50) This is a complete redesign of the MoonWorks Audio API. Voices are the new major concept. All Voices can be configured with volume, pitch, filters, panning and reverb. SourceVoices take in AudioBuffers and use them to play sound. They contain their own playback state. There are multiple kinds of SourceVoices: TransientVoice: Used for short sound effects where the client will not be keeping track of a reference over multiple frames. PersistentVoice: Used when the client needs to hold on to a Voice reference long-term. StreamingVoice: Used for playing back AudioDataStreamable objects. SoundSequence: Used to play back a series of AudioBuffers in sequence. They have a callback so that AudioBuffers can be added dynamically by the client. SourceVoices are intended to be pooled. You can obtain one from the AudioDevice pool by calling AudioDevice.Obtain<T> where T is the type of SourceVoice you wish to obtain. When you call Return on the voice it will be returned to the pool. TransientVoices are automatically returned to the pool when they have finished playing back their AudioBuffer. SourceVoices can send audio to SubmixVoices. This is a convenient way to manage categories of audio. For example the client could have a MusicSubmix that all music-related voices send to. Then the volume of all music can be changed at once without the client having to manage all the individual music voices. By default all voices send audio to AudioDevice.MasteringVoice. This is also a SubmixVoice that can be controlled like any other voice. AudioDataStreamable is used in conjunction with a StreamingVoice to play back streaming audio from an ogg or qoa file. AudioDataWav, AudioDataOgg, and AudioDataQoa all have a static CreateBuffer method that can be used to create an AudioBuffer from an audio file. Reviewed-on: https://gitea.moonside.games/MoonsideGames/MoonWorks/pulls/50
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!");
Audio Restructuring (#50) This is a complete redesign of the MoonWorks Audio API. Voices are the new major concept. All Voices can be configured with volume, pitch, filters, panning and reverb. SourceVoices take in AudioBuffers and use them to play sound. They contain their own playback state. There are multiple kinds of SourceVoices: TransientVoice: Used for short sound effects where the client will not be keeping track of a reference over multiple frames. PersistentVoice: Used when the client needs to hold on to a Voice reference long-term. StreamingVoice: Used for playing back AudioDataStreamable objects. SoundSequence: Used to play back a series of AudioBuffers in sequence. They have a callback so that AudioBuffers can be added dynamically by the client. SourceVoices are intended to be pooled. You can obtain one from the AudioDevice pool by calling AudioDevice.Obtain<T> where T is the type of SourceVoice you wish to obtain. When you call Return on the voice it will be returned to the pool. TransientVoices are automatically returned to the pool when they have finished playing back their AudioBuffer. SourceVoices can send audio to SubmixVoices. This is a convenient way to manage categories of audio. For example the client could have a MusicSubmix that all music-related voices send to. Then the volume of all music can be changed at once without the client having to manage all the individual music voices. By default all voices send audio to AudioDevice.MasteringVoice. This is also a SubmixVoice that can be controlled like any other voice. AudioDataStreamable is used in conjunction with a StreamingVoice to play back streaming audio from an ogg or qoa file. AudioDataWav, AudioDataOgg, and AudioDataQoa all have a static CreateBuffer method that can be used to create an AudioBuffer from an audio file. Reviewed-on: https://gitea.moonside.games/MoonsideGames/MoonWorks/pulls/50
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>
Audio Restructuring (#50) This is a complete redesign of the MoonWorks Audio API. Voices are the new major concept. All Voices can be configured with volume, pitch, filters, panning and reverb. SourceVoices take in AudioBuffers and use them to play sound. They contain their own playback state. There are multiple kinds of SourceVoices: TransientVoice: Used for short sound effects where the client will not be keeping track of a reference over multiple frames. PersistentVoice: Used when the client needs to hold on to a Voice reference long-term. StreamingVoice: Used for playing back AudioDataStreamable objects. SoundSequence: Used to play back a series of AudioBuffers in sequence. They have a callback so that AudioBuffers can be added dynamically by the client. SourceVoices are intended to be pooled. You can obtain one from the AudioDevice pool by calling AudioDevice.Obtain<T> where T is the type of SourceVoice you wish to obtain. When you call Return on the voice it will be returned to the pool. TransientVoices are automatically returned to the pool when they have finished playing back their AudioBuffer. SourceVoices can send audio to SubmixVoices. This is a convenient way to manage categories of audio. For example the client could have a MusicSubmix that all music-related voices send to. Then the volume of all music can be changed at once without the client having to manage all the individual music voices. By default all voices send audio to AudioDevice.MasteringVoice. This is also a SubmixVoice that can be controlled like any other voice. AudioDataStreamable is used in conjunction with a StreamingVoice to play back streaming audio from an ogg or qoa file. AudioDataWav, AudioDataOgg, and AudioDataQoa all have a static CreateBuffer method that can be used to create an AudioBuffer from an audio file. Reviewed-on: https://gitea.moonside.games/MoonsideGames/MoonWorks/pulls/50
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!");
Audio Restructuring (#50) This is a complete redesign of the MoonWorks Audio API. Voices are the new major concept. All Voices can be configured with volume, pitch, filters, panning and reverb. SourceVoices take in AudioBuffers and use them to play sound. They contain their own playback state. There are multiple kinds of SourceVoices: TransientVoice: Used for short sound effects where the client will not be keeping track of a reference over multiple frames. PersistentVoice: Used when the client needs to hold on to a Voice reference long-term. StreamingVoice: Used for playing back AudioDataStreamable objects. SoundSequence: Used to play back a series of AudioBuffers in sequence. They have a callback so that AudioBuffers can be added dynamically by the client. SourceVoices are intended to be pooled. You can obtain one from the AudioDevice pool by calling AudioDevice.Obtain<T> where T is the type of SourceVoice you wish to obtain. When you call Return on the voice it will be returned to the pool. TransientVoices are automatically returned to the pool when they have finished playing back their AudioBuffer. SourceVoices can send audio to SubmixVoices. This is a convenient way to manage categories of audio. For example the client could have a MusicSubmix that all music-related voices send to. Then the volume of all music can be changed at once without the client having to manage all the individual music voices. By default all voices send audio to AudioDevice.MasteringVoice. This is also a SubmixVoice that can be controlled like any other voice. AudioDataStreamable is used in conjunction with a StreamingVoice to play back streaming audio from an ogg or qoa file. AudioDataWav, AudioDataOgg, and AudioDataQoa all have a static CreateBuffer method that can be used to create an AudioBuffer from an audio file. Reviewed-on: https://gitea.moonside.games/MoonsideGames/MoonWorks/pulls/50
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()
Audio Restructuring (#50) This is a complete redesign of the MoonWorks Audio API. Voices are the new major concept. All Voices can be configured with volume, pitch, filters, panning and reverb. SourceVoices take in AudioBuffers and use them to play sound. They contain their own playback state. There are multiple kinds of SourceVoices: TransientVoice: Used for short sound effects where the client will not be keeping track of a reference over multiple frames. PersistentVoice: Used when the client needs to hold on to a Voice reference long-term. StreamingVoice: Used for playing back AudioDataStreamable objects. SoundSequence: Used to play back a series of AudioBuffers in sequence. They have a callback so that AudioBuffers can be added dynamically by the client. SourceVoices are intended to be pooled. You can obtain one from the AudioDevice pool by calling AudioDevice.Obtain<T> where T is the type of SourceVoice you wish to obtain. When you call Return on the voice it will be returned to the pool. TransientVoices are automatically returned to the pool when they have finished playing back their AudioBuffer. SourceVoices can send audio to SubmixVoices. This is a convenient way to manage categories of audio. For example the client could have a MusicSubmix that all music-related voices send to. Then the volume of all music can be changed at once without the client having to manage all the individual music voices. By default all voices send audio to AudioDevice.MasteringVoice. This is also a SubmixVoice that can be controlled like any other voice. AudioDataStreamable is used in conjunction with a StreamingVoice to play back streaming audio from an ogg or qoa file. AudioDataWav, AudioDataOgg, and AudioDataQoa all have a static CreateBuffer method that can be used to create an AudioBuffer from an audio file. Reviewed-on: https://gitea.moonside.games/MoonsideGames/MoonWorks/pulls/50
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;
}
Audio Restructuring (#50) This is a complete redesign of the MoonWorks Audio API. Voices are the new major concept. All Voices can be configured with volume, pitch, filters, panning and reverb. SourceVoices take in AudioBuffers and use them to play sound. They contain their own playback state. There are multiple kinds of SourceVoices: TransientVoice: Used for short sound effects where the client will not be keeping track of a reference over multiple frames. PersistentVoice: Used when the client needs to hold on to a Voice reference long-term. StreamingVoice: Used for playing back AudioDataStreamable objects. SoundSequence: Used to play back a series of AudioBuffers in sequence. They have a callback so that AudioBuffers can be added dynamically by the client. SourceVoices are intended to be pooled. You can obtain one from the AudioDevice pool by calling AudioDevice.Obtain<T> where T is the type of SourceVoice you wish to obtain. When you call Return on the voice it will be returned to the pool. TransientVoices are automatically returned to the pool when they have finished playing back their AudioBuffer. SourceVoices can send audio to SubmixVoices. This is a convenient way to manage categories of audio. For example the client could have a MusicSubmix that all music-related voices send to. Then the volume of all music can be changed at once without the client having to manage all the individual music voices. By default all voices send audio to AudioDevice.MasteringVoice. This is also a SubmixVoice that can be controlled like any other voice. AudioDataStreamable is used in conjunction with a StreamingVoice to play back streaming audio from an ogg or qoa file. AudioDataWav, AudioDataOgg, and AudioDataQoa all have a static CreateBuffer method that can be used to create an AudioBuffer from an audio file. Reviewed-on: https://gitea.moonside.games/MoonsideGames/MoonWorks/pulls/50
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>
public static unsafe AudioBuffer CreateBuffer(AudioDevice device, string filePath)
Audio Restructuring (#50) This is a complete redesign of the MoonWorks Audio API. Voices are the new major concept. All Voices can be configured with volume, pitch, filters, panning and reverb. SourceVoices take in AudioBuffers and use them to play sound. They contain their own playback state. There are multiple kinds of SourceVoices: TransientVoice: Used for short sound effects where the client will not be keeping track of a reference over multiple frames. PersistentVoice: Used when the client needs to hold on to a Voice reference long-term. StreamingVoice: Used for playing back AudioDataStreamable objects. SoundSequence: Used to play back a series of AudioBuffers in sequence. They have a callback so that AudioBuffers can be added dynamically by the client. SourceVoices are intended to be pooled. You can obtain one from the AudioDevice pool by calling AudioDevice.Obtain<T> where T is the type of SourceVoice you wish to obtain. When you call Return on the voice it will be returned to the pool. TransientVoices are automatically returned to the pool when they have finished playing back their AudioBuffer. SourceVoices can send audio to SubmixVoices. This is a convenient way to manage categories of audio. For example the client could have a MusicSubmix that all music-related voices send to. Then the volume of all music can be changed at once without the client having to manage all the individual music voices. By default all voices send audio to AudioDevice.MasteringVoice. This is also a SubmixVoice that can be controlled like any other voice. AudioDataStreamable is used in conjunction with a StreamingVoice to play back streaming audio from an ogg or qoa file. AudioDataWav, AudioDataOgg, and AudioDataQoa all have a static CreateBuffer method that can be used to create an AudioBuffer from an audio file. Reviewed-on: https://gitea.moonside.games/MoonsideGames/MoonWorks/pulls/50
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!");
Audio Restructuring (#50) This is a complete redesign of the MoonWorks Audio API. Voices are the new major concept. All Voices can be configured with volume, pitch, filters, panning and reverb. SourceVoices take in AudioBuffers and use them to play sound. They contain their own playback state. There are multiple kinds of SourceVoices: TransientVoice: Used for short sound effects where the client will not be keeping track of a reference over multiple frames. PersistentVoice: Used when the client needs to hold on to a Voice reference long-term. StreamingVoice: Used for playing back AudioDataStreamable objects. SoundSequence: Used to play back a series of AudioBuffers in sequence. They have a callback so that AudioBuffers can be added dynamically by the client. SourceVoices are intended to be pooled. You can obtain one from the AudioDevice pool by calling AudioDevice.Obtain<T> where T is the type of SourceVoice you wish to obtain. When you call Return on the voice it will be returned to the pool. TransientVoices are automatically returned to the pool when they have finished playing back their AudioBuffer. SourceVoices can send audio to SubmixVoices. This is a convenient way to manage categories of audio. For example the client could have a MusicSubmix that all music-related voices send to. Then the volume of all music can be changed at once without the client having to manage all the individual music voices. By default all voices send audio to AudioDevice.MasteringVoice. This is also a SubmixVoice that can be controlled like any other voice. AudioDataStreamable is used in conjunction with a StreamingVoice to play back streaming audio from an ogg or qoa file. AudioDataWav, AudioDataOgg, and AudioDataQoa all have a static CreateBuffer method that can be used to create an AudioBuffer from an audio file. Reviewed-on: https://gitea.moonside.games/MoonsideGames/MoonWorks/pulls/50
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);
}
}
}