Skip to content

Commit

Permalink
Add ATRAC9 decoder
Browse files Browse the repository at this point in the history
  • Loading branch information
Thealexbarney committed Dec 10, 2017
1 parent 5b1d153 commit 6a4062c
Show file tree
Hide file tree
Showing 31 changed files with 2,507 additions and 12 deletions.
67 changes: 67 additions & 0 deletions src/VGAudio.Atrac9/Codecs/Atrac9/Atrac9Config.cs
@@ -0,0 +1,67 @@
using System.IO;
using VGAudio.Utilities;

namespace VGAudio.Codecs.Atrac9
{
public class Atrac9Config
{
public int ChannelCount { get; }
public int SampleRate { get; }
public int SuperframeSize { get; }
public byte[] ConfigData { get; }

public int SampleRateIndex { get; }
public int FrameSamplesPower { get; }
public int FrameSamples { get; }
public int ChannelConfigIndex { get; }
public int FrameSize { get; }
public int SuperframeIndex { get; }
public int SuperframeSamples { get; }
public int FramesPerSuperframe { get; }
public bool HighSampleRate { get; }
public ChannelConfig ChannelConfig { get; }

public Atrac9Config(byte[] configData)
{
if (configData == null || configData.Length != 4)
{
throw new InvalidDataException("Config data must be 4 bytes long");
}

ReadConfigData(configData, out int a, out int b, out int c, out int d);
SampleRateIndex = a;
ChannelConfigIndex = b;
FrameSize = c;
SuperframeIndex = d;
ConfigData = configData;

FramesPerSuperframe = 1 << SuperframeIndex;
SuperframeSize = FrameSize << SuperframeIndex;
ChannelConfig = Tables.ChannelConfig[ChannelConfigIndex];

ChannelCount = ChannelConfig.ChannelCount;
SampleRate = Tables.SampleRates[SampleRateIndex];
HighSampleRate = SampleRateIndex > 7;
FrameSamplesPower = Tables.SamplingRateIndexToFrameSamplesPower[SampleRateIndex];
FrameSamples = 1 << FrameSamplesPower;
SuperframeSamples = FrameSamples * FramesPerSuperframe;
}

private static void ReadConfigData(byte[] configData, out int sampleRateIndex, out int channelConfigIndex, out int frameSize, out int superframeIndex)
{
var reader = new BitReader(configData);

int header = reader.ReadInt(8);
sampleRateIndex = reader.ReadInt(4);
channelConfigIndex = reader.ReadInt(3);
int validationBit = reader.ReadInt(1);
frameSize = reader.ReadInt(11) + 1;
superframeIndex = reader.ReadInt(2);

if (header != 0xFE || validationBit != 0)
{
throw new InvalidDataException("ATRAC9 Config Data is invalid");
}
}
}
}
97 changes: 97 additions & 0 deletions src/VGAudio.Atrac9/Codecs/Atrac9/Atrac9Decoder.cs
@@ -0,0 +1,97 @@
using System;
using VGAudio.Utilities;

namespace VGAudio.Codecs.Atrac9
{
public class Atrac9Decoder
{
public Atrac9Config Config { get; private set; }

private Frame Frame { get; set; }
private BitReader Reader { get; set; }

public void Initialize(byte[] configData)
{
Config = new Atrac9Config(configData);
Frame = new Frame(Config);
Reader = new BitReader(null);
}

public void Decode(byte[] atrac9Data, short[][] pcmOut)
{
ValidateBufferLength(pcmOut);
Reader.SetBuffer(atrac9Data);
DecodeSuperFrame(pcmOut);
}

private void ValidateBufferLength(short[][] buffer)
{
if (buffer == null || buffer.Length < Config.ChannelCount)
{
throw new ArgumentException("PCM buffer is too small");
}

for (int i = 0; i < Config.ChannelCount; i++)
{
if (buffer[i]?.Length < Config.SuperframeSamples)
{
throw new ArgumentException("PCM buffer is too small");
}
}
}

private void DecodeSuperFrame(short[][] pcmOut)
{
for (int i = 0; i < Config.FramesPerSuperframe; i++)
{
Frame.FrameIndex = i;
DecodeFrame(Reader, Frame);
PcmFloatToShort(pcmOut, i * Config.FrameSamples);
Reader.AlignPosition(8);
}
}

private void PcmFloatToShort(short[][] pcmOut, int start)
{
int endSample = start + Config.FrameSamples;
int channelNum = 0;
foreach (Block block in Frame.Blocks)
{
foreach (Channel channel in block.Channels)
{
double[] pcmSrc = channel.Pcm;
short[] pcmDest = pcmOut[channelNum++];
for (int d = 0, s = start; s < endSample; d++, s++)
{
double sample = pcmSrc[d];
// Not using Math.Round because it's ~20x slower on 64-bit
int roundedSample = (int)Math.Floor(sample + 0.5);
pcmDest[s] = Helpers.Clamp16(roundedSample);
}
}
}
}

private static void DecodeFrame(BitReader reader, Frame frame)
{
Unpack.UnpackFrame(reader, frame);

foreach (Block block in frame.Blocks)
{
Quantization.DequantizeSpectra(block);
Stereo.ApplyIntensityStereo(block);
Quantization.ScaleSpectrum(block);
BandExtension.ApplyBandExtension(block);
ImdctBlock(block);
}
}

private static void ImdctBlock(Block block)
{
foreach (Channel channel in block.Channels)
{
channel.Mdct.RunImdct(channel.Spectra, channel.Pcm);
}
}
}
}
9 changes: 9 additions & 0 deletions src/VGAudio.Atrac9/Codecs/Atrac9/Atrac9Parameters.cs
@@ -0,0 +1,9 @@

