ViewVC Help
View File | Revision Log | View Changeset | Root Listing
root/Oni2/OniSplit/Sound/SoundData.cs
(Generate patch)

Comparing OniSplit/Sound/SoundData.cs (file contents):
Revision 1114 by iritscen, Wed Jan 22 14:08:57 2020 UTC vs.
Revision 1155 by geyser, Mon May 3 15:26:34 2021 UTC

# Line 1 | Line 1
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 static SoundData Read(InstanceDescriptor sndd)
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");
# Line 22 | Line 41 | namespace Oni.Sound
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;
56 >                    sound.ChannelCount = (reader.ReadInt32() >> 1) + 1; // TODO: interpret the low bit? (uncompressed/compressed?)
57                      sound.SampleRate = 22050;
58 <                    reader.Skip(4);
58 >                    sound.nGameTicks = (UInt16)reader.ReadInt32();
59 >
60 >                    sound.IsIMA4 = true;
61 >                    sound.IsPCM = false;
62                  }
63                  else
64                  {
65 <                    reader.Skip(6);
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 <                    reader.Skip(44);
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();
# Line 43 | Line 94 | namespace Oni.Sound
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      }

Diff Legend

Removed lines
+ Added lines
< Changed lines (old)
> Changed lines (new)