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 |
} |