namespace VGAudio.Codecs.Atrac9
{
public class Atrac9Parameters : CodecParameters
{
public Atrac9Parameters() { }
public Atrac9Parameters(CodecParameters source) : base(source) { }
}
}
33 changes: 33 additions & 0 deletions src/VGAudio.Atrac9/Codecs/Atrac9/Atrac9Rng.cs
@@ -0,0 +1,33 @@
namespace VGAudio.Codecs.Atrac9
{
/// <summary>
/// An Xorshift RNG used by the ATRAC9 codec
/// </summary>
internal class Atrac9Rng
{
private ushort _stateA;
private ushort _stateB;
private ushort _stateC;
private ushort _stateD;

public Atrac9Rng(ushort seed)
{
int startValue = 0x4D93 * (seed ^ (seed >> 14));

_stateA = (ushort)(3 - startValue);
_stateB = (ushort)(2 - startValue);
_stateC = (ushort)(1 - startValue);
_stateD = (ushort)(0 - startValue);
}

public ushort Next()
{
ushort t = (ushort)(_stateD ^ (_stateD << 5));
_stateD = _stateC;
_stateC = _stateB;
_stateB = _stateA;
_stateA = (ushort)(t ^ _stateA ^ ((t ^ (_stateA >> 5)) >> 4));
return _stateA;
}
}
}
176 changes: 176 additions & 0 deletions src/VGAudio.Atrac9/Codecs/Atrac9/BandExtension.cs
@@ -0,0 +1,176 @@
using System;
using static VGAudio.Codecs.Atrac9.Tables;

