/// <summary> /// Calculates WAV file duration (in minutes and seconds). /// </summary> /// <param name="WAVheader">WAV file header to read info from.</param> /// <param name="durationMinutes">Output parameter for duration in minutes.</param> /// <param name="durationSeconds">Output parameter for duration in seconds.</param> public void GetWAVFileDuration(WAVHeader WAVheader, out int durationMinutes, out double durationSeconds) { // We get duration from header data, data size / bytes per sample / number of channels / sample rate durationSeconds = (double)WAVheader.Subchunk2Size / (WAVheader.BitsPerSample / 8) / WAVheader.NumChannels / WAVheader.SampleRate; durationMinutes = (int)Math.Floor(durationSeconds / 60); durationSeconds -= durationMinutes * 60; }
/// <summary> /// Create a new WAV file from data. Used mainly in the conversion methods. /// </summary> /// <param name="header">The header</param> /// <param name="fmtChunk">The format info</param> /// <param name="data">The data</param> public WAVFile(WAVHeader header, WAVFormatChunk fmtChunk, WAVDataChunk data) { Header = header; Format = fmtChunk; Data = data; Header.DataChunk = Data; Data.FormatChunk = Format; }
/// <summary> /// Create a new WEM file from the specified path. /// </summary> /// <param name="path">The WEM file.</param> public WAVFile(string path) { using (FileStream inputStream = new FileStream(path, FileMode.Open)) { BinaryReader reader = new BinaryReader(inputStream); Header = WAVHeader.CreateFromStream(reader); Format = WAVFormatChunk.CreateFromStream(reader); Data = WAVDataChunk.CreateFromStream(reader, Format); Header.DataChunk = Data; reader.Dispose(); } }
/// <summary> /// "Fixes" WAV header for decrypted files. Decrypted file contains one sample less. /// We change WAV header to supply correct data. /// </summary> public void CreateDecryptedWAVFile() { WAVHeader header = this.Header; // 2 last encrypted samples won't be decrypted because reverse calculation requires 3 consequent values // But we inserted 1 extra sample in the beginning (x1prev) while encrypting, so we'll lose only 1 sample instead of 2 header.Subchunk2Size -= sizeof(short); // ChunkSize has to be changed as well as its value should always be 36 (bytes) greater than Subchunk2Size header.ChunkSize = Header.Subchunk2Size + 36; this.Header = header; // Create a WAV file and write modified header to it CreateWAVFile(); }
/// <summary> /// "Fixes" WAV header for encrypted files. Original file contains short values, encrypted - double. /// We change WAV header to supply correct data as if it contains more samples. /// Because of this encrypted file duration seems to be 4 times longer and file is ~4 bigger. /// </summary> public void CreateEncryptedWAVFile() { WAVHeader header = this.Header; // Fix difference between short and double header.Subchunk2Size *= sizeof(double) / sizeof(short); // And we also have one extra sample at the beginning (x1prev) header.Subchunk2Size += sizeof(double); // ChunkSize has to be changed as well as its value should always be 36 (bytes) greater than Subchunk2Size header.ChunkSize = Header.Subchunk2Size + 36; this.Header = header; // Create a WAV file and write modified header to it CreateWAVFile(); }
/// <summary> /// Opens existing WAV file and reads header info. /// </summary> public void ReadWAVHeader() { using (FileStream fs = new FileStream(FileName, FileMode.Open, FileAccess.Read)) using (BinaryReader br = new BinaryReader(fs)) { WAVHeader header = new WAVHeader(); header.ChunkID = br.ReadBytes(4); header.ChunkSize = br.ReadUInt32(); header.Format = br.ReadBytes(4); header.Subchunk1ID = br.ReadBytes(4); header.Subchunk1Size = br.ReadUInt32(); header.AudioFormat = br.ReadUInt16(); header.NumChannels = br.ReadUInt16(); header.SampleRate = br.ReadUInt32(); header.ByteRate = br.ReadUInt32(); header.BlockAlign = br.ReadUInt16(); header.BitsPerSample = br.ReadUInt16(); header.Subchunk2ID = br.ReadBytes(4); header.Subchunk2Size = br.ReadUInt32(); this.Header = header; } }
public static byte[] DecodeMCAtoWAV(string mcaFile) { using (var br = new BinaryReaderX(File.OpenRead(mcaFile))) { //Header var header = br.ReadStruct <Header>(); //check info if (header.magic != "MADP") { Console.WriteLine("This is no mca file."); Environment.Exit(0); } if (header.channelCount > 2) { Console.WriteLine("Only mca's with 1 or 2 channels are supported."); Environment.Exit(0); } //Output meta Console.WriteLine($"\nMeta:\n" + $" Version: {header.version}\n" + $"\n" + $" Channels: {header.channelCount}\n" + $" SampleRate: {header.sampleRate}\n" + $" Samples: {header.numSamples}\n" + $"\n" + $" LoopStart: {header.loopStart}\n" + $" LoopEnd: {header.loopEnd}"); //Version specifics int headSize, coefShift, coefStart, startOffset, coefOffset; int coefSpacing = 0x30; if (header.version <= 3) { headSize = (int)br.BaseStream.Length - header.dataSize; coefShift = 0x0; coefStart = headSize - coefSpacing * header.channelCount; startOffset = headSize; coefOffset = coefStart + coefShift * 0x14; } else if (header.version == 4) { headSize = header.headSize; coefShift = header.coefShift; coefStart = headSize - coefSpacing * header.channelCount; startOffset = (int)br.BaseStream.Length - header.dataSize; coefOffset = coefStart + coefShift * 0x14; } else { headSize = header.headSize; coefShift = header.coefShift; coefStart = headSize - coefSpacing * header.channelCount; var tmpOff = br.BaseStream.Position; br.BaseStream.Position = coefStart - 0x4; startOffset = br.ReadInt32(); coefOffset = coefStart + coefShift * 0x14; br.BaseStream.Position = tmpOff; } //sanity check (for bad rips with the header manually truncated to in attempt to "fix" v5 headers) var fileSize = br.BaseStream.Length; if (startOffset + header.dataSize > fileSize) { if (headSize + header.dataSize > fileSize) { throw new Exception("Mismatching information. headSize + dataSize don't correlate with fileSize.\nPlease check the header."); } startOffset = (int)fileSize - header.dataSize; } //Setup coefs var channels = SetupCoefs(br.BaseStream, coefOffset, header.channelCount, coefSpacing); //Decode NGC_DSP br.BaseStream.Position = startOffset; byte[] decode; int channelDataSize = header.dataSize; if (header.channelCount == 1) { //No interleave decode = DecodeNGCDSP(br.ReadBytes(header.dataSize), header, channels[0]); } else { //Interleave //Get channelData var soundData = br.ReadBytes(header.dataSize); var channelData = new List <List <byte> >(); for (int i = 0; i < header.channelCount; i++) { channelData.Add(new List <byte>()); } using (var soundBr = new BinaryReaderX(new MemoryStream(soundData))) { while (soundBr.BaseStream.Position < soundBr.BaseStream.Length) { for (int i = 0; i < header.channelCount; i++) { channelData[i].AddRange(soundBr.ReadBytes(header.interleaveBlockSize)); } } } //Decode channelData var tmpDec = new List <byte>(); for (int i = 0; i < header.channelCount; i++) { tmpDec.AddRange(DecodeNGCDSP(channelData[i].ToArray(), header, channels[i])); } decode = tmpDec.ToArray(); channelDataSize = decode.Length / header.channelCount; } //Create WAV var wavHeader = new WAVHeader { fileSize = decode.Length + 0x28 - 0x8, channelCount = header.channelCount, sampleRate = header.sampleRate, avgBytesPerSec = header.sampleRate * 0x2, blockAlign = (short)(0x2 * header.channelCount) }; var wavStream = new MemoryStream(); using (var bw = new BinaryWriterX(wavStream, true)) { bw.WriteStruct(wavHeader); bw.WriteASCII("data"); bw.Write(decode.Length); if (header.channelCount == 1) { bw.Write(decode); } else { int sampleInChannel = 0; using (var decBr = new BinaryReaderX(new MemoryStream(decode))) { while (sampleInChannel < channelDataSize / 2) { for (int i = 0; i < header.channelCount; i++) { decBr.BaseStream.Position = i * channelDataSize + sampleInChannel * 2; bw.Write(decBr.ReadInt16()); } sampleInChannel++; } } } } return(wavStream.ToArray()); } }
/// <summary> /// Constructor to get file name and memory for header. /// </summary> /// <param name="fileName">File name</param> public WAVFile(string fileName) { this.FileName = fileName; this.Header = new WAVHeader(); }