From 7c97cf8bddc7ded091a896020698c98ccd3f5f7f Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Mon, 4 Apr 2022 23:33:36 -0700 Subject: [PATCH] WAV static sounds + static sound instance pool --- SoundInstance.cs | 24 +++-- StaticSound.cs | 218 ++++++++++++++++++++++++++++++++++++++++- StaticSoundInstance.cs | 7 +- StreamingSound.cs | 5 +- StreamingSoundOgg.cs | 11 ++- 5 files changed, 249 insertions(+), 16 deletions(-) diff --git a/SoundInstance.cs b/SoundInstance.cs index 9d99697..db128e8 100644 --- a/SoundInstance.cs +++ b/SoundInstance.cs @@ -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; diff --git a/StaticSound.cs b/StaticSound.cs index 526c384..15434d4 100644 --- a/StaticSound.cs +++ b/StaticSound.cs @@ -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 Instances = new Stack(); + 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) + /// + /// Gets a sound instance from the pool. + /// NOTE: If you lose track of instances, you will create garbage collection pressure! + /// + 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() diff --git a/StaticSoundInstance.cs b/StaticSoundInstance.cs index 7fac5e8..bb472fc 100644 --- a/StaticSoundInstance.cs +++ b/StaticSoundInstance.cs @@ -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); + } } } diff --git a/StreamingSound.cs b/StreamingSound.cs index 238a001..a045bc9 100644 --- a/StreamingSound.cs +++ b/StreamingSound.cs @@ -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() { diff --git a/StreamingSoundOgg.cs b/StreamingSoundOgg.cs index b962d40..6d31aa6 100644 --- a/StreamingSoundOgg.cs +++ b/StreamingSoundOgg.cs @@ -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;