|
/* This class has been written by
* Corinna John (Hannover, Germany)
* cj@binary-universe.net
*
* You may do with this code whatever you like,
* except selling it or claiming any rights/ownership.
*
* Please send me a little feedback about what you're
* using this code for and what changes you'd like to
* see in later versions.
*/
using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
namespace WaveMixer {
/// <summary>Stores format and samples of a wave sound.</summary>
public class WaveSound {
private WaveFormat format;
private short[] samples;
/// <summary>Returns the format header information.</summary>
public WaveFormat Format
{
get { return format; }
}
/// <summary>Returns the number of samples.</summary>
public int Count
{
get { return samples.Length; }
}
/// <summary>Returns the wave samples.</summary>
public short[] Samples
{
get { return samples; }
}
/// <summary>Returns the left channel samples.</summary>
public short[] LeftSamples
{
get {
return GetChannelSamples(0);
}
}
/// <summary>Returns the right channel samples.</summary>
public short[] RightSamples
{
get
{
return GetChannelSamples(1);
}
}
/// <summary>Returns the sample at the given position.</summary>
public short this[int indexer]{
get { return samples[indexer]; }
set { samples[indexer] = value; }
}
/// <summary>Constructor.</summary>
/// <param name="format">Format header information.</param>
/// <param name="samples">Wave samples.</param>
public WaveSound(WaveFormat format, short[] samples)
{
this.format = format;
this.samples = samples;
}
/// <summary>Constructor.</summary>
/// <param name="format">Format header information.</param>
/// <param name="samples">Wave samples.</param>
public WaveSound(string fileName)
{
this.ReadFile(fileName);
}
/// <summary>Separates all samples of one channel.</summary>
/// <param name="channelIndex">Index of the channel.</param>
/// <returns>All samples that belong to the requested channel.</returns>
private short[] GetChannelSamples(int channelIndex)
{
short[] channelSamples;
if (format != null)
{
channelSamples = new short[samples.Length / format.Channels];
int channelSamplesIndex = 0;
for (int n = channelIndex; n < samples.Length; n += format.Channels)
{
channelSamples[channelSamplesIndex] = samples[n];
channelSamplesIndex++;
}
}
else
{ //not yet initialized
channelSamples = new short[0];
}
return channelSamples;
}
/// <summary>Creates a stream in RIFF Wave format from the wave information.</summary>
/// <returns>Formatted stream.</returns>
public Stream CreateStream()
{
MemoryStream stream = new MemoryStream();
BinaryWriter writer = new BinaryWriter(stream);
Int32 dataLength = (Int32)(samples.Length * format.BitsPerSample / 8);
writer.Write(System.Text.Encoding.ASCII.GetBytes("RIFF".ToCharArray()));
writer.Write((Int32)(dataLength + 36)); //File length minus first 8 bytes of RIFF description
writer.Write(System.Text.Encoding.ASCII.GetBytes("WAVEfmt ".ToCharArray()));
writer.Write((Int32)16); //length of following chunk: 16
writer.Write(format.FormatTag);
writer.Write(format.Channels);
writer.Write(format.SamplesPerSec);
writer.Write(format.AvgBytesPerSec);
writer.Write(format.BlockAlign);
writer.Write(format.BitsPerSample);
writer.Write(System.Text.Encoding.ASCII.GetBytes("data".ToCharArray()));
writer.Write(dataLength);
for (long n = 0; n < samples.Length; n++) {
if (format.BitsPerSample == 8)
{
writer.Write((byte)samples[n]);
}
else
{
writer.Write(samples[n]);
}
}
writer.Seek(0, SeekOrigin.Begin);
return stream;
}
/// <summary>Reads a wave file.</summary>
/// <param name="fileName">Name and path of the file.</param>
/// <returns>Format and samples.</returns>
private void ReadFile(string fileName)
{
BinaryReader reader = new BinaryReader(new FileStream(fileName, FileMode.Open));
this.format = ReadHeader(reader);
if(format.BitsPerSample != 8 && format.BitsPerSample != 16){
System.Windows.Forms.MessageBox.Show("The sound has " + format.BitsPerSample + " bits per sample. Please choose a sound with 8 or 16 bits per sanmple.");
return;
}
int dataLength = reader.ReadInt32();
int maxSampleValue = 0;
int bytesPerSample = format.BitsPerSample / 8;
int countSamples = dataLength / bytesPerSample;
//sbyte[] channelSamples8 = new sbyte[countSamples];
short[] channelSamples16 = new short[countSamples];
sbyte channelSample8;
int channelSamplesIndex = 0;
int absValue;
for (int sampleIndex = 0; sampleIndex < countSamples; sampleIndex++)
{
if (format.BitsPerSample == 8)
{
channelSample8 = reader.ReadSByte();
channelSamples16[channelSamplesIndex] = (short)(channelSample8 + (channelSample8 << 8));
if (Math.Abs((int)channelSample8) > maxSampleValue)
{
maxSampleValue = Math.Abs((int)channelSample8);
}
}
else
{
channelSamples16[channelSamplesIndex] = reader.ReadInt16();
absValue = Math.Abs((int)channelSamples16[channelSamplesIndex]);
if (absValue > maxSampleValue)
{
maxSampleValue = (absValue > short.MaxValue) ? absValue - 1 : absValue;
}
}
channelSamplesIndex++;
}
reader.Close();
format.BitsPerSample = 16;
//if (format.BitsPerSample == 8)
//{
// for (int n = 0; n < channelSamples8.Length; n++)
// {
// channelSamples16[n] = (short)channelSamples8[n];
// }
//}
this.samples = channelSamples16;
}
/// <summary>Read a chunk of four bytes from a wave file</summary>
/// <param name="reader">Reader for the wave file.</param>
/// <returns>Four characters.</returns>
private string ReadChunk(BinaryReader reader)
{
byte[] ch = new byte[4];
reader.Read(ch, 0, ch.Length);
return System.Text.Encoding.ASCII.GetString(ch);
}
/// <summary>
/// Read the header from a wave file, and move the
/// reader's position to the beginning of the data chunk
/// </summary>
/// <param name="reader">Reader for the wave file.</param>
/// <returns>Format of the wave.</returns>
private WaveFormat ReadHeader(BinaryReader reader)
{
if (ReadChunk(reader) != "RIFF")
throw new Exception("Invalid file format");
reader.ReadInt32(); // File length minus first 8 bytes of RIFF description, we don't use it
if (ReadChunk(reader) != "WAVE")
throw new Exception("Invalid file format");
if (ReadChunk(reader) != "fmt ")
throw new Exception("Invalid file format");
int len = reader.ReadInt32();
if (len < 16) // bad format chunk length
throw new Exception("Invalid file format");
WaveFormat carrierFormat = new WaveFormat();
carrierFormat.FormatTag = reader.ReadInt16();
carrierFormat.Channels = reader.ReadInt16();
carrierFormat.SamplesPerSec = reader.ReadInt32();
carrierFormat.AvgBytesPerSec = reader.ReadInt32();
carrierFormat.BlockAlign = reader.ReadInt16();
carrierFormat.BitsPerSample = reader.ReadInt16();
// advance in the stream to skip the wave format block
len -= 16; // minimum format size
while (len > 0)
{
reader.ReadByte();
len--;
}
// assume the data chunk is aligned
string chunk;
do
{
chunk = ReadChunk(reader);
} while (reader.BaseStream.Position < reader.BaseStream.Length && chunk != "data");
return carrierFormat;
}
/// <summary>Saves the sound to a wave file.</summary>
/// <param name="fileName">Name and path of the destination file.</param>
public void SaveToFile(string fileName)
{
Stream stream = CreateStream();
FileStream file = new FileStream(fileName, FileMode.Create);
int blockLength;
long remainingLength;
while (stream.Position < stream.Length)
{
remainingLength = stream.Length - stream.Position;
if (remainingLength > int.MaxValue)
{
blockLength = int.MaxValue;
}
else
{
blockLength = (int)remainingLength;
}
byte[] buffer = new byte[blockLength];
stream.Read(buffer, 0, blockLength);
file.Write(buffer, 0, blockLength);
}
file.Close();
stream.Close();
}
}
}
|
By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.
If a file you wish to view isn't highlighted, and is a text file (not binary), please
let us know and we'll add colourisation support for it.