2017-01-26 23:40:46 +03:00

239 lines
8.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using SonicAudioLib.IO;
using NAudio.Wave;
namespace CsbBuilder.Audio
{
public struct AdxHeader
{
public ushort Identifier;
public ushort DataPosition;
public byte EncodeType;
public byte BlockLength;
public byte SampleBitdepth;
public byte ChannelCount;
public uint SampleRate;
public uint SampleCount;
public ushort CutoffFrequency;
public byte Version;
public byte Flags;
public bool LoopEnabled;
public uint LoopBeginSampleIndex;
public uint LoopBeginByteIndex;
public uint LoopEndSampleIndex;
public uint LoopEndByteIndex;
}
public static class AdxConverter
{
public static void ConvertToWav(string sourceFileName)
{
ConvertToWav(sourceFileName, Path.ChangeExtension(sourceFileName, "wav"));
}
public static void ConvertToWav(string sourceFileName, string destinationFileName)
{
BufferedWaveProvider provider = Decode(sourceFileName, 1.0, 0.0);
using (WaveFileWriter writer = new WaveFileWriter(destinationFileName, provider.WaveFormat))
{
int num;
byte[] buffer = new byte[32767];
while ((num = provider.Read(buffer, 0, buffer.Length)) != 0)
{
writer.Write(buffer, 0, num);
}
}
provider.ClearBuffer();
}
public static AdxHeader LoadHeader(string sourceFileName)
{
using (Stream source = File.OpenRead(sourceFileName))
{
return ReadHeader(source);
}
}
public static AdxHeader ReadHeader(Stream source)
{
AdxHeader header = new AdxHeader();
header.Identifier = EndianStream.ReadUInt16BE(source);
header.DataPosition = EndianStream.ReadUInt16BE(source);
header.EncodeType = EndianStream.ReadByte(source);
header.BlockLength = EndianStream.ReadByte(source);
header.SampleBitdepth = EndianStream.ReadByte(source);
header.ChannelCount = EndianStream.ReadByte(source);
header.SampleRate = EndianStream.ReadUInt32BE(source);
header.SampleCount = EndianStream.ReadUInt32BE(source);
header.CutoffFrequency = EndianStream.ReadUInt16BE(source);
header.Version = EndianStream.ReadByte(source);
header.Flags = EndianStream.ReadByte(source);
source.Seek(4, SeekOrigin.Current);
header.LoopEnabled = EndianStream.ReadUInt32BE(source) > 0;
header.LoopBeginSampleIndex = EndianStream.ReadUInt32BE(source);
header.LoopBeginByteIndex = EndianStream.ReadUInt32BE(source);
header.LoopEndSampleIndex = EndianStream.ReadUInt32BE(source);
header.LoopEndByteIndex = EndianStream.ReadUInt32BE(source);
return header;
}
public static BufferedWaveProvider Decode(string sourceFileName, double volume, double pitch)
{
using (Stream source = File.OpenRead(sourceFileName))
{
return Decode(source, volume, pitch);
}
}
private static void CalculateCoefficients(double cutoffFrequency, double sampleRate, out short coef1, out short coef2)
{
double a = Math.Sqrt(2.0);
double b = a - Math.Cos(cutoffFrequency * 6.2831855 / sampleRate);
double c = (b - Math.Sqrt((b - (a - 1.0)) * (a - 1.0 + b))) / (a - 1.0);
coef1 = (short)(8192.0 * c);
coef2 = (short)(c * c * -4096.0);
}
// https://wiki.multimedia.cx/index.php/CRI_ADX_ADPCM
public static BufferedWaveProvider Decode(Stream source, double volume, double pitch)
{
AdxHeader header = ReadHeader(source);
WaveFormat waveFormat = new WaveFormat((int)header.SampleRate, 16, header.ChannelCount);
BufferedWaveProvider provider = new BufferedWaveProvider(waveFormat);
provider.BufferLength = (int)(header.SampleCount * header.ChannelCount * 2);
provider.ReadFully = false;
provider.DiscardOnBufferOverflow = true;
short firstHistory1 = 0;
short firstHistory2 = 0;
short secondHistory1 = 0;
short secondHistory2 = 0;
short coef1 = 0;
short coef2 = 0;
CalculateCoefficients(header.CutoffFrequency, header.SampleRate, out coef1, out coef2);
source.Seek(header.DataPosition + 4, SeekOrigin.Begin);
if (header.ChannelCount == 1)
{
for (int i = 0; i < header.SampleCount / 32; i++)
{
byte[] block = EndianStream.ReadBytes(source, header.BlockLength);
foreach (short sampleShort in DecodeBlock(block, ref firstHistory1, ref firstHistory2, coef1, coef2))
{
double sample = (double)sampleShort * volume;
if (sample > short.MaxValue)
{
sample = short.MaxValue;
}
if (sample < short.MinValue)
{
sample = short.MinValue;
}
provider.AddSamples(BitConverter.GetBytes((short)sample), 0, 2);
}
}
}
else if (header.ChannelCount == 2)
{
for (int i = 0; i < header.SampleCount / 32; i++)
{
byte[] blockLeft = EndianStream.ReadBytes(source, header.BlockLength);
byte[] blockRight = EndianStream.ReadBytes(source, header.BlockLength);
short[] samplesLeft = DecodeBlock(blockLeft, ref firstHistory1, ref firstHistory2, coef1, coef2);
short[] samplesRight = DecodeBlock(blockRight, ref secondHistory1, ref secondHistory2, coef1, coef2);
for (int j = 0; j < 32; j++)
{
double newSampleLeft = samplesLeft[j] * volume;
double newSampleRight = samplesRight[j] * volume;
if (newSampleLeft > short.MaxValue)
{
newSampleLeft = short.MaxValue;
}
if (newSampleLeft < short.MinValue)
{
newSampleLeft = short.MinValue;
}
if (newSampleRight > short.MaxValue)
{
newSampleRight = short.MaxValue;
}
if (newSampleRight < short.MinValue)
{
newSampleRight = short.MinValue;
}
samplesLeft[j] = (short)newSampleLeft;
samplesRight[j] = (short)newSampleRight;
byte[] sampleLeft = BitConverter.GetBytes(samplesLeft[j]);
byte[] sampleRight = BitConverter.GetBytes(samplesRight[j]);
provider.AddSamples(new byte[] { sampleLeft[0], sampleLeft[1], sampleRight[0], sampleRight[1] }, 0, 4);
}
}
}
return provider;
}
public static short[] DecodeBlock(byte[] block, ref short history1, ref short history2, short coef1, short coef2)
{
int scale = (block[0] << 8 | block[1]) + 1;
short[] samples = new short[32];
for (int i = 0; i < 32; i++)
{
int sampleByte = block[2 + i / 2];
int sampleNibble = ((i & 1) != 0 ? (sampleByte & 7) - (sampleByte & 8) : ((sampleByte & 0x70) - (sampleByte & 0x80)) >> 4);
int sampleDelta = sampleNibble * scale;
int predictedSample12 = coef1 * history1 + coef2 * history2;
int predictedSample = predictedSample12 >> 12;
int sampleRaw = predictedSample + sampleDelta;
if (sampleRaw > short.MaxValue)
{
sampleRaw = short.MaxValue;
}
else if (sampleRaw < short.MinValue)
{
sampleRaw = short.MinValue;
}
samples[i] = (short)sampleRaw;
history2 = history1;
history1 = (short)sampleRaw;
}
return samples;
}
}
}