source: OniSplit/Sound/SoundData.cs@ 1186

Last change on this file since 1186 was 1156, checked in by geyser, 4 years 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: 7.4 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.Text;
4using System.IO;
5
6namespace 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}
Note: See TracBrowser for help on using the repository browser.