Click here to Skip to main content
16,021,209 members
Articles / Desktop Programming / Win32

C# WAV file class, audio mixing, and some light audio manipulation

Rate me:
Please Sign up or sign in to vote.
4.92/5 (31 votes)
20 Apr 2009CPOL7 min read 302.8K   13K   89   74
A C# class for reading and writing WAV files, light audio manipulation, and WAV file mixing.

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:

Image 1

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.
  • void Create(String pFilename, bool pStereo, int pSampleRate, short pBitsPerSample, bool pOverwrite): Creates a new WAV file.
  • 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 MergeAudioFiles(String[] pFileList, String pOutputFilename, String pTempDir): Merges audio files. This method is 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):

C#
WAVFile audioFile = new WAVFile();
String warning = audioFile.Open("someAudioFile.wav", WAVFile.WAVFileMode.READ);
if (warning == "")
{
   short audioSample = 0;
   for (int sampleNum = 0; sampleNum < audioFile.NumSamples; ++sampleNum)
   {
      audioSample = audioFile.GetNextSampleAs16Bit();
   }
}

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:

C#
// Create an array of filenames for the WAVFile class
String[] audioFilenames = new String[3];
audioFilenames[0] = "SourceFile1.wav";
audioFilenames[1] = "SourceFile2.wav";
audioFilenames[2] = "SourceFile3.wav";
// Mix the audio files together into Output.wav
WAVFile.MergeAudioFiles(audioFilenames, "Output.wav", "C:\\Temp");

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:

History

  • April 20, 2009: Created.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
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).

Comments and Discussions

 
Questioni hope people are still around reading this Pin
Member 1343535628-Sep-17 5:29
Member 1343535628-Sep-17 5:29 
QuestionHeader corrupted Pin
Member 1013282527-Aug-14 9:00
Member 1013282527-Aug-14 9:00 
Questionhow to split wav file into small wav files Pin
kingomer28-Apr-14 4:52
kingomer28-Apr-14 4:52 
QuestionCross-thread operation not valid: Control 'Form1' accessed from a thread other than the thread it was created on. Pin
jatt.net21-Jan-14 0:30
jatt.net21-Jan-14 0:30 
BugDisposing Pin
piojapko13-Oct-13 23:57
piojapko13-Oct-13 23:57 
GeneralMy vote of 5 Pin
Member 998831612-Aug-13 21:55
Member 998831612-Aug-13 21:55 
Questionintroducing a delay in the mix Pin
Member 382194729-Sep-12 19:41
Member 382194729-Sep-12 19:41 
AnswerRe: introducing a delay in the mix Pin
CalicoSkies29-Sep-12 19:47
CalicoSkies29-Sep-12 19:47 
Questionfrequency in wav file Pin
Member 825996228-Nov-11 4:44
Member 825996228-Nov-11 4:44 
AnswerRe: frequency in wav file Pin
CalicoSkies29-Sep-12 19:58
CalicoSkies29-Sep-12 19:58 
GeneralMy vote of 2 Pin
Member 839533012-Nov-11 11:51
Member 839533012-Nov-11 11:51 
GeneralRe: My vote of 2 Pin
CalicoSkies29-Sep-12 19:48
CalicoSkies29-Sep-12 19:48 
QuestionThx for your links. But your code is the worst i have ever seen ;) Pin
Member 839533011-Nov-11 22:21
Member 839533011-Nov-11 22:21 
AnswerRe: Thx for your links. But your code is the worst i have ever seen ;) Pin
CalicoSkies26-Mar-13 4:46
CalicoSkies26-Mar-13 4:46 
AnswerRe: Thx for your links. But your code is the worst i have ever seen ;) Pin
iro4es12-Feb-14 19:12
iro4es12-Feb-14 19:12 
QuestionHow do I fill your WAVFile class with RAW audio? Pin
ExoticmE21-Sep-11 11:18
ExoticmE21-Sep-11 11:18 
AnswerRe: How do I fill your WAVFile class with RAW audio? Pin
CalicoSkies21-Sep-11 15:06
CalicoSkies21-Sep-11 15:06 
QuestionC# SoundPlayer Corrupting MemoryStream Help!! Pin
UNOWN30124-Aug-11 13:18
UNOWN30124-Aug-11 13:18 
AnswerRe: C# SoundPlayer Corrupting MemoryStream Help!! Pin
CalicoSkies24-Aug-11 16:10
CalicoSkies24-Aug-11 16:10 
It sounds like this isn't an issue with the article posted.. This sounds more like a question meant for a general .NET multimedia forum.
QuestionC# - Playing WAV File from Stream Pin
UNOWN30124-Aug-11 6:19
UNOWN30124-Aug-11 6:19 
AnswerRe: C# - Playing WAV File from Stream Pin
UNOWN30124-Aug-11 8:08
UNOWN30124-Aug-11 8:08 
GeneralRe: C# - Playing WAV File from Stream Pin
Member 1013282527-Aug-14 9:26
Member 1013282527-Aug-14 9:26 
GeneralIt was really helpful! Pin
Zhumabek5-Oct-10 4:12
Zhumabek5-Oct-10 4:12 
GeneralRe: It was really helpful! Pin
CalicoSkies5-Oct-10 5:30
CalicoSkies5-Oct-10 5:30 
GeneralBig problem with your code ! Pin
harald witecek6-Sep-10 23:02
harald witecek6-Sep-10 23:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.