using System;
using System.IO;
// This file is part of the ANX.Framework and originally taken from
// the AC.AL OpenAL library, released under the MIT License.
// For details see: http://acal.codeplex.com/license
namespace WaveUtils
{
///
/// http://wiki.multimedia.cx/index.php?title=Microsoft_ADPCM
///
/// http://dslinux.gits.kiev.ua/trunk/lib/audiofile/src/libaudiofile/modules/msadpcm.c
///
/// http://netghost.narod.ru/gff/vendspec/micriff/ms_riff.txt
///
internal static class MsAdpcm
{
#region Decoding Tables
private static readonly int[] AdaptationTable =
{
230, 230, 230, 230, 307, 409, 512, 614,
768, 614, 512, 409, 307, 230, 230, 230
};
private static readonly int[] AdaptCoeff1 =
{
256, 512, 0, 192, 240, 460, 392
};
private static readonly int[] AdaptCoeff2 =
{
0, -256, 0, 64, 0, -208, -232
};
#endregion
#region StateObject (helper class)
private class StateObject
{
///
/// Indices in the aCoef array to define the predictor
/// used to encode this block.
///
public byte predicator;
///
/// Initial Delta value to use.
///
public short delta;
///
/// The second sample value of the block. When decoding this will be
/// used as the previous sample to start decoding with.
///
public short sample1;
///
/// The first sample value of the block. When decoding this will be
/// used as the previous' previous sample to start decoding with.
///
public short sample2;
}
#endregion
#region ConvertToPcm
public static void ConvertToPcm(WaveInfo info)
{
BinaryReader reader = new BinaryReader(new MemoryStream(info.Data));
MemoryStream result = new MemoryStream();
BinaryWriter writer = new BinaryWriter(result);
StateObject first = new StateObject();
StateObject second = info.Channels > 1 ? new StateObject() : first;
StateObject[] states = { first, second };
int numSamples = (info.ExtSamplesPerBlock - 2) * info.Channels;
int numberOfBlocks = info.Data.Length / info.BlockAlign;
for (int blockIndex = 0; blockIndex < numberOfBlocks; blockIndex++)
{
for (int index = 0; index < info.Channels; index++)
{
states[index].predicator = reader.ReadByte();
}
for (int index = 0; index < info.Channels; index++)
{
states[index].delta = reader.ReadInt16();
}
for (int index = 0; index < info.Channels; index++)
{
states[index].sample1 = reader.ReadInt16();
}
for (int index = 0; index < info.Channels; index++)
{
states[index].sample2 = reader.ReadInt16();
// Write first samples directly from preamble
writer.Write(states[index].sample2);
}
// Write first samples directly from preamble
for (int index = 0; index < info.Channels; index++)
{
writer.Write(states[index].sample1);
}
// We decode the samples two at a time
for (int index = 0; index < numSamples; index += 2)
{
byte code = reader.ReadByte();
DecodeSample(first, code >> 4, writer);
DecodeSample(second, code & 0x0f, writer);
}
}
reader.Close();
writer.Close();
info.Data = result.ToArray();
result.Dispose();
}
#endregion
#region DecodeSample
private static void DecodeSample(StateObject state, int code,
BinaryWriter writer)
{
int linearSample =
((state.sample1 * AdaptCoeff1[state.predicator]) +
(state.sample2 * AdaptCoeff2[state.predicator])) / 256;
linearSample = linearSample + (state.delta *
((code & 0x08) == 0x08 ? (code - 0x10) : code));
state.sample2 = state.sample1;
// clamp predictor within signed 16-bit range
state.sample1 = (short)Math.Min(short.MaxValue,
Math.Max(short.MinValue, linearSample));
state.delta = (short)Math.Max(
(state.delta * AdaptationTable[code]) / 256, 16);
writer.Write(state.sample1);
}
#endregion
}
}