1 | using System;
|
---|
2 | using System.Collections.Generic;
|
---|
3 | using System.Text;
|
---|
4 | using System.IO;
|
---|
5 |
|
---|
6 | namespace Oni.Sound
|
---|
7 | {
|
---|
8 | internal class SoundData
|
---|
9 | {
|
---|
10 | public bool IsPCM;
|
---|
11 | public bool IsIMA4;
|
---|
12 | public int SampleRate;
|
---|
13 | public int ChannelCount;
|
---|
14 | public byte[] Data;
|
---|
15 |
|
---|
16 | public UInt16 nGameTicks;
|
---|
17 |
|
---|
18 | public int SampleCount; // calculated
|
---|
19 |
|
---|
20 | //MS WAV variables
|
---|
21 | public int AverageDataRate;
|
---|
22 | public int BlockAlignment;
|
---|
23 | public int BitsPerSample;
|
---|
24 | public int HeaderSizeADPCM;
|
---|
25 |
|
---|
26 | // optional MSADPCM values
|
---|
27 | public UInt16 SamplesPerBlock;
|
---|
28 | public UInt16 nCoefPairs;
|
---|
29 | public Int16[] CoefADPCMa;
|
---|
30 | public Int16[] CoefADPCMb;
|
---|
31 |
|
---|
32 | public static SoundData Read(InstanceDescriptor sndd, bool do_pc_demo_test)
|
---|
33 | {
|
---|
34 | if (sndd.Template.Tag != TemplateTag.SNDD)
|
---|
35 | throw new ArgumentException("descriptor");
|
---|
36 |
|
---|
37 | var sound = new SoundData();
|
---|
38 |
|
---|
39 | int dataSize;
|
---|
40 | int dataOffset;
|
---|
41 |
|
---|
42 | using (var reader = sndd.OpenRead())
|
---|
43 | {
|
---|
44 | // sample rate
|
---|
45 | // duration in frames
|
---|
46 |
|
---|
47 | // IMA4 or
|
---|
48 | // MS ADPCM: block size (default 512 for mono, 1024 for stereo, interruptible)
|
---|
49 |
|
---|
50 | // sample count (automatic for IMA4 and PC demo)
|
---|
51 |
|
---|
52 |
|
---|
53 | // size in raw / offset in raw
|
---|
54 | if (sndd.IsMacFile)
|
---|
55 | {
|
---|
56 | sound.ChannelCount = (reader.ReadInt32() >> 1) + 1; // TODO: interpret the low bit? (uncompressed/compressed?)
|
---|
57 | sound.SampleRate = 22050;
|
---|
58 | sound.nGameTicks = (UInt16)reader.ReadInt32();
|
---|
59 |
|
---|
60 | sound.IsIMA4 = true;
|
---|
61 | sound.IsPCM = false;
|
---|
62 | }
|
---|
63 | else
|
---|
64 | {
|
---|
65 | sound.IsPCM = false;
|
---|
66 | reader.Skip(4); // flags (1=?, 2=?, 4=?, 8=compressed) TODO: Try uncompressed (even for demo?)
|
---|
67 | reader.Skip(2); // Int16; format ID (2= ADPCM, 1=PCM?) TODO: Try uncompressed (even for demo?)
|
---|
68 | sound.ChannelCount = reader.ReadInt16();
|
---|
69 | sound.SampleRate = reader.ReadInt32();
|
---|
70 | sound.AverageDataRate = reader.ReadInt32(); // in B/s (2 or 4 for PCM; 11155, 22311 or 22179 for Vanilla ADPCM)
|
---|
71 | sound.BlockAlignment = reader.ReadInt16(); // 2 or 4 bytes per block for PCM; 512 or 1024 for Vanilla ADPCM
|
---|
72 | sound.BitsPerSample = reader.ReadInt16(); // bits per sample per channel (4 bits per sample for ADPCM, 16 bits for PCM)
|
---|
73 | sound.HeaderSizeADPCM = reader.ReadInt16(); // size of additional (ADPCM) header (zero for PCM); typically 32 bytes
|
---|
74 |
|
---|
75 | sound.SamplesPerBlock = reader.ReadUInt16(); // UInt16; samples per block (can be inferred from block alignment etc)
|
---|
76 |
|
---|
77 | sound.nCoefPairs = reader.ReadUInt16(); // usually 7
|
---|
78 | sound.CoefADPCMa = new Int16[sound.nCoefPairs]; // usually 256 512 0 192 240 460 392
|
---|
79 | sound.CoefADPCMb = new Int16[sound.nCoefPairs]; // usually 0 -256 0 64 0 -208 -232
|
---|
80 | for (int coefPair = 0; coefPair < sound.nCoefPairs; ++coefPair)
|
---|
81 | {
|
---|
82 | sound.CoefADPCMa[coefPair] = reader.ReadInt16();
|
---|
83 | sound.CoefADPCMb[coefPair] = reader.ReadInt16();
|
---|
84 | }
|
---|
85 |
|
---|
86 | sound.nGameTicks = reader.ReadUInt16(); // UInt16; number of game ticks (truncated to lower value)
|
---|
87 | sound.IsIMA4 = false;
|
---|
88 | }
|
---|
89 |
|
---|
90 | dataSize = reader.ReadInt32();
|
---|
91 | dataOffset = reader.ReadInt32();
|
---|
92 | }
|
---|
93 |
|
---|
94 | using (var rawReader = sndd.GetRawReader(dataOffset))
|
---|
95 | sound.Data = rawReader.ReadBytes(dataSize);
|
---|
96 |
|
---|
97 | if (sound.IsIMA4 && do_pc_demo_test) // check if the raw data actually looks like IMA4
|
---|
98 | {
|
---|
99 | int nIMABlocks = sound.Data.Length / 34;
|
---|
100 | int remainder = sound.Data.Length - nIMABlocks * 34;
|
---|
101 | if (remainder == 0 && (nIMABlocks % sound.ChannelCount) == 0)
|
---|
102 | {
|
---|
103 | bool stepIndexAbove88 = false;
|
---|
104 | for (int ii = 0; ii < nIMABlocks; ii++)
|
---|
105 | {
|
---|
106 | Byte cc = sound.Data[ii * 34 + 1];
|
---|
107 | if ((cc & 0x7F) > 88)
|
---|
108 | stepIndexAbove88 = true;
|
---|
109 | }
|
---|
110 | if (stepIndexAbove88)
|
---|
111 | sound.IsIMA4 = false;
|
---|
112 | }
|
---|
113 | else
|
---|
114 | sound.IsIMA4 = false;
|
---|
115 | if (!(sound.IsIMA4)) // fill in default values of WAV (MS ADCPM) header
|
---|
116 | {
|
---|
117 | sound.IsPCM = false;
|
---|
118 | Console.WriteLine("PC-demo MS ADPCM detected; use -nodemo flag to treat as Mac IMA4.");
|
---|
119 | sound.AverageDataRate = (sound.ChannelCount == 1) ? 11155 : 22311;
|
---|
120 | sound.BlockAlignment = (sound.ChannelCount == 1) ? 512 : 1024;
|
---|
121 | sound.BitsPerSample = 4;
|
---|
122 | sound.SamplesPerBlock = 1012;
|
---|
123 | sound.HeaderSizeADPCM = 32;
|
---|
124 | sound.nCoefPairs = 7;
|
---|
125 | sound.CoefADPCMa = new Int16[7]; // usually 256 512 0 192 240 460 392
|
---|
126 | sound.CoefADPCMb = new Int16[7]; // usually 0 -256 0 64 0 -208 -232
|
---|
127 | sound.CoefADPCMa[0] = 256; sound.CoefADPCMb[0] = 0;
|
---|
128 | sound.CoefADPCMa[1] = 512; sound.CoefADPCMb[1] = -256;
|
---|
129 | sound.CoefADPCMa[2] = 0; sound.CoefADPCMb[2] = 0;
|
---|
130 | sound.CoefADPCMa[3] = 192; sound.CoefADPCMb[3] = 64;
|
---|
131 | sound.CoefADPCMa[4] = 240; sound.CoefADPCMb[4] = 0;
|
---|
132 | sound.CoefADPCMa[5] = 460; sound.CoefADPCMb[5] = -208;
|
---|
133 | sound.CoefADPCMa[6] = 392; sound.CoefADPCMb[6] = -232;
|
---|
134 | }
|
---|
135 | }
|
---|
136 | // validate data and calculate sample count
|
---|
137 | if(sound.IsIMA4) // get the sample count
|
---|
138 | {
|
---|
139 | int nIMABlocks = sound.Data.Length / 34 / sound.ChannelCount;
|
---|
140 | if(sound.Data.Length - nIMABlocks * 34 * sound.ChannelCount > 0)
|
---|
141 | throw new InvalidDataException("IMA4 data shouldn't have incomplete blocks.");
|
---|
142 | sound.SampleCount = nIMABlocks * 64;
|
---|
143 | }
|
---|
144 | else
|
---|
145 | {
|
---|
146 | // TODO: validate all the parameters: resolve conflicts if any, or bail out
|
---|
147 | // TODO: handle PCM (the following assumes MS ADPCM)
|
---|
148 | if (sound.IsPCM)
|
---|
149 | {
|
---|
150 | }
|
---|
151 | else
|
---|
152 | {
|
---|
153 | int wholeBlocks = sound.Data.Length / sound.BlockAlignment;
|
---|
154 | int leftoverBytes = sound.Data.Length - (wholeBlocks * sound.BlockAlignment);
|
---|
155 | int leftoverSamples = 8 * (leftoverBytes - 7 * sound.ChannelCount)
|
---|
156 | / sound.BitsPerSample / sound.ChannelCount + 2; // assuming 4 bits per sample
|
---|
157 | sound.SampleCount = wholeBlocks * sound.SamplesPerBlock + leftoverSamples;
|
---|
158 | }
|
---|
159 | }
|
---|
160 | return sound;
|
---|
161 | }
|
---|
162 | }
|
---|
163 | }
|
---|