mirror of
https://github.com/blueskythlikesclouds/SonicAudioTools.git
synced 2025-02-13 09:12:35 +01:00
239 lines
8.7 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|