source: OniSplit/Sound/WavExporter.cs @ 1156

Last change on this file since 1156 was 1156, checked in by geyser, 14 months ago

Fixed importing of "fact" chunk, as well as an unhandled fatal when attempting to transcode IMA4 to MS ADPCM. Also added missing padding to SNDD template and fixed the duration calculation for WAV-to-SNDD.

File size: 18.7 KB
Line 
1using System;
2using System.IO;
3
4namespace Oni.Sound
5{
6    internal class WavExporter : SoundExporter
7    {
8        #region Private data
9        private bool convert_to_PCM;
10        private bool do_pc_demo_test;
11
12        private const int fcc_RIFF = 0x46464952;
13        private const int fcc_WAVE = 0x45564157;
14        private const int fcc_fmt = 0x20746d66;
15        private const int fcc_fact = 0x74636166;
16        private const int fcc_data = 0x61746164;
17
18        private static readonly byte[] formatTemplate_ADPCM = new byte[50]
19        {
20            0x02, 0,    // format ID (2 for ADPCM)
21            0, 0,       // ChannelCount (overwritten)
22            0x22, 0x56, 0, 0, // SampleRate (usually 22050, can be 44100)
23            0, 0, 0, 0, // average data rate (computed and overwritten)
24            0, 0x02,    // block alignment (default 512, can be 1024)
25            0x04, 0,    // bits per sample (always 4)
26            0x20, 0,    // size of extended ADPCM header block
27            0xf4, 0x03, // samples per block (usually 1012, can be 2036)
28            0x07, 0,    // standard ADPCM coefficient table (always the same)
29            0, 0x01, 0, 0,
30            0, 0x02, 0, 0xff,
31            0, 0, 0, 0,
32            0xc0, 0, 0x40, 0,
33            0xf0, 0, 0, 0,
34            0xcc, 0x01, 0x30, 0xff,
35            0x88, 0x01, 0x18, 0xff
36        };
37
38        private static readonly byte[] formatTemplate_PCM = new byte[16]
39        {
40            0x01, 0,    // format ID (1 for linear PCM)
41            0, 0,       // ChannelCount (overwritten)
42            0x22, 0x56, 0, 0, // SampleRate (usually 22050, can be 44100)
43            0, 0, 0, 0, // data rate in bytes/s (computed and overwritten)
44            0x02, 0,    // block size (2 bytes for mono, 4 for stereo)
45            0x10, 0     // bits per sample (always 16)
46        };
47
48        private static readonly byte[] factTemplate = new byte[4]
49        {
50            0, 0, 0, 0  // sample count (computed and overwritten)
51        };
52
53        private static readonly int[] ima_index_table = new int[16]
54        {
55           -1, -1, -1, -1, 2, 4, 6, 8,
56           -1, -1, -1, -1, 2, 4, 6, 8
57        };
58
59        private static readonly int[] ima_step_table = new int[89]
60        {
61            7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
62            19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
63            50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
64            130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
65            337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
66            876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
67            2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
68            5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
69            15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
70        };
71
72        private static readonly int[] msadpcm_adapt_table = new int[16]
73        {
74            230, 230, 230, 230, 307, 409, 512, 614,
75            768, 614, 512, 409, 307, 230, 230, 230
76        };
77
78        private static readonly int[] msadpcm_coeff_table1 = new int[7]
79        {
80            256, 512, 0, 192, 240, 460, 392
81        };
82
83        private static readonly int[] msadpcm_coeff_table2 = new int[7]
84        {
85            0, -256, 0, 64, 0, -208, -232
86        };
87        #endregion
88
89        public WavExporter(InstanceFileManager fileManager, string outputDirPath, bool convertToPCM = false, bool noDemo = false)
90            : base(fileManager, outputDirPath)
91        {
92            convert_to_PCM = convertToPCM;
93            do_pc_demo_test = !noDemo;
94        }
95
96        private static void ClampToRange(ref int value, int lower, int upper)
97        {
98            if (value > upper)
99                value = upper;
100            if (value < lower)
101                value = lower;
102        }
103
104        protected Int16 NibbletoSampleIMA4(ref int predictor, ref int step_index, Byte nibble)
105        {
106            int step = ima_step_table[step_index];
107
108            step_index += ima_index_table[nibble];
109            ClampToRange(ref step_index, 0, 88);
110
111            int diff = step >> 3;
112
113            if ((nibble & 0x04) != 0) diff +=  step;
114            if ((nibble & 0x02) != 0) diff += (step >> 1);
115            if ((nibble & 0x01) != 0) diff += (step >> 2);
116            if ((nibble & 0x08) != 0)
117                predictor -= diff;
118            else
119                predictor += diff;
120
121            ClampToRange(ref predictor, -32768, 32767);
122            return (Int16)predictor;
123        }
124
125        protected Int16 NibbletoSampleMSADPCM(ref Int16 sample1, ref Int16 sample2, ref UInt16 delta, Byte pred_index, Byte nibble)
126        {
127            int coeff1 = msadpcm_coeff_table1[pred_index];
128            int coeff2 = msadpcm_coeff_table2[pred_index];
129
130            int prediction = ((int)sample1 * (int)coeff1 + (int)sample2 * (int)coeff2) >> 8;
131
132            int snibble = (nibble < 8) ? nibble : (nibble - 16);
133            int correction = snibble * (int)delta;
134
135            int sample = prediction + correction;
136            ClampToRange(ref sample, -32768, 32767);
137
138            sample2 = sample1;
139            sample1 = (Int16)sample;
140
141            int newDelta = delta * msadpcm_adapt_table[nibble];
142            newDelta >>= 8;
143            ClampToRange(ref newDelta, 16, 65535);
144            delta = (UInt16)newDelta;
145
146            return (Int16)sample;
147        }
148
149        protected override void ExportInstance(InstanceDescriptor descriptor)
150        {
151            var sound = SoundData.Read(descriptor, do_pc_demo_test);
152
153            using (var stream = File.Create(Path.Combine(OutputDirPath, descriptor.FullName + ".wav")))
154            using (var writer = new BinaryWriter(stream))
155            {
156                int blockSizeADPCM, samplesPerBlock, sampleCount, paddingBytes = 0;
157                if (sound.IsIMA4) // IMA4 ADPCM format
158                {
159                    blockSizeADPCM = 34 * sound.ChannelCount;
160                    samplesPerBlock = 64;
161                    sampleCount = (sound.Data.Length / blockSizeADPCM) * samplesPerBlock;
162                }
163                else
164                {
165                    blockSizeADPCM = sound.BlockAlignment;
166                    int wholeBlocks = sound.Data.Length / blockSizeADPCM;
167                    int leftoverBytes = sound.Data.Length - (wholeBlocks * blockSizeADPCM);
168                    int leftoverSamples = 0;
169                    if (leftoverBytes > 7 * sound.ChannelCount)
170                        leftoverSamples = 2 + (leftoverBytes - 7 * sound.ChannelCount)
171                                        * 8 / sound.BitsPerSample / sound.ChannelCount;
172                    else
173                        Console.Error.WriteLine("Improper trailing bytes/samples!");
174                    if (leftoverBytes > 0) // incomplete trailing block
175                        paddingBytes = blockSizeADPCM - leftoverBytes;
176                    samplesPerBlock = 2 + (blockSizeADPCM - sound.ChannelCount * 7) * 8 / sound.ChannelCount / sound.BitsPerSample;
177                    sampleCount = wholeBlocks * samplesPerBlock + leftoverSamples;
178                }
179                if (!convert_to_PCM)
180                {
181                    if (sound.IsIMA4)
182                    {
183                        throw new NotSupportedException("Transcoding from IMA4 ADPCM (Mac) to MS ADPCM (PC) not supported! Please use -extract:pcm");
184                    }
185                    var format = (byte[])formatTemplate_ADPCM.Clone();
186                    var fact = (byte[])factTemplate.Clone(); // needed for ADPCM (to specify the actual sample count)
187
188                    var averageRate = sound.SampleRate * blockSizeADPCM / samplesPerBlock;
189                    Array.Copy(BitConverter.GetBytes(sound.ChannelCount), 0, format, 2, 2);
190                    Array.Copy(BitConverter.GetBytes(sound.SampleRate), 0, format, 4, 4);
191                    Array.Copy(BitConverter.GetBytes(averageRate), 0, format, 8, 4);
192                    Array.Copy(BitConverter.GetBytes(blockSizeADPCM), 0, format, 12, 2);
193                    Array.Copy(BitConverter.GetBytes(samplesPerBlock), 0, format, 18, 2);
194
195                    Array.Copy(BitConverter.GetBytes(sampleCount), 0, fact, 0, 4);
196
197                    writer.Write(fcc_RIFF);
198                    writer.Write(8 + format.Length + 8 + fact.Length + 8 + sound.Data.Length + paddingBytes);
199                    writer.Write(fcc_WAVE);
200
201                    //
202                    // write format chunk
203                    //
204                    writer.Write(fcc_fmt);
205                    writer.Write(format.Length);
206                    writer.Write(format);
207
208                    //
209                    // write fact chunk
210                    //
211                    writer.Write(fcc_fact);
212                    writer.Write(fact.Length);
213                    writer.Write(fact);
214
215                    //
216                    // write data chunk
217                    //
218                    writer.Write(fcc_data);
219                    writer.Write(sound.Data.Length + paddingBytes);
220                    writer.Write(sound.Data);
221
222                    Byte c = 0;
223                    for (int i = 0; i < paddingBytes; i++)
224                        writer.Write(c);
225                }
226                else
227                {
228                    var format = (byte[])formatTemplate_PCM.Clone();
229
230                    var blockSizePCM = 2 * sound.ChannelCount; // 16-bit samples or sample pairs
231                    samplesPerBlock = 2;
232                    var averageRate = sound.SampleRate * blockSizePCM / samplesPerBlock;
233                    Array.Copy(BitConverter.GetBytes(sound.ChannelCount), 0, format, 2, 2);
234                    Array.Copy(BitConverter.GetBytes(sound.SampleRate), 0, format, 4, 4);
235                    Array.Copy(BitConverter.GetBytes(averageRate), 0, format, 8, 4);
236                    Array.Copy(BitConverter.GetBytes(blockSizePCM), 0, format, 12, 2);
237
238                    int dataSize = blockSizePCM * sampleCount;
239
240                    writer.Write(fcc_RIFF);
241                    writer.Write(8 + format.Length + 8 + dataSize);
242                    writer.Write(fcc_WAVE);
243
244                    //
245                    // write format chunk
246                    //
247
248                    writer.Write(fcc_fmt);
249                    writer.Write(format.Length);
250                    writer.Write(format);
251
252                    //
253                    // write data chunk
254                    //
255                    var samplesL = new Int16[sampleCount];
256                    var samplesR = new Int16[sampleCount];
257                    if (sound.IsIMA4) // decode IMA4 into linear signed 16-bit PCM
258                    {
259                        int pos = 0;
260
261                        int iSampleL = 0;
262                        int predictorL = 0;
263                        int stepIndexL = 0;
264                        int iSampleR = 0;
265                        int predictorR = 0;
266                        int stepIndexR = 0;
267
268                        int nBlocks = sound.Data.Length / blockSizeADPCM;
269                        for (int block = 0; block < nBlocks; block++)
270                        {
271                            byte headerHiL = sound.Data[pos++];
272                            byte headerLoL = sound.Data[pos++];
273                            if (block == 0) // non-standard decoding: predictor initialization ignored after start
274                            {
275                                predictorL = ((((headerHiL << 1) | (headerLoL >> 7))) << 7);
276                                if (predictorL > 32767) predictorL -= 65536;
277                            }
278                            stepIndexL = headerLoL & 0x7f;
279                            if (stepIndexL > 88)
280                                Console.WriteLine("Block {0} (L): Initial IMA4 step index is {1}, clamping to 88.", block, stepIndexL);
281                            ClampToRange(ref stepIndexL, 0, 88);
282
283                            for (int b = 0; b < 32; b++)
284                            {
285                                Byte nibblesL = sound.Data[pos++];
286                                Byte nibbleHiL = (Byte)(nibblesL >> 4);
287                                Byte nibbleLoL = (Byte)(nibblesL & 0xF);
288
289                                samplesL[iSampleL++] = NibbletoSampleIMA4(ref predictorL, ref stepIndexL, nibbleLoL);
290                                samplesL[iSampleL++] = NibbletoSampleIMA4(ref predictorL, ref stepIndexL, nibbleHiL);
291                            }
292
293                            if (sound.ChannelCount == 2)
294                            {
295                                byte headerHiR = sound.Data[pos++];
296                                byte headerLoR = sound.Data[pos++];
297                                if (block == 0) // non-standard decoding: predictor initialization ignored after start
298                                {
299                                    predictorR = ((((headerHiR << 1) | (headerLoR >> 7))) << 7);
300                                    if (predictorR > 32767) predictorR -= 65536;
301                                }
302                                stepIndexR = headerLoR & 0x7f;
303                                if (stepIndexR > 88)
304                                    Console.WriteLine("Block {0} (R): Initial IMA4 step index is {1}, clamping to 88.", block, stepIndexR);
305                                ClampToRange(ref stepIndexR, 0, 88);
306
307                                for (int b = 0; b < 32; b++)
308                                {
309                                    Byte nibblesR = sound.Data[pos++];
310                                    Byte nibbleHiR = (Byte)(nibblesR >> 4);
311                                    Byte nibbleLoR = (Byte)(nibblesR & 0xF);
312
313                                    samplesR[iSampleR++] = NibbletoSampleIMA4(ref predictorR, ref stepIndexR, nibbleLoR);
314                                    samplesR[iSampleR++] = NibbletoSampleIMA4(ref predictorR, ref stepIndexR, nibbleHiR);
315                                }
316                            }
317                        }
318                    }
319                    else // decode MSADPCM into linear signed 16-bit PCM
320                    {
321                        int pos = 0;
322                        Byte pred_indexL = 0, pred_indexR = 0;
323                        UInt16 deltaL = 0, deltaR = 0;
324                        int iSampleL = 0;
325                        int iSampleR = 0;
326                        Int16 sample1L = 0, sample2L = 0;
327                        Int16 sample1R = 0, sample2R = 0;
328
329                        while (pos < sound.Data.Length)
330                        {
331                            if ((pos % blockSizeADPCM) == 0) // read block header
332                            {
333                                pred_indexL = sound.Data[pos++];
334                                if (sound.ChannelCount == 2)
335                                    pred_indexR = sound.Data[pos++];
336                                Byte deltaLo = sound.Data[pos++];
337                                Byte deltaHi = sound.Data[pos++];
338                                deltaL = (UInt16)(deltaLo + 256 * deltaHi);
339                                if (sound.ChannelCount == 2)
340                                {
341                                    deltaLo = sound.Data[pos++];
342                                    deltaHi = sound.Data[pos++];
343                                    deltaR = (UInt16)(deltaLo + 256 * deltaHi);
344                                }
345                                Byte sampleLo = sound.Data[pos++];
346                                Byte sampleHi = sound.Data[pos++];
347                                UInt16 usample = (UInt16)(sampleLo + 256 * sampleHi);
348                                sample1L = (Int16)((usample < 32767) ? usample : (usample - 65536));
349                                if (sound.ChannelCount == 2)
350                                {
351                                    sampleLo = sound.Data[pos++];
352                                    sampleHi = sound.Data[pos++];
353                                    usample = (UInt16)(sampleLo + 256 * sampleHi);
354                                    sample1R = (Int16)((usample < 32767) ? usample : (usample - 65536));
355                                }
356                                sampleLo = sound.Data[pos++];
357                                sampleHi = sound.Data[pos++];
358                                usample = (UInt16)(sampleLo + 256 * sampleHi);
359                                sample2L = (Int16)((usample < 32767) ? usample : (usample - 65536));
360                                if (sound.ChannelCount == 2)
361                                {
362                                    sampleLo = sound.Data[pos++];
363                                    sampleHi = sound.Data[pos++];
364                                    usample = (UInt16)(sampleLo + 256 * sampleHi);
365                                    sample2R = (Int16)((usample < 32767) ? usample : (usample - 65536));
366                                }
367                                samplesL[iSampleL++] = sample2L;
368                                samplesL[iSampleL++] = sample1L;
369                                if (sound.ChannelCount == 2)
370                                {
371                                    samplesR[iSampleR++] = sample2R;
372                                    samplesR[iSampleR++] = sample1R;
373                                }
374                            }
375                            else // read pair of nibbles
376                            {
377                                Byte nibbles = sound.Data[pos++];
378                                Byte nibbleHi = (Byte)(nibbles >> 4);
379                                Byte nibbleLo = (Byte)(nibbles & 0xF);
380                                samplesL[iSampleL++] = NibbletoSampleMSADPCM(ref sample1L, ref sample2L, ref deltaL, pred_indexL, nibbleHi);
381                                if (sound.ChannelCount == 2)
382                                    samplesR[iSampleR++] = NibbletoSampleMSADPCM(ref sample1R, ref sample2R, ref deltaR, pred_indexR, nibbleLo);
383                                else
384                                    samplesL[iSampleL++] = NibbletoSampleMSADPCM(ref sample1L, ref sample2L, ref deltaL, pred_indexL, nibbleLo);
385                            }
386                        }
387                    }
388                    writer.Write(fcc_data);
389                    writer.Write(dataSize);
390                    for (int smp = 0; smp < sampleCount; smp++)
391                    {
392                        writer.Write(samplesL[smp]);
393                        if(sound.ChannelCount == 2)
394                            writer.Write(samplesR[smp]);
395                    }
396                }
397            }
398        }
399    }
400}
Note: See TracBrowser for help on using the repository browser.