Introduction

One of my interests and hobbies is music. Recently, I created a C# class that could be used to open, read, and write WAV audio files. My WAVFile class supports 8- and 16-bit audio, mono or stereo. One of its special features is a method that will mix WAV audio files together, so that the audio from each source WAV file will be heard simultaneously. Any number of WAV files can be mixed together. The only restriction is that they all need to have the same sample rate. Otherwise, the WAV files can be either mono or stereo and contain 8- or 16-bit audio.

All audio handling and mixing is done within the WAVFile class, and no external libraries are required.

In addition to the WAVFile class, I've included an application for mixing WAV audio files. The compiled application and the full source code are included in the link at the top of the article.

Included Application

The following is a screenshot of the application:

To add WAV files to the list, you can drag & drop files onto the GUI, or choose File > Add Audio File(s).

Prerequisites

The compiled application requires version 3.5 of the .NET runtime. The code provided was created with Visual C# 2008; earlier versions of Visual C# may not be able to open or compile the code.

Background

A WAV audio file consists of a header at the beginning of the file, which contains strings to identify the file type ("RIFF" and "WAVE"), as well as information about the audio contained in the file (number of channels, sample rate, number of bits per channel, size of the data, etc.). Following the header is all of the audio data. Digital audio data is numeric: each sample is an integer that represents the level of the audio signal at that point in time.

One of the issues that needs to be dealt with when mixing audio (and dealing with audio in general) is audio clipping. Digital audio clipping is caused by numeric range limitations of the sample size (8 or 16 bits): when audio samples are manipulated (i.e., if the volume is increased), it's possible for the resulting values to go beyond the numeric range. When that happens, the result (often loud) pops and clicks in the audio, which is undesirable. So, in order to mix WAV files together, MergeAudioFiles() in my WAVFile class will first analyze each audio file to determine the highest audio sample, then reduce the volume in all of the audio files, copying them to a temporary directory. The audio volume is reduced enough so that when the audio samples are added together during the mixing, the numeric range of the data will not be exceeded. After mixing, the volume of the final mix file is increased back up to an acceptable level. The mixing process can take over a minute or two in some cases, which is normal.

Using the Code

The WAVFile class would be a useful class for reading and writing WAV audio files. The class can open, read, and write WAV audio. The following are some of the most important methods (note that they all will throw an exception for a serious or potentially serious error):

string Open(String pFilename, WAVFileMode pMode): Opens an existing WAV file. Returns a string, which will be blank upon success, and upon error, will contain the reason it failed. This method will throw an exception for a more serious error.

byte[] GetNextSample_ByteArray(): When in read or read/write mode, this returns the next sample as a byte array, which will contain one byte for 8-bit audio or 2 bytes for 16-bit audio.

byte GetNextSample_8bit(): This is a convenience method that returns the next audio sample as a byte (8 bits). This is only valid when dealing with 8-bit audio.

short GetNextSample_16bit(): This is a convenience method that returns the next audio sample as a short (16 bits). This is only valid when dealing with 16-bit audio.

short GetNextSampleAs16Bit(): Similar to the above method, except that it will always return a 16-bit value, regardless of whether the audio is 8-bit or 16-bit. Additionally, if the audio is 8-bit, the value will be scaled to 16 bits. For example, if the audio is 8-bit, a value that is 50% of the maximum will still be 50% of the maximum after being scaled to a 16-bit value.

byte GetNextSampleAs8Bit(): Similar to the above method, this will always return the next sample as an 8-bit value, regardless of whether the audio is 8-bit or 16-bit, and 16-bit values will be scaled down to 8 bits.

AddSample_ByteArray(byte[] pSample): When in write or read/write mode, this adds an audio sample to the WAV file. It takes a byte array as a parameter; the array would need to contain one byte for 8-bit audio or 2 bytes for 16-bit audio.

void AddSample_8bit(byte pSample): A convenience method to add an 8-bit value to a WAV file. The WAV file must contain 8-bit audio.

void AddSample_16bit(short pSample): A convenience method to add a 16-bit value to a WAV file. The WAV file must contain 16-bit audio.

void Close(): Closes the audio file.

And, the following are some of the extra utility functions (these are all static):

void AdjustVolume_Copy(String pSrcFilename, String pDestFilename, double pMultiplier): Adjusts the volume of a WAV file, copying it to a new file.

void AdjustVolumeInPlace(String pFilename, double pMultiplier): Adjusts the volume of a WAV file (alters the original file).

void AdjustVolume_Copy_8BitTo16Bit(String pSrcFilename, String pDestFilename, double pMultiplier): Adjusts the volume of a WAV file and converts 8-bit samples to 16-bit samples, copying it to a new file.

void Convert_8BitTo16Bit_Copy(String pSrcFilename, String pDestFilename): Converts an 8-bit WAV file to a 16-bit WAV file, copying it to a new file.

