295 lines
7.5 KiB
C#
295 lines
7.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace MoonWorks.Audio
|
|
{
|
|
public class StaticSound : AudioResource
|
|
{
|
|
internal FAudio.FAudioBuffer Handle;
|
|
public ushort FormatTag { get; }
|
|
public ushort BitsPerSample { get; }
|
|
public ushort Channels { get; }
|
|
public uint SamplesPerSecond { get; }
|
|
public ushort BlockAlign { get; }
|
|
|
|
public uint LoopStart { get; set; } = 0;
|
|
public uint LoopLength { get; set; } = 0;
|
|
|
|
private Stack<StaticSoundInstance> Instances = new Stack<StaticSoundInstance>();
|
|
|
|
public static StaticSound LoadOgg(AudioDevice device, string filePath)
|
|
{
|
|
var filePointer = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero);
|
|
|
|
if (error != 0)
|
|
{
|
|
throw new AudioLoadException("Error loading file!");
|
|
}
|
|
var info = FAudio.stb_vorbis_get_info(filePointer);
|
|
var bufferSize = FAudio.stb_vorbis_stream_length_in_samples(filePointer) * info.channels;
|
|
var buffer = new float[bufferSize];
|
|
|
|
FAudio.stb_vorbis_get_samples_float_interleaved(
|
|
filePointer,
|
|
info.channels,
|
|
buffer,
|
|
(int) bufferSize
|
|
);
|
|
|
|
FAudio.stb_vorbis_close(filePointer);
|
|
|
|
return new StaticSound(
|
|
device,
|
|
(ushort) info.channels,
|
|
info.sample_rate,
|
|
buffer,
|
|
0,
|
|
(uint) buffer.Length
|
|
);
|
|
}
|
|
|
|
// mostly borrowed from https://github.com/FNA-XNA/FNA/blob/b71b4a35ae59970ff0070dea6f8620856d8d4fec/src/Audio/SoundEffect.cs#L385
|
|
public static StaticSound LoadWav(AudioDevice device, string filePath)
|
|
{
|
|
// Sample data
|
|
byte[] data;
|
|
|
|
// WaveFormatEx data
|
|
ushort wFormatTag;
|
|
ushort nChannels;
|
|
uint nSamplesPerSec;
|
|
uint nAvgBytesPerSec;
|
|
ushort nBlockAlign;
|
|
ushort wBitsPerSample;
|
|
int samplerLoopStart = 0;
|
|
int samplerLoopEnd = 0;
|
|
|
|
using (BinaryReader reader = new BinaryReader(File.OpenRead(filePath)))
|
|
{
|
|
// RIFF Signature
|
|
string signature = new string(reader.ReadChars(4));
|
|
if (signature != "RIFF")
|
|
{
|
|
throw new NotSupportedException("Specified stream is not a wave file.");
|
|
}
|
|
|
|
reader.ReadUInt32(); // Riff Chunk Size
|
|
|
|
string wformat = new string(reader.ReadChars(4));
|
|
if (wformat != "WAVE")
|
|
{
|
|
throw new NotSupportedException("Specified stream is not a wave file.");
|
|
}
|
|
|
|
// WAVE Header
|
|
string format_signature = new string(reader.ReadChars(4));
|
|
while (format_signature != "fmt ")
|
|
{
|
|
reader.ReadBytes(reader.ReadInt32());
|
|
format_signature = new string(reader.ReadChars(4));
|
|
}
|
|
|
|
int format_chunk_size = reader.ReadInt32();
|
|
|
|
wFormatTag = reader.ReadUInt16();
|
|
nChannels = reader.ReadUInt16();
|
|
nSamplesPerSec = reader.ReadUInt32();
|
|
nAvgBytesPerSec = reader.ReadUInt32();
|
|
nBlockAlign = reader.ReadUInt16();
|
|
wBitsPerSample = reader.ReadUInt16();
|
|
|
|
// Reads residual bytes
|
|
if (format_chunk_size > 16)
|
|
{
|
|
reader.ReadBytes(format_chunk_size - 16);
|
|
}
|
|
|
|
// data Signature
|
|
string data_signature = new string(reader.ReadChars(4));
|
|
while (data_signature.ToLowerInvariant() != "data")
|
|
{
|
|
reader.ReadBytes(reader.ReadInt32());
|
|
data_signature = new string(reader.ReadChars(4));
|
|
}
|
|
if (data_signature != "data")
|
|
{
|
|
throw new NotSupportedException("Specified wave file is not supported.");
|
|
}
|
|
|
|
int waveDataLength = reader.ReadInt32();
|
|
data = reader.ReadBytes(waveDataLength);
|
|
|
|
// Scan for other chunks
|
|
while (reader.PeekChar() != -1)
|
|
{
|
|
char[] chunkIDChars = reader.ReadChars(4);
|
|
if (chunkIDChars.Length < 4)
|
|
{
|
|
break; // EOL!
|
|
}
|
|
byte[] chunkSizeBytes = reader.ReadBytes(4);
|
|
if (chunkSizeBytes.Length < 4)
|
|
{
|
|
break; // EOL!
|
|
}
|
|
string chunk_signature = new string(chunkIDChars);
|
|
int chunkDataSize = BitConverter.ToInt32(chunkSizeBytes, 0);
|
|
if (chunk_signature == "smpl") // "smpl", Sampler Chunk Found
|
|
{
|
|
reader.ReadUInt32(); // Manufacturer
|
|
reader.ReadUInt32(); // Product
|
|
reader.ReadUInt32(); // Sample Period
|
|
reader.ReadUInt32(); // MIDI Unity Note
|
|
reader.ReadUInt32(); // MIDI Pitch Fraction
|
|
reader.ReadUInt32(); // SMPTE Format
|
|
reader.ReadUInt32(); // SMPTE Offset
|
|
uint numSampleLoops = reader.ReadUInt32();
|
|
int samplerData = reader.ReadInt32();
|
|
|
|
for (int i = 0; i < numSampleLoops; i += 1)
|
|
{
|
|
reader.ReadUInt32(); // Cue Point ID
|
|
reader.ReadUInt32(); // Type
|
|
int start = reader.ReadInt32();
|
|
int end = reader.ReadInt32();
|
|
reader.ReadUInt32(); // Fraction
|
|
reader.ReadUInt32(); // Play Count
|
|
|
|
if (i == 0) // Grab loopStart and loopEnd from first sample loop
|
|
{
|
|
samplerLoopStart = start;
|
|
samplerLoopEnd = end;
|
|
}
|
|
}
|
|
|
|
if (samplerData != 0) // Read Sampler Data if it exists
|
|
{
|
|
reader.ReadBytes(samplerData);
|
|
}
|
|
}
|
|
else // Read unwanted chunk data and try again
|
|
{
|
|
reader.ReadBytes(chunkDataSize);
|
|
}
|
|
}
|
|
// End scan
|
|
}
|
|
|
|
return new StaticSound(
|
|
device,
|
|
wFormatTag,
|
|
wBitsPerSample,
|
|
nBlockAlign,
|
|
nChannels,
|
|
nSamplesPerSec,
|
|
data,
|
|
0,
|
|
(uint) data.Length
|
|
);
|
|
}
|
|
|
|
public StaticSound(
|
|
AudioDevice device,
|
|
ushort formatTag,
|
|
ushort bitsPerSample,
|
|
ushort blockAlign,
|
|
ushort channels,
|
|
uint samplesPerSecond,
|
|
byte[] buffer,
|
|
uint bufferOffset, /* number of bytes */
|
|
uint bufferLength /* number of bytes */
|
|
) : base(device)
|
|
{
|
|
FormatTag = formatTag;
|
|
BitsPerSample = bitsPerSample;
|
|
BlockAlign = blockAlign;
|
|
Channels = channels;
|
|
SamplesPerSecond = samplesPerSecond;
|
|
|
|
Handle = new FAudio.FAudioBuffer();
|
|
Handle.Flags = FAudio.FAUDIO_END_OF_STREAM;
|
|
Handle.pContext = IntPtr.Zero;
|
|
Handle.AudioBytes = bufferLength;
|
|
Handle.pAudioData = Marshal.AllocHGlobal((int) bufferLength);
|
|
Marshal.Copy(buffer, (int) bufferOffset, Handle.pAudioData, (int) bufferLength);
|
|
Handle.PlayBegin = 0;
|
|
Handle.PlayLength = 0;
|
|
|
|
if (formatTag == 1)
|
|
{
|
|
Handle.PlayLength = (uint) (
|
|
bufferLength /
|
|
channels /
|
|
(bitsPerSample / 8)
|
|
);
|
|
}
|
|
else if (formatTag == 2)
|
|
{
|
|
Handle.PlayLength = (uint) (
|
|
bufferLength /
|
|
blockAlign *
|
|
(((blockAlign / channels) - 6) * 2)
|
|
);
|
|
}
|
|
|
|
LoopStart = 0;
|
|
LoopLength = 0;
|
|
}
|
|
|
|
public StaticSound(
|
|
AudioDevice device,
|
|
ushort channels,
|
|
uint samplesPerSecond,
|
|
float[] buffer,
|
|
uint bufferOffset, /* in floats */
|
|
uint bufferLength /* in floats */
|
|
) : base(device)
|
|
{
|
|
FormatTag = 3;
|
|
BitsPerSample = 32;
|
|
BlockAlign = (ushort) (4 * channels);
|
|
Channels = channels;
|
|
SamplesPerSecond = samplesPerSecond;
|
|
|
|
var bufferLengthInBytes = (int) (bufferLength * sizeof(float));
|
|
Handle = new FAudio.FAudioBuffer();
|
|
Handle.Flags = FAudio.FAUDIO_END_OF_STREAM;
|
|
Handle.pContext = IntPtr.Zero;
|
|
Handle.AudioBytes = (uint) bufferLengthInBytes;
|
|
Handle.pAudioData = Marshal.AllocHGlobal(bufferLengthInBytes);
|
|
Marshal.Copy(buffer, (int) bufferOffset, Handle.pAudioData, (int) bufferLength);
|
|
Handle.PlayBegin = 0;
|
|
Handle.PlayLength = 0;
|
|
|
|
LoopStart = 0;
|
|
LoopLength = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a sound instance from the pool.
|
|
/// NOTE: If you lose track of instances, you will create garbage collection pressure!
|
|
/// </summary>
|
|
public StaticSoundInstance GetInstance()
|
|
{
|
|
if (Instances.Count == 0)
|
|
{
|
|
Instances.Push(new StaticSoundInstance(Device, this));
|
|
}
|
|
|
|
return Instances.Pop();
|
|
}
|
|
|
|
internal void FreeInstance(StaticSoundInstance instance)
|
|
{
|
|
Instances.Push(instance);
|
|
}
|
|
|
|
protected override void Destroy()
|
|
{
|
|
Marshal.FreeHGlobal(Handle.pAudioData);
|
|
}
|
|
}
|
|
}
|