source: OniSplit/Sound/WavFile.cs @ 1156

Last change on this file since 1156 was 1156, checked in by geyser, 14 months ago

Fixed importing of "fact" chunk, as well as an unhandled fatal when attempting to transcode IMA4 to MS ADPCM. Also added missing padding to SNDD template and fixed the duration calculation for WAV-to-SNDD.

File size: 4.9 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.IO;
4//using System.Runtime.Remoting.Metadata.W3cXsd2001;
5
6namespace 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}
Note: See TracBrowser for help on using the repository browser.