Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a .wav File in C#

Tags:

c#

wav

As an excuse to learn C#, I have been trying to code a simple project: creating audio files. To start, I want to make sure that I can write files that meet the WAVE format. I have researched the format online (for example, here), but whenever I try to play back a file, it won't open correctly. Here is my code. Is something missing or incorrect?

uint numsamples = 44100;
ushort numchannels = 1;
ushort samplelength = 1; // in bytes
uint samplerate = 22050;

FileStream f = new FileStream("a.wav", FileMode.Create);
BinaryWriter wr = new BinaryWriter(f);

wr.Write("RIFF");
wr.Write(36 + numsamples * numchannels * samplelength);
wr.Write("WAVEfmt ");
wr.Write(16);
wr.Write((ushort)1);
wr.Write(numchannels);
wr.Write(samplerate);
wr.Write(samplerate * samplelength * numchannels);
wr.Write(samplelength * numchannels);
wr.Write((ushort)(8 * samplelength));
wr.Write("data");
wr.Write(numsamples * samplelength);

// for now, just a square wave
Waveform a = new Waveform(440, 50);

double t = 0.0;
for (int i = 0; i < numsamples; i++, t += 1.0 / samplerate)
{
    wr.Write((byte)((a.sample(t) + (samplelength == 1 ? 128 : 0)) & 0xff));
}
like image 612
user2034752 Avatar asked Feb 02 '13 07:02

user2034752


People also ask

How is a WAV file structured?

WAV File Format The WAVE file format, being a subset of Microsoft's RIFF specification, starts with a file header followed by a sequence of data chunks. A WAVE file has a single “WAVE” chunk which consists of two sub-chunks: a “fmt” chunk - specifies the data format. a “data” chunk - contains the actual sample data.


4 Answers

The major problem is:

BinaryWriter.Write(string) writes a string that is prefixed with it's length for BinaryReader to read it back. It is not intended to be used like your case. You need to write the bytes directly instead of using BinaryWriter.Write(string).

What you should do:

Convert the string into bytes and then write the bytes directly.

byte[] data = System.Text.Encoding.ASCII.GetBytes("RIFF");
binaryWriter.Write(data);

or make it one line:

binaryWriter.Write(System.Text.Encoding.ASCII.GetBytes("RIFF"));

There may also be other problems, like the integers you are writing may not be of the same size as required. You should check them carefully.

As for endianess, the link you put states that data are in little-endian and BinaryWriter uses little-endian, so this should not be a problem.

like image 55
Alvin Wong Avatar answered Oct 08 '22 17:10

Alvin Wong


The simplest way possible, you can simply change:

wr.Write("RIFF");

to:

wr.Write("RIFF".ToArray());

Writing a string in a binary file, it will include the length of the string so that it can be deserialized back into a string later. In this case you just want the four bytes to be written as four bytes, and converting it to a char array will do just that.

like image 23
Dave Cousineau Avatar answered Oct 08 '22 18:10

Dave Cousineau


I lack the proper WAV data, but try replacing the part of your code where you generate the header with this code (replace appropriately):

wr.Write(Encoding.ASCII.GetBytes("RIFF"));
wr.Write(0);
wr.Write(Encoding.ASCII.GetBytes("WAVE"));
wr.Write(Encoding.ASCII.GetBytes("fmt "));
wr.Write(18 + (int)(numsamples * samplelength));
wr.Write((short)1); // Encoding
wr.Write((short)numchannels); // Channels
wr.Write((int)(samplerate)); // Sample rate
wr.Write((int)(samplerate * samplelength * numchannels)); // Average bytes per second
wr.Write((short)(samplelength * numchannels)); // block align
wr.Write((short)(8 * samplelength)); // bits per sample
wr.Write((short)(numsamples * samplelength)); // Extra size
wr.Write("data");
like image 3
Jason Avatar answered Oct 08 '22 16:10

Jason


@Alvin-wong answer works perfect. Just wanted to add another suggestion although a few more lines is:

binaryWriter.Write('R');
binaryWriter.Write('I');
binaryWriter.Write('F');
binaryWriter.Write('F'); 
like image 1
DannyC Avatar answered Oct 08 '22 16:10

DannyC