using System; using System.Collections.Generic; using System.Text; using System.IO; namespace sndutil { /// /// Writes a *mono* WAV file. Supports different sample formats: /// * PCM08: 08-bit Linear PCM /// * PCM16: 16-bit Linear PCM /// * PCM32: 24-bit Linear PCM /// * FLT32: 32-bit Floating Point (IEEE 754) /// /// In the case of PCM format, the input samples are clipped betwen [-1, +1) prior /// to convert them in 8, 16 or 24 bit integer data types. Therefore the precision /// in PCM formats get larger with number of bits but the dynamic range is limited /// between [-1, +1). If you want to have to record signals with a greater dynamic /// range and better SNR use FLT32 format. /// /// References: /// http://www.sonicspot.com/guide/wavefiles.html /// public class WavFileWriter { public enum Format { /// 08-bit Linear Unsigned Integer PCM PCM08, /// 16-bit Linear Signed Integer PCM PCM16, /// 32-bit Linear Signed Integer PCM PCM24, /// 32-bit Floating Point IEEE 754 FLT32, } private Format _format; private int _fs; private string _filename; // if aplicable private Stream _stream; // generic stream with CanSeek := true private BinaryWriter _writer; private uint _sampleCount; private bool _closed; private bool _clipped; // Any sample was clipped? // WAV File Header, 44 bytes private byte[] _hdr_chunkID; // "riff" little-endian! private uint _hdr_chunkSize; // private byte[] _hdr_format; // "wave" little-endian! private byte[] _hdr_subchunk1ID; // "fmt " little-endian! private uint _hdr_subchunk1Size; private ushort _hdr_audioFormat; private ushort _hdr_numChannels; private uint _hdr_sampleRate; private uint _hdr_byteRate; private ushort _hdr_blockAlign; private ushort _hdr_bitsPerSample; private byte[] _hdr_subchunk2ID; // "data" little-endian! private uint _hdr_subchunk2Size; #region Constructors /// /// Constructor /// /// File name created wiwth FileMode.Create (OVERWRITE MODE!) /// Sample format /// Sampling frequency public WavFileWriter(string filename, Format format, int fs) { _filename = filename; // FileMode.Create => Overwrite! // FileMode.CreateNew => Don't overwrite! _stream = new FileStream(_filename, FileMode.Create); //_stream = new FileStream(_filename, FileMode.CreateNew); Constructor(_stream, format, fs); } /// /// Constructor /// /// Any Stream Class derived object with the property CanSeek set to true /// like files and memory streams but it isn't valid sockets, serial ports or the like streams /// Sample format /// Sampling frequency public WavFileWriter(Stream stream, Format format, int fs) { Constructor(stream, format, fs); } /// /// Constructor /// /// Access type; for writting or reading /// File name to CREATE for write private void Constructor(Stream stream, Format format, int fs) { _stream = stream; _format = format; _fs = fs; _sampleCount = 0; _closed = false; _clipped = false; _writer = new BinaryWriter(_stream); // Make space for WAV file, written later... _stream.Seek(44, SeekOrigin.Begin); // 44 bytes } #endregion /// /// Writes audio samples in the WAV file. If the sample format is PCM then a clip /// operation between (+1, -1) must be performed because samples outside this range /// can't be represented with this fixed point format. /// /// Array with data /// Offset in the array /// Number of samples that will be written public int XferData(double[] buffer, int offset, int count) { int i; // Audio data must be between [-1, +1] for PCM formats. If not, must be clipped // thus, in PCM format, the dynamic range in limited to [-1, +1) if ((_format == Format.PCM08) || (_format == Format.PCM16) || (_format == Format.PCM24)) { for (i = offset; i < offset + count; i++) { if (buffer[i] > +1.0) { buffer[i] = +1.0; _clipped = true; } if (buffer[i] < -1.0) { buffer[i] = -1.0; _clipped = true; } } } // samples in different formats byte s08; short s16; int s24; byte s24_1; byte s24_2; byte s24_3; float s32; int items = 0; for (i = offset; i < offset + count; i++) { switch (_format) { case Format.PCM08: s08 = (byte) (((1 + buffer[i]) / 2) * 255); _writer.Write(s08); break; case Format.PCM16: s16 = (short) (buffer[i] * 0032767); _writer.Write(s16); break; case Format.PCM24: // only LSB 24-bit take effect! s24 = (int) (buffer[i] * 8388607); s24_1 = (byte) ((s24 & 0x000000FF) ); s24_2 = (byte) ((s24 & 0x0000FF00) >> 08); s24_3 = (byte) ((s24 & 0x00FF0000) >> 16); // WAV File Format => little endian! _writer.Write(s24_1); _writer.Write(s24_2); _writer.Write(s24_3); break; case Format.FLT32: s32 = (float) (buffer[i]); _writer.Write(s32); break; } _sampleCount++; items++; } return items; } #region Properties public bool Closed { get { return _closed; } } public bool Clipped { get { return _clipped; } } public Stream Stream { get { return _stream; } } public Format SampleFormat { get { return _format; } } public int Fs { get { return _fs; } } #endregion /// /// Finish writting the file header to achieve a coherent WAV header and then close the /// opened stream (file) /// public void Close() { // WAV file header fields switch (_format) { case Format.PCM08: // 8-bit samples are always stores as UNSIGNED (centered at 128) _hdr_audioFormat = (ushort)0x0001; _hdr_bitsPerSample = (ushort)08; break; case Format.PCM16: _hdr_audioFormat = (ushort)0x0001; _hdr_bitsPerSample = (ushort)16; break; case Format.PCM24: _hdr_audioFormat = (ushort)0x0001; _hdr_bitsPerSample = (ushort)24; break; case Format.FLT32: _hdr_audioFormat = (ushort)0x0003; _hdr_bitsPerSample = (ushort)32; break; } ASCIIEncoding ascii = new ASCIIEncoding(); _hdr_chunkID = ascii.GetBytes("RIFF"); _hdr_format = ascii.GetBytes("WAVE"); _hdr_subchunk1ID = ascii.GetBytes("fmt "); _hdr_subchunk2ID = ascii.GetBytes("data"); _hdr_subchunk1Size = 16; _hdr_numChannels = 1; _hdr_sampleRate = (uint) _fs; _hdr_byteRate = _hdr_sampleRate * _hdr_numChannels * _hdr_bitsPerSample / 8; _hdr_blockAlign = (ushort)(_hdr_numChannels * _hdr_bitsPerSample / 8); _hdr_subchunk2Size = _sampleCount * _hdr_numChannels * _hdr_bitsPerSample / 8; _hdr_chunkSize = 36 + _hdr_subchunk2Size; // Go back and write WAV file header _stream.Seek(0, SeekOrigin.Begin); _writer.Write(_hdr_chunkID); _writer.Write(_hdr_chunkSize); _writer.Write(_hdr_format); _writer.Write(_hdr_subchunk1ID); _writer.Write(_hdr_subchunk1Size); _writer.Write(_hdr_audioFormat); _writer.Write(_hdr_numChannels); _writer.Write(_hdr_sampleRate); _writer.Write(_hdr_byteRate); _writer.Write(_hdr_blockAlign); _writer.Write(_hdr_bitsPerSample); _writer.Write(_hdr_subchunk2ID); _writer.Write(_hdr_subchunk2Size); _writer.Close(); _stream.Close(); _closed = true; } } }