Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5b1d153
commit 6a4062c
Showing
31 changed files
with
2,507 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
| ||
namespace VGAudio.Codecs.Atrac9 | ||
{ | ||
public class Atrac9Parameters : CodecParameters | ||
{ | ||
public Atrac9Parameters() { } | ||
public Atrac9Parameters(CodecParameters source) : base(source) { } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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]; | ||
} | ||
} | ||
} |
Oops, something went wrong.