#region license // This file is part of Vocaluxe. // // Vocaluxe 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. // // Vocaluxe 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 Vocaluxe. If not, see . #endregion using System; using System.Diagnostics; using System.IO; using System.Text; namespace VocaluxeLib.Utils { //Simple class to read and write WAV files public class CWavFile { public short NumChannels { get; private set; } public int SampleRate { get; private set; } public short BitsPerSample { get; private set; } public string FileName { get; private set; } //Unless this is true, all fields are to be considered as invalid public int DataSize { get; private set; } //Number of samples (per channel) public int NumSamples { get { return DataSize / (BitsPerSample / 8 * NumChannels); } } //Number of samples left to be read (per channel) public int NumSamplesLeft { get; private set; } public bool IsOpen { get; private set; } private int _DataPos; private FileStream _File; private bool _IsWritable; public CWavFile() { IsOpen = false; } ~CWavFile() { Debug.Assert(!IsOpen); Close(); } public void Create(string fileName, short numChannels, int sampleRate, short bitsPerSample) { if (IsOpen) { Close(); } _File = new FileStream(fileName, FileMode.Create); FileName = fileName; NumChannels = numChannels; SampleRate = sampleRate; BitsPerSample = bitsPerSample; DataSize = 0; NumSamplesLeft = 0; IsOpen = true; _IsWritable = true; _WriteHeader(); } public void Close() { if (!IsOpen) { return; } if (_IsWritable) { var writer = new BinaryWriter(_File); writer.Seek(4, SeekOrigin.Begin); writer.Write((int)_File.Length - 8); writer.Seek(_DataPos - 4, SeekOrigin.Begin); writer.Write(DataSize); } _File.Dispose(); _File = null; IsOpen = false; } public bool Open(string fileName) { if (IsOpen) { Close(); } if (!File.Exists(fileName)) { return false; } FileName = fileName; _File = new FileStream(FileName, FileMode.Open); try { var reader = new BinaryReader(_File); if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "RIFF") { return false; } reader.ReadInt32(); //Chunksize (file) if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "WAVE") { return false; } var curPos = _File.Position; if (_SeekTo(reader, "fmt ") < 0) { return false; } var audioFormat = reader.ReadInt16(); if (audioFormat != 1) //PCM { return false; } NumChannels = reader.ReadInt16(); SampleRate = reader.ReadInt32(); var bytesPerSec = reader.ReadInt32(); var blockAlign = reader.ReadInt16(); BitsPerSample = reader.ReadInt16(); if (BitsPerSample != 8 && BitsPerSample != 16) { return false; } if (blockAlign != NumChannels * (BitsPerSample / 8)) { return false; } if (bytesPerSec != blockAlign * SampleRate) { return false; } _File.Position = curPos; DataSize = _SeekTo(reader, "data"); if (DataSize <= 0) { return false; } _DataPos = (int)_File.Position; DataSize = Math.Min(DataSize, (int)_File.Length - _DataPos); NumSamplesLeft = NumSamples; } catch (Exception) { _File.Dispose(); throw; } IsOpen = true; return true; } /// /// Gets an array of numSamples samples per channel for the selected channel(s) /// /// Samples per channel /// Channel for which to get the samples (0=all) /// public byte[] GetNextSamples8Bit(int numSamples, int channel = 0) { if (channel < 0 || channel > NumChannels || BitsPerSample != 8) { return null; } if (numSamples > NumSamplesLeft) { return null; } byte[] result; var reader = new BinaryReader(_File); if (channel == 0 || NumChannels == 1) { result = reader.ReadBytes(numSamples * NumChannels); } else { result = new byte[numSamples]; for (var i = 0; i < numSamples; i++) { _File.Seek(channel - 1, SeekOrigin.Current); result[i] = reader.ReadByte(); _File.Seek(NumChannels - channel, SeekOrigin.Current); } } NumSamplesLeft -= numSamples; return result; } /// /// Gets an array of numSamples samples per channel for the selected channel(s) /// /// Samples per channel /// Channel for which to get the samples (0=all) /// public short[] GetNextSamples16Bit(int numSamples, int channel = 0) { if (channel < 0 || channel > NumChannels || BitsPerSample != 16) { return null; } if (numSamples > NumSamplesLeft) { return null; } short[] result; var reader = new BinaryReader(_File); try { if (channel == 0 || NumChannels == 1) { result = new short[numSamples * NumChannels]; for (var i = 0; i < numSamples * NumChannels; i++) { result[i] = reader.ReadInt16(); } } else { result = new short[numSamples]; for (var i = 0; i < numSamples; i++) { _File.Seek((channel - 1) * 2, SeekOrigin.Current); result[i] = reader.ReadInt16(); _File.Seek((NumChannels - channel) * 2, SeekOrigin.Current); } } NumSamplesLeft -= numSamples; } catch (EndOfStreamException e) { Console.WriteLine("Error: " + e); return null; } return result; } public byte[] GetNextSamples16BitAsBytes(int numSamples, int channel = 0) { var samples = GetNextSamples16Bit(numSamples, channel); if (samples == null) { return null; } var samplesByte = new byte[samples.Length * 2]; Buffer.BlockCopy(samples, 0, samplesByte, 0, samplesByte.Length); return samplesByte; } public bool WriteSamples(byte[] samples, int channel = 0) { if (!_IsWritable || channel < 0 || channel > NumChannels || BitsPerSample != 8) { return false; } var writer = new BinaryWriter(_File); if (channel == 0 || NumChannels == 1) { writer.Write(samples); } else { foreach (var sample in samples) { _File.Seek(channel - 1, SeekOrigin.Current); writer.Write(sample); _File.Seek(NumChannels - channel, SeekOrigin.Current); } } DataSize += samples.Length; return true; } public bool Write16BitSamples(short[] samples, int channel = 0) { if (!_IsWritable || channel < 0 || channel > NumChannels || BitsPerSample != 16) { return false; } var writer = new BinaryWriter(_File); if (channel == 0 || NumChannels == 1) { foreach (var sample in samples) { writer.Write(sample); } } else { foreach (var sample in samples) { _File.Seek((channel - 1) * 2, SeekOrigin.Current); writer.Write(sample); _File.Seek((NumChannels - channel) * 2, SeekOrigin.Current); } } DataSize += samples.Length * 2; return true; } public bool Write16BitSamples(byte[] samples, int channel = 0) { if (samples.Length == 0) { return true; } var samplesShort = new short[samples.Length / 2]; Buffer.BlockCopy(samples, 0, samplesShort, 0, samples.Length); return Write16BitSamples(samplesShort, channel); } private void _WriteHeader() { if (!IsOpen || !_IsWritable) { return; } var writer = new BinaryWriter(_File); writer.Write(Encoding.ASCII.GetBytes("RIFF")); writer.Write(0); writer.Write(Encoding.ASCII.GetBytes("WAVE")); writer.Write(Encoding.ASCII.GetBytes("fmt ")); writer.Write(16); writer.Write((short)1); // PCM writer.Write(NumChannels); // Channels writer.Write(SampleRate); // Sample rate writer.Write(SampleRate * NumChannels * (BitsPerSample / 8)); // Average bytes per second writer.Write((short)(BitsPerSample / 8 * NumChannels)); // block align writer.Write(BitsPerSample); // bits per sample writer.Write(Encoding.ASCII.GetBytes("data")); writer.Write(DataSize); _DataPos = (int)_File.Position; writer.Seek(4, SeekOrigin.Begin); writer.Write((int)writer.BaseStream.Length); writer.Seek(_DataPos, SeekOrigin.Begin); } private static int _SeekTo(BinaryReader reader, string chunkId) { do { var curChunkId = Encoding.ASCII.GetString(reader.ReadBytes(4)); var chunkSize = reader.ReadInt32(); if (curChunkId == chunkId) { return chunkSize; } reader.BaseStream.Seek(chunkSize, SeekOrigin.Current); } while (reader.BaseStream.Position < reader.BaseStream.Length); return -1; } } }