namespace VGAudio.Codecs.Atrac9
{
internal static class BandExtension
{
public static void ApplyBandExtension(Block block)
{
if (!block.BandExtensionEnabled || !block.HasExtensionData) return;

foreach (Channel channel in block.Channels)
{
ApplyBandExtensionChannel(channel);
}
}

private static void ApplyBandExtensionChannel(Channel channel)
{
int groupAUnit = channel.Block.QuantizationUnitCount;
int[] scaleFactors = channel.ScaleFactors;
double[] spectra = channel.Spectra;
double[] scales = channel.BexScales;
int[] values = channel.BexValues;

GetBexBandInfo(out int bandCount, out int groupBUnit, out int groupCUnit, groupAUnit);
int totalUnits = Math.Max(groupCUnit, 22);

int groupABin = QuantUnitToCoeffIndex[groupAUnit];
int groupBBin = QuantUnitToCoeffIndex[groupBUnit];
int groupCBin = QuantUnitToCoeffIndex[groupCUnit];
int totalBins = QuantUnitToCoeffIndex[totalUnits];

FillHighFrequencies(spectra, groupABin, groupBBin, groupCBin, totalBins);

switch (channel.BexMode)
{
case 0:
int bexQuantUnits = totalUnits - groupAUnit;

switch (bandCount)
{
case 3:
scales[0] = BexMode0Bands3[0][values[0]];
scales[1] = BexMode0Bands3[1][values[0]];
scales[2] = BexMode0Bands3[2][values[1]];
scales[3] = BexMode0Bands3[3][values[2]];
scales[4] = BexMode0Bands3[4][values[3]];
break;
case 4:
scales[0] = BexMode0Bands4[0][values[0]];
scales[1] = BexMode0Bands4[1][values[0]];
scales[2] = BexMode0Bands4[2][values[1]];
scales[3] = BexMode0Bands4[3][values[2]];
scales[4] = BexMode0Bands4[4][values[3]];
break;
case 5:
scales[0] = BexMode0Bands5[0][values[0]];
scales[1] = BexMode0Bands5[1][values[1]];
scales[2] = BexMode0Bands5[2][values[1]];
break;
}

scales[bexQuantUnits - 1] = SpectrumScale[scaleFactors[groupAUnit]];

AddNoiseToSpectrum(channel, QuantUnitToCoeffIndex[totalUnits - 1],
QuantUnitToCoeffCount[totalUnits - 1]);
ScaleBexQuantUnits(spectra, scales, groupAUnit, totalUnits);
break;
case 1:
for (int i = groupAUnit; i < totalUnits; i++)
{
scales[i - groupAUnit] = SpectrumScale[scaleFactors[i]];
}

AddNoiseToSpectrum(channel, groupABin, totalBins - groupABin);
ScaleBexQuantUnits(spectra, scales, groupAUnit, totalUnits);
break;
case 2:
double groupAScale2 = BexMode2Scale[values[0]];
double groupBScale2 = BexMode2Scale[values[1]];

for (int i = groupABin; i < groupBBin; i++)
{
spectra[i] *= groupAScale2;
}

for (int i = groupBBin; i < groupCBin; i++)
{
spectra[i] *= groupBScale2;
}
return;
case 3:
double rate = Math.Pow(2, BexMode3Rate[values[1]]);
double scale = BexMode3Initial[values[0]];
for (int i = groupABin; i < totalBins; i++)
{
scale *= rate;
spectra[i] *= scale;
}
return;
case 4:
double mult = BexMode4Multiplier[values[0]];
double groupAScale4 = 0.7079468 * mult;
double groupBScale4 = 0.5011902 * mult;
double groupCScale4 = 0.3548279 * mult;

for (int i = groupABin; i < groupBBin; i++)
{
spectra[i] *= groupAScale4;
}

for (int i = groupBBin; i < groupCBin; i++)
{
spectra[i] *= groupBScale4;
}

for (int i = groupCBin; i < totalBins; i++)
{
spectra[i] *= groupCScale4;
}
return;
}
}

private static void ScaleBexQuantUnits(double[] spectra, double[] scales, int startUnit, int totalUnits)
{
for (int i = startUnit; i < totalUnits; i++)
{
for (int k = QuantUnitToCoeffIndex[i]; k < QuantUnitToCoeffIndex[i + 1]; k++)
{
spectra[k] *= scales[i - startUnit];
}
}
}

private static void FillHighFrequencies(double[] spectra, int groupABin, int groupBBin, int groupCBin, int totalBins)
{
for (int i = 0; i < groupBBin - groupABin; i++)
{
spectra[groupABin + i] = spectra[groupABin - i - 1];
}

for (int i = 0; i < groupCBin - groupBBin; i++)
{
spectra[groupBBin + i] = spectra[groupBBin - i - 1];
}

for (int i = 0; i < totalBins - groupCBin; i++)
{
spectra[groupCBin + i] = spectra[groupCBin - i - 1];
}
}

private static void AddNoiseToSpectrum(Channel channel, int index, int count)
{
if (channel.Rng == null)
{
int[] sf = channel.ScaleFactors;
ushort seed = (ushort)(543 * (sf[8] + sf[12] + sf[15] + 1));
channel.Rng = new Atrac9Rng(seed);
}
for (int i = 0; i < count; i++)
{
channel.Spectra[i + index] = channel.Rng.Next() / 65535.0 * 2.0 - 1.0;
}
}

public static void GetBexBandInfo(out int bandCount, out int groupAUnit, out int groupBUnit, int quantUnits)
{
groupAUnit = BexGroupInfo[quantUnits - 13][0];
groupBUnit = BexGroupInfo[quantUnits - 13][1];
bandCount = BexGroupInfo[quantUnits - 13][2];
}
}
}

0 comments on commit 6a4062c

Please sign in to comment.