source: OniSplit/Sound/SoundData.cs@ 1155

Last change on this file since 1155 was 1155, checked in by geyser, 4 years ago

Uploading WIP code for SoundData, because the previous commit relies on it after all.

File size: 7.5 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// Console.WriteLine("Sample count:");
161// Console.WriteLine(sound.SampleCount);
162
163 return sound;
164 }
165 }
166}
Note: See TracBrowser for help on using the repository browser.