How to Normalize a WAV File in C# with NAudio
Every few months someone asks how you can “normalize” an audio file with NAudio. And I’m usually quite reluctant to answer, because often the person asking doesn’t understand the limitations of normalizing. They simply assume it is an automatic way to make a quiet audio file louder.
So what is normalizing an audio file? It’s simply amplifying every sample in the file by the largest amount possible without causing any clipping. That’s great if the entire file is quiet, but if there’s just one sample in there that already has the maximum value, then you’re stuck. Normalization won’t do anything.
But having acknowledged that it doesn’t help in every situation, how can we implement it for files it is suitable for? Well we start by examining every sample value and picking out the loudest. That’s most easily done by letting NAudio convert the samples into floating point in the range +/- 1.0, and the AudioFileReader
class will do that automatically for us.
What we’ll do is read out batches of floating point samples using the Read
method and find the largest value. We’ll use Math.Abs
as the maximum peak might be a negative rather than a positive peak:
var inPath = @"E:\Audio\wav\input.wav";
float max = 0;
using (var reader = new AudioFileReader(inPath))
{
// find the max peak
float[] buffer = new float[reader.WaveFormat.SampleRate];
int read;
do
{
read = reader.Read(buffer, 0, buffer.Length);
for (int n = 0; n < read; n++)
{
var abs = Math.Abs(buffer[n]);
if (abs > max) max = abs;
}
} while (read > 0);
Console.WriteLine($"Max sample value: {max}");
}
So that finds us the maximum value. How can we use that to normalize the file? Well, first of all if the max value is 0 or greater than or equal to 1, then normalization is not possible. But if it is between 0 and 1, then we can multiply each sample by (1 / max)
to get the maximum possible amplification without clipping.
AudioFileReader
has a handy Volume
property that we can use to amplify the samples as they are read out, and since we’ve just read the whole way through, we need to jump back to the beginning by setting Position = 0
. Then we can use the convenience method WaveFileWriter.CreateWaveFile16
to write the amplified audio back to a 16 bit WAV file.
Here’s the entire normalization example:
var inPath = @"E:\Audio\wav\input.wav";
var outPath = @"E:\Audio\wav\normalized.wav";
float max = 0;
using (var reader = new AudioFileReader(inPath))
{
// find the max peak
float[] buffer = new float[reader.WaveFormat.SampleRate];
int read;
do
{
read = reader.Read(buffer, 0, buffer.Length);
for (int n = 0; n < read; n++)
{
var abs = Math.Abs(buffer[n]);
if (abs > max) max = abs;
}
} while (read > 0);
Console.WriteLine($"Max sample value: {max}");
if (max == 0 || max > 1.0f)
throw new InvalidOperationException("File cannot be normalized");
// rewind and amplify
reader.Position = 0;
reader.Volume = 1.0f / max;
// write out to a new WAV file
WaveFileWriter.CreateWaveFile16(outPath, reader);
}
Now, as I said at the beginning, normalization is only good for files that are consistently quiet throughout. But what if your files do have the occasional loud bit? In that case, what you want is a compressor or limiter effect. Compression makes the loudest bits quieter, which means that afterwards you will be able to boost the whole file without clipping. It’s certainly possible to implement a compressor in NAudio, although there isn’t a built-in one, so I’ll cover how to make one in a future post.