void CopyAndConvert(String pSrcFilename, String pDestFilename, short pBitsPerSample, bool pStereo): Copies a WAV file to a new file, altering the number of bits per sample and/or the number of channels.

These are some of the (read-only) properties that are available:

SampleRateHz: The audio sample rate (in Hz).

BitsPerSample: The number of bits per sample (i.e., 8 or 16).

NumChannels: The number of audio channels - 1 (mono) or 2 (stereo).

IsStereo: Boolean, whether or not the audio is stereo (has 2 channels).

NumSamples: The number of audio samples.

NumSamplesRemaining: The number of audio samples remaining (when reading through an existing WAV file).

An example of opening a WAV file and looping through to get each audio sample (assuming the audio file contains 16-bit audio):

The MergeAudioFiles() method is static, so you don't need a WAVFile instance in order to call it. Its parameters are an array of strings (the source WAV file names), the destination file name, and a directory that can be used as a temporary directory. For example:

Exceptions

The following exceptions (declared in WAVFileExceptions.cs) are thrown by the WAVFile class:

WAVFileReadException: Thrown when there is a problem reading a WAV file

WAVFileWriteException: Thrown when there is a problem writing to a WAV file

WAVFileBitsPerSampleException: Thrown when an unsupported number of bits per sample is encountered. The BitsPerSample property will contain the bits per sample value that is invalid.

WAVFileSampleRateException: Thrown when an unsupported sample rate is encountered. The SampleRate property will contain the sample rate that is invalid.

WAVFileAudioMergeException: Thrown by WAVFile.MergeAudioFiles() when there is a problem mixing WAV files.

WAVFileIOException: Thrown when there is a general I/O problem.

WAVFileException: Thrown when there is a general/unclassified problem.

In addition to the Message property provided by the exception classes, these exception classes provide another property, ThrowingMethodName, which is a string containing the name of the method that threw the exception.

Points of Interest

One interesting thing to note is that data in a WAV audio file is always little-endian, per the specification. On big-endian systems, the byte order must be reversed before manipulating the audio data, and the byte order for a sample must be reversed before saving it to a WAV file. My WAVFile class handles this automatically; for example, if the system is big-endian, then when retrieving audio samples from a WAV file using GetNextSample_16bit() or adding a 16-bit sample to a WAV file using AddSample_16bit(), the bye order will be automatically reversed so that the data is in the proper order.

In creating the WAVFile class, it was necessary to look up the WAV file format specification. I found many web pages describing the WAV file format. Each page has basically the same information, but with different notes. I found the following four pages useful:

Share

About the Author

I was born and raised (and currently still reside in) northwest Oregon, USA. I started using computers at a very young age, maybe 4 or 5 years old, using my dad's computers. I got my own computer when I was 12 (1992). Later, I realized that I really enjoy programming, and in college, I decided to get a degree in software engineering. In 2005, I earned my bachelor's degree in software engineering from Oregon Institute of Technology.

I have been working as a software engineer since October 2003. So far, I've had experience with C++, PHP, JavaScript, Perl, Bash shell scripting, C#, and HTML. I have developed GUIs with MFC, wxWidgets, and WinForms. I also have experience with open-source technologies such as Linux, Apache, MySQL, and PostgreSQL. My favorite Linux text editor is Vim.

In my personal time I enjoy tinkering with computers (building PCs, gaming, occasionally working on personal programming projects), playing music (I enjoy playing guitar and synthesizer/piano), and spending time with family & friends. I also enjoy reading the occasional book (pretty much anything with a good story, but usually sci-fi, drama, programming books, advice books, and scientific non-fiction, such as books written by physicist Michio Kaku). Currently, my favorite TV show is The Office; I also like watching the news when I can. I also enjoy the occasional movie (sci-fi, comedy, action, drama; pretty much anything with a good story).

I am making a software which will do conversion of audio to text and i am stuck in the problem i have to pass chucks of data of wav file to the audio to text conversion function, so kindly help me out by giving me some specific running code example of how to split wav file and pass it to my function
please help me !!!!

When reading a WAV file, the sample rate doesn't need to be calculated - It's written in the WAV file header. It's a 4-byte value stored in the 25th-28th bytes of the WAV file (0-based indexes would be 24-27).

You come from C programming i guess...
If i run your program i get tons of exceptions @ c# 2010 or on your compiled IL exe.
(choosen file already in use) (thread overlap) (choosen file used by another thread) wtf srsly !

All necessary data are stored in seperate variables, cmon its C# not assembler.

Normally i dont write my feedback on code thats 2 years old.
But in this case all i have seen is that abstrusely i just need 2 let u know that i did the same program in tenth part (length) of yourth. If anyone would look at it he would understand that the load function opens the header AND the sampledata. Stores it in different subclasses ready 2 use. just 4 example...

Why u dont use binary writing ? Its a binary file ...

Sry about my bad english. Please fix ur codingstyle!
If i finish my work i pm or email it to you if you want.

