WAV static sounds + static sound instance pool

pull/18/head
cosmonaut 2022-04-04 23:33:36 -07:00
parent 08a3c01f66
commit b1b6b84809
7 changed files with 251 additions and 18 deletions

@ -1 +1 @@
Subproject commit de0c1f833c12a992af5c7daebe1705cd2c72f743
Subproject commit 0b6d5dabbf428633482fe3a956fbdb53228fcf35

@ -1 +1 @@
Subproject commit 4e9088b49de46ea8b4285948cfe69875ac4c2290
Subproject commit b35aaa494e44d08242788ff0ba2cb7a508f4d8f0

View File

@ -6,8 +6,8 @@ namespace MoonWorks.Audio
{
public abstract class SoundInstance : AudioResource
{
internal IntPtr Handle { get; }
internal FAudio.FAudioWaveFormatEx Format { get; }
internal IntPtr Handle;
internal FAudio.FAudioWaveFormatEx Format;
public bool Loop { get; }
protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings;
@ -163,17 +163,19 @@ namespace MoonWorks.Audio
public SoundInstance(
AudioDevice device,
ushort formatTag,
ushort bitsPerSample,
ushort blockAlign,
ushort channels,
uint samplesPerSecond,
bool is3D,
bool loop
) : base(device)
{
var blockAlign = (ushort) (4 * channels);
var format = new FAudio.FAudioWaveFormatEx
{
wFormatTag = 3,
wBitsPerSample = 32,
wFormatTag = formatTag,
wBitsPerSample = bitsPerSample,
nChannels = channels,
nBlockAlign = blockAlign,
nSamplesPerSec = samplesPerSecond,
@ -184,8 +186,8 @@ namespace MoonWorks.Audio
FAudio.FAudio_CreateSourceVoice(
Device.Handle,
out var handle,
ref format,
out Handle,
ref Format,
FAudio.FAUDIO_VOICE_USEFILTER,
FAudio.FAUDIO_DEFAULT_FREQ_RATIO,
IntPtr.Zero,
@ -193,20 +195,22 @@ namespace MoonWorks.Audio
IntPtr.Zero
);
if (handle == IntPtr.Zero)
if (Handle == IntPtr.Zero)
{
Logger.LogError("SoundInstance failed to initialize!");
return;
}
Handle = handle;
this.is3D = is3D;
InitDSPSettings(Format.nChannels);
// FIXME: not everything should be running through reverb...
/*
FAudio.FAudioVoice_SetOutputVoices(
handle,
Handle,
ref Device.ReverbSends
);
*/
Loop = loop;
State = SoundState.Stopped;

View File

@ -1,4 +1,6 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
namespace MoonWorks.Audio
@ -6,12 +8,17 @@ 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);
@ -43,6 +50,194 @@ namespace MoonWorks.Audio
);
}
// 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,
@ -52,6 +247,9 @@ namespace MoonWorks.Audio
uint bufferLength /* in floats */
) : base(device)
{
FormatTag = 3;
BitsPerSample = 32;
BlockAlign = (ushort) (4 * channels);
Channels = channels;
SamplesPerSecond = samplesPerSecond;
@ -69,9 +267,23 @@ namespace MoonWorks.Audio
LoopLength = 0;
}
public StaticSoundInstance CreateInstance(bool loop = false)
/// <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(bool loop = false)
{
return new StaticSoundInstance(Device, this, false, loop);
if (Instances.Count == 0)
{
Instances.Push(new StaticSoundInstance(Device, this, false, loop));
}
return Instances.Pop();
}
internal void FreeInstance(StaticSoundInstance instance)
{
Instances.Push(instance);
}
protected override void Destroy()

View File

@ -35,7 +35,7 @@ namespace MoonWorks.Audio
StaticSound parent,
bool is3D,
bool loop
) : base(device, parent.Channels, parent.SamplesPerSecond, is3D, loop)
) : base(device, parent.FormatTag, parent.BitsPerSample, parent.BlockAlign, parent.Channels, parent.SamplesPerSecond, is3D, loop)
{
Parent = parent;
}
@ -92,5 +92,10 @@ namespace MoonWorks.Audio
FAudio.FAudioSourceVoice_ExitLoop(Handle, 0);
}
}
public void Free()
{
Parent.FreeInstance(this);
}
}
}

View File

@ -18,11 +18,14 @@ namespace MoonWorks.Audio
public StreamingSound(
AudioDevice device,
ushort formatTag,
ushort bitsPerSample,
ushort blockAlign,
ushort channels,
uint samplesPerSecond,
bool is3D,
bool loop
) : base(device, channels, samplesPerSecond, is3D, loop) { }
) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond, is3D, loop) { }
public override void Play()
{

View File

@ -46,7 +46,16 @@ namespace MoonWorks.Audio
FAudio.stb_vorbis_info info,
bool is3D,
bool loop
) : base(device, (ushort) info.channels, info.sample_rate, is3D, loop)
) : base(
device,
3, /* float type */
32, /* size of float */
(ushort) (4 * info.channels),
(ushort) info.channels,
info.sample_rate,
is3D,
loop
)
{
FileHandle = fileHandle;
Info = info;