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 Instances = new Stack(); 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; } /// /// Gets a sound instance from the pool. /// NOTE: If you lose track of instances, you will create garbage collection pressure! /// 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); } } }