[1114] | 1 | using System;
|
---|
| 2 | using System.Collections.Generic;
|
---|
| 3 | using System.Text;
|
---|
[1155] | 4 | using System.IO;
|
---|
[1114] | 5 |
|
---|
| 6 | namespace Oni.Sound
|
---|
| 7 | {
|
---|
| 8 | internal class SoundData
|
---|
| 9 | {
|
---|
[1155] | 10 | public bool IsPCM;
|
---|
[1130] | 11 | public bool IsIMA4;
|
---|
[1114] | 12 | public int SampleRate;
|
---|
| 13 | public int ChannelCount;
|
---|
| 14 | public byte[] Data;
|
---|
| 15 |
|
---|
[1155] | 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 |
|
---|
[1131] | 32 | public static SoundData Read(InstanceDescriptor sndd, bool do_pc_demo_test)
|
---|
[1114] | 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 | {
|
---|
[1155] | 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
|
---|
[1114] | 54 | if (sndd.IsMacFile)
|
---|
| 55 | {
|
---|
[1155] | 56 | sound.ChannelCount = (reader.ReadInt32() >> 1) + 1; // TODO: interpret the low bit? (uncompressed/compressed?)
|
---|
[1114] | 57 | sound.SampleRate = 22050;
|
---|
[1155] | 58 | sound.nGameTicks = (UInt16)reader.ReadInt32();
|
---|
| 59 |
|
---|
[1130] | 60 | sound.IsIMA4 = true;
|
---|
[1155] | 61 | sound.IsPCM = false;
|
---|
[1114] | 62 | }
|
---|
| 63 | else
|
---|
| 64 | {
|
---|
[1155] | 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?)
|
---|
[1114] | 68 | sound.ChannelCount = reader.ReadInt16();
|
---|
| 69 | sound.SampleRate = reader.ReadInt32();
|
---|
[1155] | 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)
|
---|
[1130] | 87 | sound.IsIMA4 = false;
|
---|
[1114] | 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 |
|
---|
[1131] | 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;
|
---|
[1155] | 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 | }
|
---|
[1131] | 135 | }
|
---|
[1155] | 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 | }
|
---|
[1114] | 160 | return sound;
|
---|
| 161 | }
|
---|
| 162 | }
|
---|
| 163 | }
|
---|