| 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 | //            Console.WriteLine("Sample count:"); | 
 
 
 
 
 | 161 | //            Console.WriteLine(sound.SampleCount); | 
 
 
 
 
 | 162 |  | 
 
 
 
 
 | 163 | return sound; | 
 
 
 
 
 | 164 | } | 
 
 
 
 
 | 165 | } | 
 
 
 
 
 | 166 | } |