| 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) |
| 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); // ADPCM format identifiers (change to support PCM?) |
| 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 |
|
|
| 112 |
|
} |
| 113 |
|
else |
| 114 |
|
sound.IsIMA4 = false; |
| 115 |
< |
if (!(sound.IsIMA4)) |
| 116 |
< |
Console.WriteLine("PC-demo MS ADPCM detected; use -nodemo flag to treat as IMA4."); |
| 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 |
|
} |