using System; using System.Collections.Generic; using System.IO; //using System.Runtime.Remoting.Metadata.W3cXsd2001; namespace Oni.Sound { internal class WavFile { #region Private data private const int fcc_RIFF = 0x46464952; private const int fcc_WAVE = 0x45564157; private const int fcc_fmt = 0x20746d66; private const int fcc_fact = 0x74636166; private const int fcc_data = 0x61746164; private WavFormat format; private int channelCount; private int sampleRate; private int averageBytesPerSecond; private int blockAlign; private int bitsPerSample; private int sampleCount; private byte[] extraData; private byte[] soundData; #endregion public static WavFile FromFile(string filePath) { using (var reader = new BinaryReader(filePath)) { if (reader.ReadInt32() != fcc_RIFF) throw new InvalidDataException("Not a WAV file"); int size = reader.ReadInt32(); if (reader.ReadInt32() != fcc_WAVE) throw new InvalidDataException("Not a WAV file"); var header = new WavFile() { sampleCount = -1 }; for (int chunkType, chunkSize, chunkStart; reader.Position < size; reader.Position = chunkStart + chunkSize) { chunkType = reader.ReadInt32(); chunkSize = reader.ReadInt32(); chunkStart = reader.Position; if (chunkType == fcc_fmt) header.ReadFormatChunk(reader, chunkSize); if (chunkType == fcc_fact) header.ReadFactChunk(reader, chunkSize); if (chunkType == fcc_data) header.ReadDataChunk(reader, chunkSize); } header.TruncatePerFact(); return header; } } private void ReadFormatChunk(BinaryReader reader, int chunkSize) { format = (WavFormat)reader.ReadInt16(); channelCount = reader.ReadInt16(); sampleRate = reader.ReadInt32(); averageBytesPerSecond = reader.ReadInt32(); blockAlign = reader.ReadInt16(); bitsPerSample = reader.ReadInt16(); if (chunkSize > 16) extraData = reader.ReadBytes(reader.ReadInt16()); else extraData = new byte[0]; } private void ReadFactChunk(BinaryReader reader, int chunkSize) { sampleCount = reader.ReadInt32(); } private void ReadDataChunk(BinaryReader reader, int chunkSize) { soundData = reader.ReadBytes(chunkSize); } private void TruncatePerFact() // TODO: MORE THOROUGH VALIDATION? { if(sampleCount == -1) // not explicitly set (no fact chunk present) { Console.WriteLine("The imported WAV file has no FACT chunk."); } else if (format == WavFormat.Adpcm) // calculate truncated data size { var blockSizeADPCM = blockAlign; var samplesPerBlock = 2 + (blockSizeADPCM - channelCount * 7) * 8 / channelCount / bitsPerSample; int wholeBlocks = sampleCount / samplesPerBlock; if (wholeBlocks * blockAlign > soundData.Length) Console.Error.WriteLine("Sample count exceeds the range of sound data."); int leftoverSamples = sampleCount - wholeBlocks * samplesPerBlock; if (leftoverSamples < 2) // a block always starts with at least two samples? Console.Error.WriteLine("Improper trailing bytes/samples!"); if (bitsPerSample != 4) // are MS ADPCM nibbles always 4-bit-sized? Console.Error.WriteLine("Nibble size is expected to be 4 bits!"); int leftoverNibbles = (leftoverSamples - 2) * channelCount; int leftoverBytes = 7 * channelCount + (int)Math.Ceiling((leftoverNibbles * bitsPerSample) * 0.125); Array.Resize(ref soundData, wholeBlocks * blockAlign + leftoverBytes); } } public WavFormat Format => format; public int ChannelCount => channelCount; public int SampleRate => sampleRate; public int AverageBytesPerSecond => averageBytesPerSecond; public int BlockAlign => blockAlign; public int BitsPerSample => bitsPerSample; public int SampleCount => sampleCount; public byte[] ExtraData => extraData; public byte[] SoundData => soundData; } }