#region Copyright & License Information /* * Copyright 2007-2014 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation. For more information, * see COPYING. */ #endregion using System; using System.IO; namespace OpenRA.FileFormats { public class WavLoader { public readonly int FileSize; public readonly string Format; public readonly int FmtChunkSize; public readonly int AudioFormat; public readonly int Channels; public readonly int SampleRate; public readonly int ByteRate; public readonly int BlockAlign; public readonly int BitsPerSample; public readonly int UncompressedSize; public readonly int DataSize; public readonly byte[] RawOutput; public enum WaveType { Pcm = 0x1, ImaAdpcm = 0x11 }; public static WaveType Type { get; private set; } public WavLoader(Stream s) { while (s.Position < s.Length) { if ((s.Position & 1) == 1) s.ReadByte(); // Alignment var type = s.ReadASCII(4); switch (type) { case "RIFF": FileSize = s.ReadInt32(); Format = s.ReadASCII(4); if (Format != "WAVE") throw new NotSupportedException("Not a canonical WAVE file."); break; case "fmt ": FmtChunkSize = s.ReadInt32(); AudioFormat = s.ReadInt16(); Type = (WaveType)AudioFormat; if (Type != WaveType.Pcm && Type != WaveType.ImaAdpcm) throw new NotSupportedException("Compression type is not supported."); Channels = s.ReadInt16(); SampleRate = s.ReadInt32(); ByteRate = s.ReadInt32(); BlockAlign = s.ReadInt16(); BitsPerSample = s.ReadInt16(); s.ReadBytes(FmtChunkSize - 16); break; case "fact": { var chunkSize = s.ReadInt32(); UncompressedSize = s.ReadInt32(); s.ReadBytes(chunkSize - 4); } break; case "data": DataSize = s.ReadInt32(); RawOutput = s.ReadBytes(DataSize); break; default: // Ignore unknown chunks { var chunkSize = s.ReadInt32(); s.ReadBytes(chunkSize); } break; } } if (Type == WaveType.ImaAdpcm) { RawOutput = DecodeImaAdpcmData(); BitsPerSample = 16; } } public static float WaveLength(Stream s) { s.Position = 12; var fmt = s.ReadASCII(4); if (fmt != "fmt ") return 0; s.Position = 22; var channels = s.ReadInt16(); var sampleRate = s.ReadInt32(); s.Position = 34; var bitsPerSample = s.ReadInt16(); var length = s.Length * 8; return length / (channels * sampleRate * bitsPerSample); } public byte[] DecodeImaAdpcmData() { var s = new MemoryStream(RawOutput); var numBlocks = DataSize / BlockAlign; var blockDataSize = BlockAlign - (Channels * 4); var outputSize = UncompressedSize * Channels * 2; var outOffset = 0; var output = new byte[outputSize]; var predictor = new int[Channels]; var index = new int[Channels]; // Decode each block of IMA ADPCM data in RawOutput for (var block = 0; block < numBlocks; block++) { // Each block starts with a initial state per-channel for (var c = 0; c < Channels; c++) { predictor[c] = s.ReadInt16(); index[c] = s.ReadUInt8(); /* unknown/reserved */ s.ReadUInt8(); // Output first sample from input output[outOffset++] = (byte)predictor[c]; output[outOffset++] = (byte)(predictor[c] >> 8); if (outOffset >= outputSize) return output; } // Decode and output remaining data in this block var blockOffset = 0; while (blockOffset < blockDataSize) { for (var c = 0; c < Channels; c++) { // Decode 4 bytes (to 16 bytes of output) per channel var chunk = s.ReadBytes(4); var decoded = ImaAdpcmLoader.LoadImaAdpcmSound(chunk, ref index[c], ref predictor[c]); // Interleave output, one sample per channel var outOffsetChannel = outOffset + (2 * c); for (var i = 0; i < decoded.Length; i += 2) { var outOffsetSample = outOffsetChannel + i; if (outOffsetSample >= outputSize) return output; output[outOffsetSample] = decoded[i]; output[outOffsetSample + 1] = decoded[i + 1]; outOffsetChannel += 2 * (Channels - 1); } blockOffset += 4; } outOffset += 16 * Channels; } } return output; } } }