context: #region Copyright & License Information
  /*
   * Copyright 2007-2021 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, either version 3 of
   * the License, or (at your option) any later version. For more
   * information, see COPYING.
   */
  #endregion
  
  using System;
  using System.Collections.Generic;
  using System.IO;
  using OpenRA.Primitives;
  
  namespace OpenRA.Mods.Common.FileFormats
  {
  	public static class WavReader
  	{
  		enum WaveType { Pcm = 0x1, MsAdpcm = 0x2, ImaAdpcm = 0x11 }
  
  		public static bool LoadSound(Stream s, out Func<Stream> result, out short channels, out int sampleBits, out int sampleRate, out float lengthInSeconds)
  		{
  			result = null;
  			channels = -1;
  			sampleBits = -1;
  			sampleRate = -1;
  			lengthInSeconds = -1;
  
  			var type = s.ReadASCII(4);
  			if (type != "RIFF")
  				return false;
  
  			s.ReadInt32(); // File-size
  			var format = s.ReadASCII(4);
  			if (format != "WAVE")
  				return false;
  
  			WaveType audioType = 0;
  			var dataOffset = -1L;
  			var dataSize = -1;
  			var uncompressedSize = -1;
  			short blockAlign = -1;
  			while (s.Position < s.Length)
  			{
  				if ((s.Position & 1) == 1)
  					s.ReadByte(); // Alignment
  
  				if (s.Position == s.Length)
  					break; // Break if we aligned with end of stream
  
  				var blockType = s.ReadASCII(4);
  				switch (blockType)
  				{
  					case "fmt ":
  						var fmtChunkSize = s.ReadInt32();
  						var audioFormat = s.ReadInt16();
  						audioType = (WaveType)audioFormat;
  
  						if (!Enum.IsDefined(typeof(WaveType), audioType))
  							throw new NotSupportedException($"Compression type {audioFormat} is not supported.");
  
  						channels = s.ReadInt16();
  						sampleRate = s.ReadInt32();
  						s.ReadInt32(); // Byte Rate
  						blockAlign = s.ReadInt16();
  						sampleBits = s.ReadInt16();
  						lengthInSeconds = (float)(s.Length * 8) / (channels * sampleRate * sampleBits);
  						s.ReadBytes(fmtChunkSize - 16);
  						break;
  					case "fact":
  						var chunkSize = s.ReadInt32();
  						uncompressedSize = s.ReadInt32();
  						s.ReadBytes(chunkSize - 4);
  						break;
  					case "data":
  						dataSize = s.ReadInt32();
  						dataOffset = s.Position;
  						s.Position += dataSize;
  						break;
  					case "LIST":
  					case "cue ":
  						var listCueChunkSize = s.ReadInt32();
  						s.ReadBytes(listCueChunkSize);
  						break;
  					default:
  						s.Position = s.Length; // Skip to end of stream
  						break;
  				}
  			}
  
  			// sampleBits refers to the output bitrate, which is always 16 for adpcm.
  			if (audioType != WaveType.Pcm)
  				sampleBits = 16;
  
  			var chan = channels;
  			result = () =>
  			{
  				var audioStream = SegmentStream.CreateWithoutOwningStream(s, dataOffset, dataSize);
  				if (audioType == WaveType.ImaAdpcm)
  					return new WavStreamImaAdpcm(audioStream, dataSize, blockAlign, chan, uncompressedSize);
  				if (audioType == WaveType.MsAdpcm)
  					return new WavStreamMsAdpcm(audioStream, dataSize, blockAlign, chan);
  
  				return audioStream; // Data is already PCM format.
  			};
  
  			return true;
  		}
  
  		sealed class WavStreamImaAdpcm : ReadOnlyAdapterStream
  		{
  			readonly short channels;
  			readonly int numBlocks;
  			readonly int blockDataSize;
  			readonly int outputSize;
  			readonly int[] predictor;
  			readonly int[] index;
  
  			readonly byte[] interleaveBuffer;
  			int outOffset;
  			int currentBlock;
  
  			public WavStreamImaAdpcm(Stream stream, int dataSize, short blockAlign, short channels, int uncompressedSize)
  				: base(stream)
  			{
  				this.channels = channels;
  				numBlocks = dataSize / blockAlign;
  				blockDataSize = blockAlign - (channels * 4);
  				outputSize = uncompressedSize * channels * 2;
  				predictor = new int[channels];
  				index = new int[channels];
  
  				interleaveBuffer = new byte[channels * 16];
  			}
  
  			protected override bool BufferData(Stream baseStream, Queue<byte> data)
  			{
  				// Decode each block of IMA ADPCM data
  				// Each block starts with a initial state per-channel
  				for (var c = 0; c < channels; c++)
  				{
  					predictor[c] = baseStream.ReadInt16();
  					index[c] = baseStream.ReadUInt8();
  					baseStream.ReadUInt8(); // Unknown/Reserved
  
  					// Output first sample from input
  					data.Enqueue((byte)predictor[c]);
  					data.Enqueue((byte)(predictor[c] >> 8));
  					outOffset += 2;
  
  					if (outOffset >= outputSize)
  						return true;
  				}
  
  				// 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 = baseStream.ReadBytes(4);
  						var decoded = ImaAdpcmReader.LoadImaAdpcmSound(chunk, ref index[c], ref predictor[c]);
  
  						// Interleave output, one sample per channel
  						var interleaveChannelOffset = 2 * c;
  						for (var i = 0; i < decoded.Length; i += 2)
  						{
  							var interleaveSampleOffset = interleaveChannelOffset + i;
  							interleaveBuffer[interleaveSampleOffset] = decoded[i];
  							interleaveBuffer[interleaveSampleOffset + 1] = decoded[i + 1];
  							interleaveChannelOffset += 2 * (channels - 1);
  						}
  
  						blockOffset += 4;
  					}
  
  					var outputRemaining = outputSize - outOffset;
  					var toCopy = Math.Min(outputRemaining, interleaveBuffer.Length);
  					for (var i = 0; i < toCopy; i++)
  						data.Enqueue(interleaveBuffer[i]);
  
  					outOffset += 16 * channels;
  
  					if (outOffset >= outputSize)
  						return true;
  				}
  
  				return ++currentBlock >= numBlocks;
  			}
  		}
  
  		// Format docs https://wiki.multimedia.cx/index.php/Microsoft_ADPCM
  		public sealed class WavStreamMsAdpcm : ReadOnlyAdapterStream
  		{
  			static readonly int[] AdaptationTable =
  			{
  				230, 230, 230, 230, 307, 409, 512, 614,
  				768, 614, 512, 409, 307, 230, 230, 230
  			};
  
  			static readonly int[] AdaptCoeff1 = { 256, 512, 0, 192, 240, 460, 392 };
  
  			static readonly int[] AdaptCoeff2 = { 0, -256, 0, 64, 0, -208, -232 };
  
  			readonly short channels;
  			readonly int blockDataSize;
  			readonly int numBlocks;
  
  			int currentBlock;
  
  			public WavStreamMsAdpcm(Stream stream, int dataSize, short blockAlign, short channels)
  				: base(stream)
  			{
  				this.channels = channels;
  				blockDataSize = blockAlign - channels * 7;
  				numBlocks = dataSize / blockAlign;
  			}
  
  			protected override bool BufferData(Stream baseStream, Queue<byte> data)
  			{
  				var bpred = new byte[channels];
  				var chanIdelta = new short[channels];
  
  				var s1 = new short[channels];
  				var s2 = new short[channels];
  
  				for (var c = 0; c < channels; c++)
  					bpred[c] = baseStream.ReadUInt8();
  
  				for (var c = 0; c < channels; c++)
  
gt: 					chanIdelta[c] = baseStream.ReadInt16();