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