Implemented WestwoodCompression for AUD files.
This commit is contained in:
@@ -24,7 +24,7 @@ namespace OpenRA.Mods.Cnc.AudioLoaders
|
|||||||
var readFormat = s.ReadByte();
|
var readFormat = s.ReadByte();
|
||||||
s.Position = start;
|
s.Position = start;
|
||||||
|
|
||||||
return readFormat == (int)SoundFormat.ImaAdpcm;
|
return readFormat == (int)SoundFormat.ImaAdpcm || readFormat == (int)SoundFormat.WestwoodCompressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ISoundLoader.TryParseSound(Stream stream, out ISoundFormat sound)
|
bool ISoundLoader.TryParseSound(Stream stream, out ISoundFormat sound)
|
||||||
|
|||||||
@@ -30,6 +30,24 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
|||||||
ImaAdpcm = 99,
|
ImaAdpcm = 99,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct AudChunk
|
||||||
|
{
|
||||||
|
public int CompressedSize;
|
||||||
|
public int OutputSize;
|
||||||
|
|
||||||
|
public static AudChunk Read(Stream s)
|
||||||
|
{
|
||||||
|
AudChunk c;
|
||||||
|
c.CompressedSize = s.ReadUInt16();
|
||||||
|
c.OutputSize = s.ReadUInt16();
|
||||||
|
|
||||||
|
if (s.ReadUInt32() != 0xdeaf)
|
||||||
|
throw new InvalidDataException("Chunk header is bogus");
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class AudReader
|
public static class AudReader
|
||||||
{
|
{
|
||||||
public static bool LoadSound(Stream s, out Func<Stream> result, out int sampleRate, out int sampleBits, out int channels, out float lengthInSeconds)
|
public static bool LoadSound(Stream s, out Func<Stream> result, out int sampleRate, out int sampleBits, out int channels, out float lengthInSeconds)
|
||||||
@@ -50,9 +68,6 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
|||||||
if (!Enum.IsDefined(typeof(SoundFormat), readFormat))
|
if (!Enum.IsDefined(typeof(SoundFormat), readFormat))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (readFormat == (int)SoundFormat.WestwoodCompressed)
|
|
||||||
throw new NotImplementedException();
|
|
||||||
|
|
||||||
var offsetPosition = s.Position;
|
var offsetPosition = s.Position;
|
||||||
var streamLength = s.Length;
|
var streamLength = s.Length;
|
||||||
var segmentLength = (int)(streamLength - offsetPosition);
|
var segmentLength = (int)(streamLength - offsetPosition);
|
||||||
@@ -60,7 +75,18 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
|||||||
result = () =>
|
result = () =>
|
||||||
{
|
{
|
||||||
var audioStream = SegmentStream.CreateWithoutOwningStream(s, offsetPosition, segmentLength);
|
var audioStream = SegmentStream.CreateWithoutOwningStream(s, offsetPosition, segmentLength);
|
||||||
return new AudStream(audioStream, outputSize, dataSize);
|
|
||||||
|
switch (readFormat)
|
||||||
|
{
|
||||||
|
case (int)SoundFormat.ImaAdpcm:
|
||||||
|
return new ImaAdpcmAudStream(audioStream, outputSize, dataSize);
|
||||||
|
|
||||||
|
case (int)SoundFormat.WestwoodCompressed:
|
||||||
|
return new WestwoodCompressedAudStream(audioStream, outputSize, dataSize);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -71,7 +97,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class AudStream : ReadOnlyAdapterStream
|
sealed class ImaAdpcmAudStream : ReadOnlyAdapterStream
|
||||||
{
|
{
|
||||||
readonly int outputSize;
|
readonly int outputSize;
|
||||||
int dataSize;
|
int dataSize;
|
||||||
@@ -80,7 +106,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
|||||||
int baseOffset;
|
int baseOffset;
|
||||||
int index;
|
int index;
|
||||||
|
|
||||||
public AudStream(Stream stream, int outputSize, int dataSize)
|
public ImaAdpcmAudStream(Stream stream, int outputSize, int dataSize)
|
||||||
: base(stream)
|
: base(stream)
|
||||||
{
|
{
|
||||||
this.outputSize = outputSize;
|
this.outputSize = outputSize;
|
||||||
@@ -94,7 +120,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
|||||||
if (dataSize <= 0)
|
if (dataSize <= 0)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
var chunk = ImaAdpcmChunk.Read(baseStream);
|
var chunk = AudChunk.Read(baseStream);
|
||||||
for (var n = 0; n < chunk.CompressedSize; n++)
|
for (var n = 0; n < chunk.CompressedSize; n++)
|
||||||
{
|
{
|
||||||
var b = baseStream.ReadUInt8();
|
var b = baseStream.ReadUInt8();
|
||||||
@@ -119,5 +145,39 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
|||||||
return dataSize <= 0;
|
return dataSize <= 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed class WestwoodCompressedAudStream : ReadOnlyAdapterStream
|
||||||
|
{
|
||||||
|
readonly int outputSize;
|
||||||
|
int dataSize;
|
||||||
|
|
||||||
|
public WestwoodCompressedAudStream(Stream stream, int outputSize, int dataSize)
|
||||||
|
: base(stream)
|
||||||
|
{
|
||||||
|
this.outputSize = outputSize;
|
||||||
|
this.dataSize = dataSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Length => outputSize;
|
||||||
|
|
||||||
|
protected override bool BufferData(Stream baseStream, Queue<byte> data)
|
||||||
|
{
|
||||||
|
if (dataSize <= 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var chunk = AudChunk.Read(baseStream);
|
||||||
|
|
||||||
|
var input = baseStream.ReadBytes(chunk.CompressedSize);
|
||||||
|
var output = new byte[chunk.OutputSize];
|
||||||
|
WestwoodCompressedReader.DecodeWestwoodCompressedSample(input, output);
|
||||||
|
|
||||||
|
foreach (var b in output)
|
||||||
|
data.Enqueue(b);
|
||||||
|
|
||||||
|
dataSize -= 8 + chunk.CompressedSize;
|
||||||
|
|
||||||
|
return dataSize <= 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,24 +13,6 @@ using System.IO;
|
|||||||
|
|
||||||
namespace OpenRA.Mods.Common.FileFormats
|
namespace OpenRA.Mods.Common.FileFormats
|
||||||
{
|
{
|
||||||
public struct ImaAdpcmChunk
|
|
||||||
{
|
|
||||||
public int CompressedSize;
|
|
||||||
public int OutputSize;
|
|
||||||
|
|
||||||
public static ImaAdpcmChunk Read(Stream s)
|
|
||||||
{
|
|
||||||
ImaAdpcmChunk c;
|
|
||||||
c.CompressedSize = s.ReadUInt16();
|
|
||||||
c.OutputSize = s.ReadUInt16();
|
|
||||||
|
|
||||||
if (s.ReadUInt32() != 0xdeaf)
|
|
||||||
throw new InvalidDataException("Chunk header is bogus");
|
|
||||||
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ImaAdpcmReader
|
public class ImaAdpcmReader
|
||||||
{
|
{
|
||||||
static readonly int[] IndexAdjust = { -1, -1, -1, -1, 2, 4, 6, 8 };
|
static readonly int[] IndexAdjust = { -1, -1, -1, -1, 2, 4, 6, 8 };
|
||||||
|
|||||||
86
OpenRA.Mods.Common/FileFormats/WestwoodCompressedReader.cs
Normal file
86
OpenRA.Mods.Common/FileFormats/WestwoodCompressedReader.cs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
#region Copyright & License Information
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2007-2022 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;
|
||||||
|
|
||||||
|
namespace OpenRA.Mods.Common.FileFormats
|
||||||
|
{
|
||||||
|
public class WestwoodCompressedReader
|
||||||
|
{
|
||||||
|
static readonly int[] AudWsStepTable2 = { -2, -1, 0, 1 };
|
||||||
|
static readonly int[] AudWsStepTable4 = { -9, -8, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 8 };
|
||||||
|
|
||||||
|
public static void DecodeWestwoodCompressedSample(byte[] input, byte[] output)
|
||||||
|
{
|
||||||
|
if (input.Length == output.Length)
|
||||||
|
{
|
||||||
|
Array.Copy(input, output, output.Length);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sample = 0x80;
|
||||||
|
var r = 0;
|
||||||
|
var w = 0;
|
||||||
|
|
||||||
|
while (r < input.Length)
|
||||||
|
{
|
||||||
|
var count = input[r++] & 0x3f;
|
||||||
|
|
||||||
|
switch (input[r - 1] >> 6)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
for (count++; count > 0; count--)
|
||||||
|
{
|
||||||
|
var code = input[r++];
|
||||||
|
output[w++] = (byte)(sample = (sample + AudWsStepTable2[(code >> 0) & 0x03]).Clamp(byte.MinValue, byte.MaxValue));
|
||||||
|
output[w++] = (byte)(sample = (sample + AudWsStepTable2[(code >> 2) & 0x03]).Clamp(byte.MinValue, byte.MaxValue));
|
||||||
|
output[w++] = (byte)(sample = (sample + AudWsStepTable2[(code >> 4) & 0x03]).Clamp(byte.MinValue, byte.MaxValue));
|
||||||
|
output[w++] = (byte)(sample = (sample + AudWsStepTable2[(code >> 6) & 0x03]).Clamp(byte.MinValue, byte.MaxValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
for (count++; count > 0; count--)
|
||||||
|
{
|
||||||
|
var code = input[r++];
|
||||||
|
output[w++] = (byte)(sample = (sample + AudWsStepTable4[(code >> 0) & 0x0f]).Clamp(byte.MinValue, byte.MaxValue));
|
||||||
|
output[w++] = (byte)(sample = (sample + AudWsStepTable4[(code >> 4) & 0xff]).Clamp(byte.MinValue, byte.MaxValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2 when (count & 0x20) != 0:
|
||||||
|
output[w++] = (byte)(sample += (sbyte)((sbyte)count << 3) >> 3);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
for (count++; count > 0; count--)
|
||||||
|
output[w++] = input[r++];
|
||||||
|
|
||||||
|
sample = input[r - 1];
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
for (count++; count > 0; count--)
|
||||||
|
output[w++] = (byte)sample;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user