/* This file is part of libWiiSharp * Copyright (C) 2009 Leathl * Copyright (C) 2020 Github Contributors * * libWiiSharp is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * libWiiSharp is distributed in the hope that it will be * useful, but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; namespace libWiiSharp { public class BNS : IDisposable { private BNS_Header bnsHeader = new BNS_Header(); private BNS_Info bnsInfo = new BNS_Info(); private BNS_Data bnsData = new BNS_Data(); private int[,] lSamples = new int[2, 2]; private int[,] rlSamples = new int[2, 2]; private int[] tlSamples = new int[2]; private int[] hbcDefTbl = new int[16] { 674, 1040, 3598, -1738, 2270, -583, 3967, -1969, 1516, 381, 3453, -1468, 2606, -617, 3795, -1759 }; private int[] defTbl = new int[16] { 1820, -856, 3238, -1514, 2333, -550, 3336, -1376, 2444, -949, 3666, -1764, 2654, -701, 3420, -1398 }; private int[] pHist1 = new int[2]; private int[] pHist2 = new int[2]; private int tempSampleCount; private byte[] waveFile; private bool loopFromWave; private bool converted; private bool toMono; private bool isDisposed; /// /// 0x00 (0) = No Loop, 0x01 (1) = Loop /// public bool HasLoop { get => this.bnsInfo.HasLoop == (byte) 1; set => this.bnsInfo.HasLoop = value ? (byte) 1 : (byte) 0; } /// /// The start sample of the Loop /// public uint LoopStart { get => this.bnsInfo.LoopStart; set => this.bnsInfo.LoopStart = value; } /// /// The total number of samples in this file /// public uint TotalSampleCount { get => this.bnsInfo.LoopEnd; set => this.bnsInfo.LoopEnd = value; } /// /// If true and the input Wave file is stereo, the BNS will be converted to Mono. /// Be sure to set this before you call Convert()! /// public bool StereoToMono { get => this.toMono; set => this.toMono = value; } public event EventHandler Progress; protected BNS() { } public BNS(string waveFile) => this.waveFile = File.ReadAllBytes(waveFile); public BNS(string waveFile, bool loopFromWave) { this.waveFile = File.ReadAllBytes(waveFile); this.loopFromWave = loopFromWave; } public BNS(byte[] waveFile) => this.waveFile = waveFile; public BNS(byte[] waveFile, bool loopFromWave) { this.waveFile = waveFile; this.loopFromWave = loopFromWave; } #region IDisposable Members ~BNS() => this.Dispose(false); public void Dispose() { this.Dispose(true); GC.SuppressFinalize((object) this); } protected virtual void Dispose(bool disposing) { if (disposing && !this.isDisposed) { this.bnsHeader = (BNS_Header) null; this.bnsInfo = (BNS_Info) null; this.bnsData = (BNS_Data) null; this.lSamples = (int[,]) null; this.rlSamples = (int[,]) null; this.tlSamples = (int[]) null; this.hbcDefTbl = (int[]) null; this.pHist1 = (int[]) null; this.pHist2 = (int[]) null; this.waveFile = (byte[]) null; } this.isDisposed = true; } #endregion #region Public Functions /// /// Returns the length of the BNS audio file in seconds /// /// /// public static int GetBnsLength(byte[] bnsFile) { uint sampleRate = (uint) Shared.Swap(BitConverter.ToUInt16(bnsFile, 44)); uint sampleCount = Shared.Swap(BitConverter.ToUInt32(bnsFile, 52)); return (int)(sampleCount / sampleRate); } /// /// Converts the Wave file to BNS /// public void Convert() => this.convert(this.waveFile, this.loopFromWave); /// /// Returns the BNS file as a Byte Array. If not already converted, it will be done first. /// /// public byte[] ToByteArray() => this.ToMemoryStream().ToArray(); /// /// Returns the BNS file as a Memory Stream. If not already converted, it will be done first. /// /// public MemoryStream ToMemoryStream() { if (!this.converted) this.convert(this.waveFile, this.loopFromWave); MemoryStream memoryStream = new MemoryStream(); try { this.bnsHeader.Write((Stream) memoryStream); this.bnsInfo.Write((Stream) memoryStream); this.bnsData.Write((Stream) memoryStream); return memoryStream; } catch { memoryStream.Dispose(); throw; } } /// /// Saves the BNS file to the given path. If not already converted, it will be done first. /// /// public void Save(string destinationFile) { if (File.Exists(destinationFile)) File.Delete(destinationFile); using (FileStream fileStream = new FileStream(destinationFile, FileMode.Create)) { byte[] array = this.ToMemoryStream().ToArray(); fileStream.Write(array, 0, array.Length); } } /// /// Sets the Loop to the given Start Sample. Be sure that you call Convert() first! /// /// public void SetLoop(int loopStartSample) { this.bnsInfo.HasLoop = (byte) 1; this.bnsInfo.LoopStart = (uint) loopStartSample; } #endregion #region Private Functions private void convert(byte[] waveFile, bool loopFromWave) { Wave wave = new Wave(waveFile); int numLoops = wave.NumLoops; int loopStart = wave.LoopStart; this.bnsInfo.ChannelCount = (byte) wave.NumChannels; this.bnsInfo.SampleRate = (ushort) wave.SampleRate; if (this.bnsInfo.ChannelCount > (byte) 2 || this.bnsInfo.ChannelCount < (byte) 1) throw new Exception("Unsupported Amount of Channels!"); if (wave.BitDepth != 16) throw new Exception("Only 16bit Wave files are supported!"); this.bnsData.Data = wave.DataFormat == 1 ? this.Encode(wave.SampleData) : throw new Exception("The format of this Wave file is not supported!"); if (this.bnsInfo.ChannelCount == (byte) 1) { this.bnsHeader.InfoLength = 96U; this.bnsHeader.DataOffset = 128U; this.bnsInfo.Size = 96U; this.bnsInfo.Channel1StartOffset = 28U; this.bnsInfo.Channel2StartOffset = 0U; this.bnsInfo.Channel1Start = 40U; this.bnsInfo.Coefficients1Offset = 0U; } this.bnsData.Size = (uint) (this.bnsData.Data.Length + 8); this.bnsHeader.DataLength = this.bnsData.Size; this.bnsHeader.FileSize = (uint) this.bnsHeader.Size + this.bnsInfo.Size + this.bnsData.Size; if (loopFromWave && numLoops == 1 && loopStart != -1) { this.bnsInfo.LoopStart = (uint) loopStart; this.bnsInfo.HasLoop = (byte) 1; } this.bnsInfo.LoopEnd = (uint) this.tempSampleCount; for (int index = 0; index < 16; ++index) { this.bnsInfo.Coefficients1[index] = this.defTbl[index]; if (this.bnsInfo.ChannelCount == (byte) 2) this.bnsInfo.Coefficients2[index] = this.defTbl[index]; } this.converted = true; } private byte[] Encode(byte[] inputFrames) { int[] inputBuffer = new int[14]; this.tempSampleCount = inputFrames.Length / (this.bnsInfo.ChannelCount == (byte) 2 ? 4 : 2); int num1 = inputFrames.Length / (this.bnsInfo.ChannelCount == (byte) 2 ? 4 : 2) % 14; Array.Resize(ref inputFrames, inputFrames.Length + (14 - num1) * (this.bnsInfo.ChannelCount == (byte) 2 ? 4 : 2)); int num2 = inputFrames.Length / (this.bnsInfo.ChannelCount == (byte) 2 ? 4 : 2); int num3 = (num2 + 13) / 14; List intList1 = new List(); List intList2 = new List(); int startIndex = 0; if (this.toMono && this.bnsInfo.ChannelCount == (byte) 2) this.bnsInfo.ChannelCount = (byte) 1; else if (this.toMono) this.toMono = false; for (int index = 0; index < num2; ++index) { intList1.Add((int) BitConverter.ToInt16(inputFrames, startIndex)); startIndex += 2; if (this.bnsInfo.ChannelCount == (byte) 2 || this.toMono) { intList2.Add((int) BitConverter.ToInt16(inputFrames, startIndex)); startIndex += 2; } } byte[] numArray1 = new byte[this.bnsInfo.ChannelCount == (byte) 2 ? num3 * 16 : num3 * 8]; int num4 = 0; int num5 = num3 * 8; this.bnsInfo.Channel2Start = this.bnsInfo.ChannelCount == (byte) 2 ? (uint) num5 : 0U; int[] array1 = intList1.ToArray(); int[] array2 = intList2.ToArray(); for (int index1 = 0; index1 < num3; ++index1) { try { if (index1 % (num3 / 100) != 0) { if (index1 + 1 != num3) goto label_14; } this.ChangeProgress((index1 + 1) * 100 / num3); } catch { } label_14: for (int index2 = 0; index2 < 14; ++index2) inputBuffer[index2] = array1[index1 * 14 + index2]; byte[] numArray2 = this.RepackAdpcm(0, this.defTbl, inputBuffer); for (int index2 = 0; index2 < 8; ++index2) numArray1[num4 + index2] = numArray2[index2]; num4 += 8; if (this.bnsInfo.ChannelCount == (byte) 2) { for (int index2 = 0; index2 < 14; ++index2) inputBuffer[index2] = array2[index1 * 14 + index2]; byte[] numArray3 = this.RepackAdpcm(1, this.defTbl, inputBuffer); for (int index2 = 0; index2 < 8; ++index2) numArray1[num5 + index2] = numArray3[index2]; num5 += 8; } } this.bnsInfo.LoopEnd = (uint) (num3 * 7); return numArray1; } private byte[] RepackAdpcm(int index, int[] table, int[] inputBuffer) { byte[] numArray1 = new byte[8]; int[] numArray2 = new int[2]; double num1 = 999999999.0; for (int tableIndex = 0; tableIndex < 8; ++tableIndex) { double outError; byte[] numArray3 = this.CompressAdpcm(index, table, tableIndex, inputBuffer, out outError); if (outError < num1) { num1 = outError; for (int index1 = 0; index1 < 8; ++index1) numArray1[index1] = numArray3[index1]; for (int index1 = 0; index1 < 2; ++index1) numArray2[index1] = this.tlSamples[index1]; } } for (int index1 = 0; index1 < 2; ++index1) { int[,] rlSamples = this.rlSamples; int num2 = index1; int index2 = index; int index3 = num2; int num3 = numArray2[index1]; rlSamples[index2, index3] = num3; } return numArray1; } private byte[] CompressAdpcm( int index, int[] table, int tableIndex, int[] inputBuffer, out double outError) { byte[] numArray = new byte[8]; int num1 = 0; int num2 = table[2 * tableIndex]; int num3 = table[2 * tableIndex + 1]; int stdExponent = this.DetermineStdExponent(index, table, tableIndex, inputBuffer); while (stdExponent <= 15) { bool flag = false; num1 = 0; numArray[0] = (byte) (stdExponent | tableIndex << 4); for (int index1 = 0; index1 < 2; ++index1) this.tlSamples[index1] = this.rlSamples[index, index1]; int num4 = 0; for (int index1 = 0; index1 < 14; ++index1) { int num5 = this.tlSamples[1] * num2 + this.tlSamples[0] * num3 >> 11; int input1 = inputBuffer[index1] - num5 >> stdExponent; if (input1 <= 7 && input1 >= -8) { int num6 = this.Clamp(input1, -8, 7); numArray[index1 / 2 + 1] = (index1 & 1) == 0 ? (byte) (num6 << 4) : (byte) ((uint) numArray[index1 / 2 + 1] | (uint) (num6 & 15)); int input2 = num5 + (num6 << stdExponent); this.tlSamples[0] = this.tlSamples[1]; this.tlSamples[1] = this.Clamp(input2, (int) short.MinValue, (int) short.MaxValue); num1 += (int) Math.Pow((double) (this.tlSamples[1] - inputBuffer[index1]), 2.0); } else { ++stdExponent; flag = true; break; } } if (!flag) num4 = 14; if (num4 == 14) break; } outError = (double) num1; return numArray; } private int DetermineStdExponent(int index, int[] table, int tableIndex, int[] inputBuffer) { int[] numArray = new int[2]; int num1 = 0; int num2 = table[2 * tableIndex]; int num3 = table[2 * tableIndex + 1]; for (int index1 = 0; index1 < 2; ++index1) numArray[index1] = this.rlSamples[index, index1]; for (int index1 = 0; index1 < 14; ++index1) { int num4 = numArray[1] * num2 + numArray[0] * num3 >> 11; int num5 = inputBuffer[index1] - num4; if (num5 > num1) num1 = num5; numArray[0] = numArray[1]; numArray[1] = inputBuffer[index1]; } return this.FindExponent((double) num1); } private int FindExponent(double residual) { int num = 0; for (; residual > 7.5 || residual < -8.5; residual /= 2.0) ++num; return num; } private int Clamp(int input, int min, int max) { if (input < min) return min; return input > max ? max : input; } private void ChangeProgress(int progressPercentage) { EventHandler progress = this.Progress; if (progress == null) return; progress(new object(), new ProgressChangedEventArgs(progressPercentage, new object())); } #endregion #region BNS to Wave #region Public Functions /// /// Converts a BNS audio file to Wave format. /// /// /// /// public static Wave BnsToWave(Stream inputFile) { BNS bns = new BNS(); byte[] samples = bns.Read(inputFile); Wave wave = new Wave((int) bns.bnsInfo.ChannelCount, 16, (int) bns.bnsInfo.SampleRate, samples); if (bns.bnsInfo.HasLoop == (byte) 1) wave.AddLoop((int) bns.bnsInfo.LoopStart); return wave; } public static Wave BnsToWave(string pathToFile) { BNS bns = new BNS(); byte[] samples = (byte[]) null; using (FileStream fileStream = new FileStream(pathToFile, FileMode.Open)) samples = bns.Read((Stream) fileStream); Wave wave = new Wave((int) bns.bnsInfo.ChannelCount, 16, (int) bns.bnsInfo.SampleRate, samples); if (bns.bnsInfo.HasLoop == (byte) 1) wave.AddLoop((int) bns.bnsInfo.LoopStart); return wave; } public static Wave BnsToWave(byte[] bnsFile) { BNS bns = new BNS(); byte[] samples = (byte[]) null; using (MemoryStream memoryStream = new MemoryStream(bnsFile)) samples = bns.Read((Stream) memoryStream); Wave wave = new Wave((int) bns.bnsInfo.ChannelCount, 16, (int) bns.bnsInfo.SampleRate, samples); if (bns.bnsInfo.HasLoop == (byte) 1) wave.AddLoop((int) bns.bnsInfo.LoopStart); return wave; } #endregion #region Private Functions private byte[] Read(Stream input) { input.Seek(0L, SeekOrigin.Begin); this.bnsHeader.Read(input); this.bnsInfo.Read(input); this.bnsData.Read(input); return this.Decode(); } private byte[] Decode() { List byteList = new List(); int num = this.bnsData.Data.Length / (this.bnsInfo.ChannelCount == (byte) 2 ? 16 : 8); int dataOffset1 = 0; int dataOffset2 = num * 8; byte[] numArray1 = new byte[0]; byte[] numArray2 = new byte[0]; for (int index1 = 0; index1 < num; ++index1) { byte[] numArray3 = this.DecodeAdpcm(0, dataOffset1); if (this.bnsInfo.ChannelCount == (byte) 2) numArray2 = this.DecodeAdpcm(1, dataOffset2); for (int index2 = 0; index2 < 14; ++index2) { byteList.Add(numArray3[index2 * 2]); byteList.Add(numArray3[index2 * 2 + 1]); if (this.bnsInfo.ChannelCount == (byte) 2) { byteList.Add(numArray2[index2 * 2]); byteList.Add(numArray2[index2 * 2 + 1]); } } dataOffset1 += 8; if (this.bnsInfo.ChannelCount == (byte) 2) dataOffset2 += 8; } return byteList.ToArray(); } private byte[] DecodeAdpcm(int channel, int dataOffset) { byte[] numArray = new byte[28]; int num1 = (int) this.bnsData.Data[dataOffset] >> 4 & 15; int num2 = 1 << ((int) this.bnsData.Data[dataOffset] & 15); int num3 = this.pHist1[channel]; int num4 = this.pHist2[channel]; int num5 = channel == 0 ? this.bnsInfo.Coefficients1[num1 * 2] : this.bnsInfo.Coefficients2[num1 * 2]; int num6 = channel == 0 ? this.bnsInfo.Coefficients1[num1 * 2 + 1] : this.bnsInfo.Coefficients2[num1 * 2 + 1]; for (int index = 0; index < 14; ++index) { short num7 = (short) this.bnsData.Data[dataOffset + (index / 2 + 1)]; int num8 = (index & 1) != 0 ? (int) num7 & 15 : (int) num7 >> 4; if (num8 >= 8) num8 -= 16; int num9 = this.Clamp((num2 * num8 << 11) + (num5 * num3 + num6 * num4) + 1024 >> 11, (int) short.MinValue, (int) short.MaxValue); numArray[index * 2] = (byte) ((uint) (short) num9 & (uint) byte.MaxValue); numArray[index * 2 + 1] = (byte) ((uint) (short) num9 >> 8); num4 = num3; num3 = num9; } this.pHist1[channel] = num3; this.pHist2[channel] = num4; return numArray; } #endregion #endregion } }