Skip to content

Commit

Permalink
Implemented WestwoodCompression for AUD files.
Browse files Browse the repository at this point in the history
  • Loading branch information
IceReaper authored and pchote committed Jan 8, 2023
1 parent fb93281 commit 0b94a06
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 26 deletions.
2 changes: 1 addition & 1 deletion OpenRA.Mods.Cnc/AudioLoaders/AudLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ bool IsAud(Stream s)
var readFormat = s.ReadByte();
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)
Expand Down
74 changes: 67 additions & 7 deletions OpenRA.Mods.Cnc/FileFormats/AudReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,24 @@ enum SoundFormat
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 bool LoadSound(Stream s, out Func<Stream> result, out int sampleRate, out int sampleBits, out int channels, out float lengthInSeconds)
Expand All @@ -50,17 +68,25 @@ public static bool LoadSound(Stream s, out Func<Stream> result, out int sampleRa
if (!Enum.IsDefined(typeof(SoundFormat), readFormat))
return false;

if (readFormat == (int)SoundFormat.WestwoodCompressed)
throw new NotImplementedException();

var offsetPosition = s.Position;
var streamLength = s.Length;
var segmentLength = (int)(streamLength - offsetPosition);

result = () =>
{
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
Expand All @@ -71,7 +97,7 @@ public static bool LoadSound(Stream s, out Func<Stream> result, out int sampleRa
return true;
}

sealed class AudStream : ReadOnlyAdapterStream
sealed class ImaAdpcmAudStream : ReadOnlyAdapterStream
{
readonly int outputSize;
int dataSize;
Expand All @@ -80,7 +106,7 @@ sealed class AudStream : ReadOnlyAdapterStream
int baseOffset;
int index;

public AudStream(Stream stream, int outputSize, int dataSize)
public ImaAdpcmAudStream(Stream stream, int outputSize, int dataSize)
: base(stream)
{
this.outputSize = outputSize;
Expand All @@ -94,7 +120,7 @@ protected override bool BufferData(Stream baseStream, Queue<byte> data)
if (dataSize <= 0)
return true;

var chunk = ImaAdpcmChunk.Read(baseStream);
var chunk = AudChunk.Read(baseStream);
for (var n = 0; n < chunk.CompressedSize; n++)
{
var b = baseStream.ReadUInt8();
Expand All @@ -119,5 +145,39 @@ protected override bool BufferData(Stream baseStream, Queue<byte> data)
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;
}
}
}
}
18 changes: 0 additions & 18 deletions OpenRA.Mods.Common/FileFormats/ImaAdpcmReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,6 @@

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
{
static readonly int[] IndexAdjust = { -1, -1, -1, -1, 2, 4, 6, 8 };
Expand Down
86 changes: 86 additions & 0 deletions OpenRA.Mods.Common/FileFormats/WestwoodCompressedReader.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}
}

0 comments on commit 0b94a06

Please sign in to comment.