It looks like you're trying to add a 16-bit sample but only giving it a single byte. AddSample_16bit() expects a short. Perhaps .NET is expanding that byte to a short; however, I am suspicious about that. Perhaps that is why it is crashing.

Be sure that you are adding audio samples that match the number of bits in the audio file you are creating. If you have created a 16-bit audio file, then be sure you are adding 16-bit samples. If you created an 8-bit audio file, then trying to add a 16-bit audio sample will not work, although it should throw an exception rather than crashing.

Hey guys, I have been doing some experimenting with WAV files and I need to be able to play an WAV file from a stream. What I am going to be doing is opening a file, getting the raw data of the wav file. I do this by using this class provided by NightFox:

But forsome reason when I used this functionto play audio like this:
All it does is say the Header information is corrupted, even though I am using the same method of building a wav file content as the creator of the WAVFile class uses.
Does anyone have any suggestions?

Hey guys. I actually figured out the problem. Apparently when NightFox creates the WAV file from the FileStream in his own class, Windows will automatically change the data length value and total file size value when its created. So he puts 0 in these positions. But when playing directly from a stream, and not creating an actual WAV file, it does not fill these values in automatically, so I had to manually put them in. In this case, the data size is simply the length of my short[] array multiplied by 2. And the file size is that number plus 28.

I am getting an error...."X is inaccessible due to its protection level". huh? Any clue on how to fix this? I am developing an app for a friend so I only need this to make the draft of my program standalone while I develop it.

What version of Visual C# are you using? I developed this with Visual C# 2008, and I suppose it's possible that if you're using a newer version or one of the Express versions, or an older version of Visual C#, you could get some weird errors.

I added your project to mine. Added the references in my project. Added the proper "using" statement. But when I imported it, I did get a comment about a "trusted" app. After that, I went into the security settings of your project but I did not see anything specific that would cause this.

BTW...your code is loaded with explaining comments. Just want you to know how much I appreciate that!

I added your project to mine. Added the references in my project. Added the proper "using" statement. But when I imported it, I did get a comment about a "trusted" app. After that, I went into the security settings of your project but I did not see anything specific that would cause this.

I'm not sure what to say there.. I haven't heard about "trusted" apps before.

C Foley wrote:

BTW...your code is loaded with explaining comments. Just want you to know how much I appreciate that!

Hi dear eric
i used your source code in my project and it sounds great.
actually i have two wave files that are the same as specs(mono, 8000hz, 16-bit).
i want to mix these two files in stereo format, so one file plays in right speaker and one file plays in the left.
so please help me.
regards
saeed

I'm not entirely sure if this will work, but maybe you could use the WAVFile class in this project to create a new audio file that's stereo, 8000Hz, and 16 bits. Then, open both of your source files. Then, start iterating through the audio samples of your source files, first writing a sample from one source file to your destination file, then writing a sample from your other source file, and keep alternating back and forth like that.

hi,
This is my problem too. you're right about iterating L/R but my problem is where you should calculate the bitspersample and the total size of the wave file.
can you or anyone help me plz ?
alalavin.

No, unfortunately, this application can't change the sample rate of a WAV file. To do that properly would be somewhat complicated, and at the time I wrote this, I wasn't familiar with an algorithm to do so (and I'm still not).

No, as it is coded now, it won't change the sample rate. The reason is that changing the sample rate could be tricky if the desired sample rate is not an even multiple of the source sample rate. For instance, you could easily re-sample from 48KHz to 24KHz because 24KHz is exactly half of 48KHz. Otherwise (when the desired sample rate is not an even multiple of the source sample rate), I've heard that sample rate conversion requires an algorithm that can do some interpolation of intermediary audio samples. When I posted this article, I didn't know enough about sample rate conversion to do that, and I hadn't looked in detail into how to do it.

I am just now downloading the class, because I am interested in dinking around with audio right now, and your article was interesting. The one feature I don't see discussed is Saving a WAV. It seems like an oversight since you do provide for the creation of a new file, and for adding samples to a WAV. I am going to want to set the audio samples to a set of bytes that I generate in my own code, so I will probably look into your class to see how easy that would be to do. Besides saving, it would also be helpful to be able to set entire swaths of samples from a buffer: a writeSamples(Byte[]) method (or something like that.

I see that your sample app attempts to actually mix the files into a new output file. Unfortunately, when I try to mix a couple of files with the app (using VS 2008), I get this exception:

System.InvalidOperationException was unhandled
Message="Cross-thread operation not valid: Control 'Form1' accessed from a thread other than the thread it was created on."

In any case, at least the WaveFile class provides more of the implementation I was looking for than I expected from the article. I thought the mixing was just rendered to the soundcard, not to an output file. I haven't used VS for sometime, but I am used to seeing that same sort of threading restriction in the SWT in Java, which also restricts UI access from a non-UI thread.

Yes, it does save to WAV as well. The process of saving a WAV file with my class is this:
- Create a WAV file using the Create() method
- Add some audio samples to it
- Close the file with the Close() method