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;
}
}
}