0 Comments

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.

Want to get up to speed with the the fundamentals principles of digital audio and how to got about writing audio applications with NAudio? Be sure to check out my Pluralsight courses, Digital Audio Fundamentals, and Audio Programming with NAudio.
Vote on HN
comments powered by Disqus