| 1 | using System;
 | 
|---|
| 2 | using System.Collections.Generic;
 | 
|---|
| 3 | using System.IO;
 | 
|---|
| 4 | //using System.Runtime.Remoting.Metadata.W3cXsd2001;
 | 
|---|
| 5 | 
 | 
|---|
| 6 | namespace Oni.Sound
 | 
|---|
| 7 | {
 | 
|---|
| 8 |     internal class WavFile
 | 
|---|
| 9 |     {
 | 
|---|
| 10 |         #region Private data
 | 
|---|
| 11 |         private const int fcc_RIFF = 0x46464952;
 | 
|---|
| 12 |         private const int fcc_WAVE = 0x45564157;
 | 
|---|
| 13 |         private const int fcc_fmt  = 0x20746d66;
 | 
|---|
| 14 |         private const int fcc_fact = 0x74636166;
 | 
|---|
| 15 |         private const int fcc_data = 0x61746164;
 | 
|---|
| 16 | 
 | 
|---|
| 17 |         private WavFormat format;
 | 
|---|
| 18 |         private int channelCount;
 | 
|---|
| 19 |         private int sampleRate;
 | 
|---|
| 20 |         private int averageBytesPerSecond;
 | 
|---|
| 21 |         private int blockAlign;
 | 
|---|
| 22 |         private int bitsPerSample;
 | 
|---|
| 23 |         private int sampleCount;
 | 
|---|
| 24 |         private byte[] extraData;
 | 
|---|
| 25 |         private byte[] soundData;
 | 
|---|
| 26 |         #endregion
 | 
|---|
| 27 | 
 | 
|---|
| 28 |         public static WavFile FromFile(string filePath)
 | 
|---|
| 29 |         {
 | 
|---|
| 30 |             using (var reader = new BinaryReader(filePath))
 | 
|---|
| 31 |             {
 | 
|---|
| 32 |                 if (reader.ReadInt32() != fcc_RIFF)
 | 
|---|
| 33 |                     throw new InvalidDataException("Not a WAV file");
 | 
|---|
| 34 | 
 | 
|---|
| 35 |                 int size = reader.ReadInt32();
 | 
|---|
| 36 | 
 | 
|---|
| 37 |                 if (reader.ReadInt32() != fcc_WAVE)
 | 
|---|
| 38 |                     throw new InvalidDataException("Not a WAV file");
 | 
|---|
| 39 | 
 | 
|---|
| 40 |                 var header = new WavFile()
 | 
|---|
| 41 |                 {
 | 
|---|
| 42 |                     sampleCount = -1
 | 
|---|
| 43 |                 };
 | 
|---|
| 44 | 
 | 
|---|
| 45 |                 for (int chunkType, chunkSize, chunkStart; reader.Position < size; reader.Position = chunkStart + chunkSize)
 | 
|---|
| 46 |                 {
 | 
|---|
| 47 |                     chunkType = reader.ReadInt32();
 | 
|---|
| 48 |                     chunkSize = reader.ReadInt32();
 | 
|---|
| 49 |                     chunkStart = reader.Position;
 | 
|---|
| 50 | 
 | 
|---|
| 51 |                     if (chunkType == fcc_fmt)
 | 
|---|
| 52 |                         header.ReadFormatChunk(reader, chunkSize);
 | 
|---|
| 53 |                     if (chunkType == fcc_fact)
 | 
|---|
| 54 |                         header.ReadFactChunk(reader, chunkSize);
 | 
|---|
| 55 |                     if (chunkType == fcc_data)
 | 
|---|
| 56 |                         header.ReadDataChunk(reader, chunkSize);
 | 
|---|
| 57 |                 }
 | 
|---|
| 58 |                 header.TruncatePerFact();
 | 
|---|
| 59 |                 return header;
 | 
|---|
| 60 |             }
 | 
|---|
| 61 |         }
 | 
|---|
| 62 | 
 | 
|---|
| 63 |         private void ReadFormatChunk(BinaryReader reader, int chunkSize)
 | 
|---|
| 64 |         {
 | 
|---|
| 65 |             format = (WavFormat)reader.ReadInt16();
 | 
|---|
| 66 |             channelCount = reader.ReadInt16();
 | 
|---|
| 67 |             sampleRate = reader.ReadInt32();
 | 
|---|
| 68 |             averageBytesPerSecond = reader.ReadInt32();
 | 
|---|
| 69 |             blockAlign = reader.ReadInt16();
 | 
|---|
| 70 |             bitsPerSample = reader.ReadInt16();
 | 
|---|
| 71 | 
 | 
|---|
| 72 |             if (chunkSize > 16)
 | 
|---|
| 73 |                 extraData = reader.ReadBytes(reader.ReadInt16());
 | 
|---|
| 74 |             else
 | 
|---|
| 75 |                 extraData = new byte[0];
 | 
|---|
| 76 |         }
 | 
|---|
| 77 | 
 | 
|---|
| 78 |         private void ReadFactChunk(BinaryReader reader, int chunkSize)
 | 
|---|
| 79 |         {
 | 
|---|
| 80 |             sampleCount = reader.ReadInt32();
 | 
|---|
| 81 |         }
 | 
|---|
| 82 | 
 | 
|---|
| 83 |         private void ReadDataChunk(BinaryReader reader, int chunkSize)
 | 
|---|
| 84 |         {
 | 
|---|
| 85 |             soundData = reader.ReadBytes(chunkSize);
 | 
|---|
| 86 |         }
 | 
|---|
| 87 |         private void TruncatePerFact() // TODO: MORE THOROUGH VALIDATION?
 | 
|---|
| 88 |         {
 | 
|---|
| 89 |             if(sampleCount == -1) // not explicitly set (no fact chunk present)
 | 
|---|
| 90 |             {
 | 
|---|
| 91 |                 Console.WriteLine("The imported WAV file has no FACT chunk.");
 | 
|---|
| 92 |             }
 | 
|---|
| 93 |             else if (format == WavFormat.Adpcm) // calculate truncated data size
 | 
|---|
| 94 |             {
 | 
|---|
| 95 |                 var blockSizeADPCM = blockAlign;
 | 
|---|
| 96 |                 var samplesPerBlock = 2 + (blockSizeADPCM - channelCount * 7) * 8 / channelCount / bitsPerSample;
 | 
|---|
| 97 |                 int wholeBlocks = sampleCount / samplesPerBlock;
 | 
|---|
| 98 |                 if (wholeBlocks * blockAlign > soundData.Length)
 | 
|---|
| 99 |                     Console.Error.WriteLine("Sample count exceeds the range of sound data.");
 | 
|---|
| 100 |                 int leftoverSamples = sampleCount - wholeBlocks * samplesPerBlock;
 | 
|---|
| 101 |                 if (leftoverSamples < 2) // a block always starts with at least two samples?
 | 
|---|
| 102 |                     Console.Error.WriteLine("Improper trailing bytes/samples!");
 | 
|---|
| 103 |                 if (bitsPerSample != 4) // are MS ADPCM nibbles always 4-bit-sized?
 | 
|---|
| 104 |                     Console.Error.WriteLine("Nibble size is expected to be 4 bits!");
 | 
|---|
| 105 |                 int leftoverNibbles = (leftoverSamples - 2) * channelCount;
 | 
|---|
| 106 |                 int leftoverBytes = 7 * channelCount
 | 
|---|
| 107 |                     + (int)Math.Ceiling((leftoverNibbles * bitsPerSample) * 0.125);
 | 
|---|
| 108 |                 Array.Resize(ref soundData, wholeBlocks * blockAlign + leftoverBytes);
 | 
|---|
| 109 |             }
 | 
|---|
| 110 |         }
 | 
|---|
| 111 | 
 | 
|---|
| 112 |         public WavFormat Format => format;
 | 
|---|
| 113 |         public int ChannelCount => channelCount;
 | 
|---|
| 114 |         public int SampleRate => sampleRate;
 | 
|---|
| 115 |         public int AverageBytesPerSecond => averageBytesPerSecond;
 | 
|---|
| 116 |         public int BlockAlign => blockAlign;
 | 
|---|
| 117 |         public int BitsPerSample => bitsPerSample;
 | 
|---|
| 118 |         public int SampleCount => sampleCount;
 | 
|---|
| 119 |         public byte[] ExtraData => extraData;
 | 
|---|
| 120 |         public byte[] SoundData => soundData;
 | 
|---|
| 121 |     }
 | 
|---|
| 122 | }
 | 
|---|