WAV static sounds + static sound instance pool

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

View File

@ -1,4 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace MoonWorks.Audio namespace MoonWorks.Audio
@ -6,12 +8,17 @@ namespace MoonWorks.Audio
public class StaticSound : AudioResource public class StaticSound : AudioResource
{ {
internal FAudio.FAudioBuffer Handle; internal FAudio.FAudioBuffer Handle;
public ushort FormatTag { get; }
public ushort BitsPerSample { get; }
public ushort Channels { get; } public ushort Channels { get; }
public uint SamplesPerSecond { get; } public uint SamplesPerSecond { get; }
public ushort BlockAlign { get; }
public uint LoopStart { get; set; } = 0; public uint LoopStart { get; set; } = 0;
public uint LoopLength { get; set; } = 0; public uint LoopLength { get; set; } = 0;
private Stack<StaticSoundInstance> Instances = new Stack<StaticSoundInstance>();
public static StaticSound LoadOgg(AudioDevice device, string filePath) public static StaticSound LoadOgg(AudioDevice device, string filePath)
{ {
var filePointer = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero); 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( public StaticSound(
AudioDevice device, AudioDevice device,
ushort channels, ushort channels,
@ -52,6 +247,9 @@ namespace MoonWorks.Audio
uint bufferLength /* in floats */ uint bufferLength /* in floats */
) : base(device) ) : base(device)
{ {
FormatTag = 3;
BitsPerSample = 32;
BlockAlign = (ushort) (4 * channels);
Channels = channels; Channels = channels;
SamplesPerSecond = samplesPerSecond; SamplesPerSecond = samplesPerSecond;
@ -69,9 +267,23 @@ namespace MoonWorks.Audio
LoopLength = 0; 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() protected override void Destroy()

View File

@ -35,7 +35,7 @@ namespace MoonWorks.Audio
StaticSound parent, StaticSound parent,
bool is3D, bool is3D,
bool loop 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; Parent = parent;
} }
@ -92,5 +92,10 @@ namespace MoonWorks.Audio
FAudio.FAudioSourceVoice_ExitLoop(Handle, 0); FAudio.FAudioSourceVoice_ExitLoop(Handle, 0);
} }
} }
public void Free()
{
Parent.FreeInstance(this);
}
} }
} }

View File

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

View File

@ -46,7 +46,16 @@ namespace MoonWorks.Audio
FAudio.stb_vorbis_info info, FAudio.stb_vorbis_info info,
bool is3D, bool is3D,
bool loop 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; FileHandle = fileHandle;
Info = info; Info = info;