Posted in:

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.

Comments

Comment by Paweł Bosky

is there also simply denoise command?

Paweł Bosky
Comment by Mark Heath

afraid not - noise removal is quite tricky. A noise gate would be a good place to start, but NAudio doesn't have one built in

Mark Heath
Comment by Paweł Bosky

I tried this code for nomalization but unfortunately it did not work. Any suggestions?
I have tried this within the code of skrybotdomowy.sf.net

Paweł Bosky
Comment by Mark Heath

what do you mean it did not work? what were you expecting to happen that didn't?

Mark Heath
Comment by Edgar Cayce

Hi, have been trying to adapt this so it works with audio in a memorystream, which could be in MP3, WAV or GSM format, but I am stumbling, can you give me any pointers? I would like to write back out to a stream in same format.

Edgar Cayce
Comment by Mark Heath

most of the readers in NAudio support reading from a memory stream (WavFileReader, Mp3FileReader), but AudioFileReader doesn't (because of MediaFoundationReader). So just use the lower-level reader for your file type

Mark Heath
Comment by Edgar Cayce

Mark thx so much for your reply. That's what I thought, but they're different - for instance, WaveFileReader.Read() reads bytes instead of floats, and does not have a Volume member. What am I missing here?

Edgar Cayce
Comment by Mark Heath

If you look at the code for AudioFileReader, it is just adding decorators on top - e.g. use ToSampleProvider to get to floats, and VolumeSampleProvider to have a volume property. You just need to rewind directly on the WaveFileReader after you've found the peak volume

Mark Heath
Comment by Edgar Cayce

Mark I appreciate your patience here. OK, so I have...

public static MemoryStream NormalizeAudio(MemoryStream SourceAudio)
{
float MaxLevel = 0;

using (var reader = new WaveFileReader(SourceAudio))
{

ISampleProvider FloatSP = reader.ToSampleProvider();

// find the max peak
float[] buffer = new float[reader.WaveFormat.SampleRate];
int read;
do
{
read = FloatSP.Read(buffer, 0, buffer.Length);
for (int n = 0; n < read; n++)
{
var abs = Math.Abs(buffer[n]);
if (abs > MaxLevel) MaxLevel = abs;
}
} while (read > 0);
Console.WriteLine($"Max sample value: {MaxLevel}");

if (MaxLevel == 0 || MaxLevel > 1.0f)
throw new InvalidOperationException("File cannot be normalized");

// rewind and amplify
reader.Position = 0;
VolumeSampleProvider VSP = new VolumeSampleProvider(FloatSP);
VSP.Volume = 1.0f / MaxLevel;

// write out to a new WAV file
MemoryStream ConvertedAudio = new MemoryStream();

// here's where I am stuck! how to use the Volume Stream Provider to write out with increased volume?
WaveFileWriter.WriteWavFileToStream(ConvertedAudio, VSP); // VSP is not right type for this
}
}

Edgar Cayce
Comment by Edgar Cayce

Ahhhhh would VSP.ToWaveProvider16() be what I want?

Edgar Cayce
Comment by Edgar Cayce

Well that did work and I got it all working for GSM encoded WAV but for the life of me I can't figure out how to open and work with raw GSM files. I tried using RawSourceWaveStream with Gsm610WaveFormat and then WaveFormatConversionStream.CreatePcmStream and it gives no error but all I get is gibberish. Even when I just try to open a file and play it with NAudio I get the same thing. SOX has no trouble with the file but... I have been googling all day to no avail here. Any pointers on how to work with a raw .GSM file?

Edgar Cayce
Comment by Mark Heath

RawSourceWaveStream is the right thing to be using if you have raw compressed audio without a header. But you have to be sure that there is no header, and that you know the exact wave format.

Mark Heath
Comment by Edgar Cayce

Mark again thanks so much for the reply. I made a post on Stack Overflow here https://stackoverflow.com/q... trying to get some help. From what I can tell it's a GSM encoded file - that's what VLC tells me about it. Also trying the example from here https://www.online-convert.... . Looking at it in a hex editor does not reveal anything that looks like a header.

Edgar Cayce
Comment by Edgar Cayce

Just want to offer that I'd pay for help on this one, I'm at edgar at medtek dot net

Edgar Cayce
Comment by Tony Kindschuh

Hello,
I've tried the code above to normalize some user uploaded mp3 files for my site, and I was feeling great because it worked wonderful, but I still had to convert the outputted wave file back to mp3. So I watched some of your Pluralsight videos about using encoders to convert to MP3. After watching those, I found out that a lot of NAudio is (or was) dependent on the Windows OS. Problem is while I'm debugging on Windows, I'm actually deploying my website using a Linux container. So I'm wondering, if NAudio will work on a Linux machine and what I read was just outdated, or if you may have some tips on how I can normalize user uploaded audio in my .net core service running on Linux?
Thank you,
Tony

Tony Kindschuh
Comment by Mark Heath

For Linux I'd recommend using something like ffmpeg to convert WAV back into MP3 once you've done the normalization

Mark Heath
Comment by Elif Şahin

Hello,
I want to get your opinion on a subject. We use acoustic sensor in the project we work on. Data from this sensor to our system; It transfers min, max and average spectrogram values onto a single spectrogram graph. Spektogram consists of frequency and dB axes. Our goal is to get the sound output from the spectrogram. But we do not know which method to use while doing this. Could the Naudio library allow us to make such an application? Are there any projects in the past related to this? Could you give us an idea about this subject?
Thank you for your interest.

Elif Şahin