using System;
using System.IO;
namespace Audio
{
///
/// This class writes audio data to a .aif file on disk
///
public class AiffFileWriter : Stream
{
private Stream outStream;
private BinaryWriter writer;
private long dataSizePos;
private long commSampleCountPos;
private int dataChunkSize = 8;
private NAudio.Wave.WaveFormat format;
private string filename;
///
/// Creates an Aiff file by reading all the data from a WaveProvider
/// BEWARE: the WaveProvider MUST return 0 from its Read method when it is finished,
/// or the Aiff File will grow indefinitely.
///
/// The filename to use
/// The source WaveProvider
public static void CreateAiffFile(string filename, NAudio.Wave.WaveStream sourceProvider)
{
using (AiffFileWriter writer = new AiffFileWriter(filename, sourceProvider.WaveFormat))
{
byte[] buffer = new byte[16384];
while (sourceProvider.Position < sourceProvider.Length)
{
int count = Math.Min((int)(sourceProvider.Length - sourceProvider.Position), buffer.Length);
int bytesRead = sourceProvider.Read(buffer, 0, count);
if (bytesRead == 0)
{
// end of source provider
break;
}
writer.Write(buffer, 0, bytesRead);
}
}
}
///
/// AiffFileWriter that actually writes to a stream
///
/// Stream to be written to
/// Wave format to use
public AiffFileWriter(Stream outStream, NAudio.Wave.WaveFormat format)
{
this.outStream = outStream;
this.format = format;
this.writer = new BinaryWriter(outStream, System.Text.Encoding.ASCII);
this.writer.Write(System.Text.Encoding.ASCII.GetBytes("FORM"));
this.writer.Write((int)0); // placeholder
this.writer.Write(System.Text.Encoding.ASCII.GetBytes("AIFF"));
CreateCommChunk();
WriteSsndChunkHeader();
}
///
/// Creates a new AiffFileWriter
///
/// The filename to write to
/// The Wave Format of the output data
public AiffFileWriter(string filename, NAudio.Wave.WaveFormat format)
: this(new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.Read), format)
{
this.filename = filename;
}
private void WriteSsndChunkHeader()
{
this.writer.Write(System.Text.Encoding.ASCII.GetBytes("SSND"));
dataSizePos = this.outStream.Position;
this.writer.Write((int)0); // placeholder
this.writer.Write((int)0); // zero offset
this.writer.Write(SwapEndian((int)format.BlockAlign));
}
private byte[] SwapEndian(short n)
{
return new byte[] { (byte)(n >> 8), (byte)(n & 0xff) };
}
private byte[] SwapEndian(int n)
{
return new byte[] { (byte)((n >> 24) & 0xff), (byte)((n >> 16) & 0xff), (byte)((n >> 8) & 0xff), (byte)(n & 0xff), };
}
private void CreateCommChunk()
{
this.writer.Write(System.Text.Encoding.ASCII.GetBytes("COMM"));
this.writer.Write(SwapEndian((int)18));
this.writer.Write(SwapEndian((short)format.Channels));
commSampleCountPos = this.outStream.Position; ;
this.writer.Write((int)0); // placeholder for total number of samples
this.writer.Write(SwapEndian((short)format.BitsPerSample));
this.writer.Write(IEEE.ConvertToIeeeExtended(format.SampleRate));
}
///
/// The aiff file name or null if not applicable
///
public string Filename
{
get { return filename; }
}
///
/// Number of bytes of audio in the data chunk
///
public override long Length
{
get { return dataChunkSize; }
}
///
/// WaveFormat of this aiff file
///
public NAudio.Wave.WaveFormat WaveFormat
{
get { return format; }
}
///
/// Returns false: Cannot read from a AiffFileWriter
///
public override bool CanRead
{
get { return false; }
}
///
/// Returns true: Can write to a AiffFileWriter
///
public override bool CanWrite
{
get { return true; }
}
///
/// Returns false: Cannot seek within a AiffFileWriter
///
public override bool CanSeek
{
get { return false; }
}
///
/// Read is not supported for a AiffFileWriter
///
public override int Read(byte[] buffer, int offset, int count)
{
throw new InvalidOperationException("Cannot read from an AiffFileWriter");
}
///
/// Seek is not supported for a AiffFileWriter
///
public override long Seek(long offset, SeekOrigin origin)
{
throw new InvalidOperationException("Cannot seek within an AiffFileWriter");
}
///
/// SetLength is not supported for AiffFileWriter
///
///
public override void SetLength(long value)
{
throw new InvalidOperationException("Cannot set length of an AiffFileWriter");
}
///
/// Gets the Position in the AiffFile (i.e. number of bytes written so far)
///
public override long Position
{
get { return dataChunkSize; }
set { throw new InvalidOperationException("Repositioning an AiffFileWriter is not supported"); }
}
///
/// Appends bytes to the AiffFile (assumes they are already in the correct format)
///
/// the buffer containing the wave data
/// the offset from which to start writing
/// the number of bytes to write
public override void Write(byte[] data, int offset, int count)
{
byte[] swappedData = new byte[data.Length];
int align = format.BitsPerSample / 8;
for (int i = 0; i < data.Length; i++)
{
int pos = (int)Math.Floor((double)i / align) * align + (align - (i % align) - 1);
swappedData[i] = data[pos];
}
outStream.Write(swappedData, offset, count);
dataChunkSize += count;
}
private byte[] value24 = new byte[3]; // keep this around to save us creating it every time
///
/// Writes a single sample to the Aiff file
///
/// the sample to write (assumed floating point with 1.0f as max value)
public void WriteSample(float sample)
{
if (WaveFormat.BitsPerSample == 16)
{
writer.Write(SwapEndian((Int16)(Int16.MaxValue * sample)));
dataChunkSize += 2;
}
else if (WaveFormat.BitsPerSample == 24)
{
var value = BitConverter.GetBytes((Int32)(Int32.MaxValue * sample));
value24[2] = value[1];
value24[1] = value[2];
value24[0] = value[3];
writer.Write(value24);
dataChunkSize += 3;
}
else if (WaveFormat.BitsPerSample == 32 && WaveFormat.Encoding == NAudio.Wave.WaveFormatEncoding.Extensible)
{
writer.Write(SwapEndian(UInt16.MaxValue * (Int32)sample));
dataChunkSize += 4;
}
else
{
throw new ApplicationException("Only 16, 24 or 32 bit PCM or IEEE float audio data supported");
}
}
///
/// Writes 32 bit floating point samples to the Aiff file
/// They will be converted to the appropriate bit depth depending on the WaveFormat of the AIF file
///
/// The buffer containing the floating point samples
/// The offset from which to start writing
/// The number of floating point samples to write
public void WriteSamples(float[] samples, int offset, int count)
{
for (int n = 0; n < count; n++)
{
WriteSample(samples[offset + n]);
}
}
///
/// Writes 16 bit samples to the Aiff file
///
/// The buffer containing the 16 bit samples
/// The offset from which to start writing
/// The number of 16 bit samples to write
public void WriteSamples(short[] samples, int offset, int count)
{
// 16 bit PCM data
if (WaveFormat.BitsPerSample == 16)
{
for (int sample = 0; sample < count; sample++)
{
writer.Write(SwapEndian(samples[sample + offset]));
}
dataChunkSize += (count * 2);
}
// 24 bit PCM data
else if (WaveFormat.BitsPerSample == 24)
{
byte[] value;
for (int sample = 0; sample < count; sample++)
{
value = BitConverter.GetBytes(UInt16.MaxValue * (Int32)samples[sample + offset]);
value24[2] = value[1];
value24[1] = value[2];
value24[0] = value[3];
writer.Write(value24);
}
dataChunkSize += (count * 3);
}
// 32 bit PCM data
else if (WaveFormat.BitsPerSample == 32 && WaveFormat.Encoding == NAudio.Wave.WaveFormatEncoding.Extensible)
{
for (int sample = 0; sample < count; sample++)
{
writer.Write(SwapEndian(UInt16.MaxValue * (Int32)samples[sample + offset]));
}
dataChunkSize += (count * 4);
}
else
{
throw new ApplicationException("Only 16, 24 or 32 bit PCM audio data supported");
}
}
///
/// Ensures data is written to disk
///
public override void Flush()
{
writer.Flush();
}
#region IDisposable Members
///
/// Actually performs the close,making sure the header contains the correct data
///
/// True if called from Dispose
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (outStream != null)
{
try
{
UpdateHeader(writer);
}
finally
{
// in a finally block as we don't want the FileStream to run its disposer in
// the GC thread if the code above caused an IOException (e.g. due to disk full)
outStream.Close(); // will close the underlying base stream
outStream = null;
}
}
}
}
///
/// Updates the header with file size information
///
protected virtual void UpdateHeader(BinaryWriter writer)
{
this.Flush();
writer.Seek(4, SeekOrigin.Begin);
writer.Write(SwapEndian((int)(outStream.Length - 8)));
UpdateCommChunk(writer);
UpdateSsndChunk(writer);
}
private void UpdateCommChunk(BinaryWriter writer)
{
writer.Seek((int)commSampleCountPos, SeekOrigin.Begin);
writer.Write(SwapEndian((int)(dataChunkSize * 8 / format.BitsPerSample / format.Channels)));
}
private void UpdateSsndChunk(BinaryWriter writer)
{
writer.Seek((int)dataSizePos, SeekOrigin.Begin);
writer.Write(SwapEndian((int)dataChunkSize));
}
///
/// Finaliser - should only be called if the user forgot to close this AiffFileWriter
///
~AiffFileWriter()
{
System.Diagnostics.Debug.Assert(false, "AiffFileWriter was not disposed");
Dispose(false);
}
#endregion
}
}