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.
Comments
is there also simply denoise command?
Paweł Boskyafraid 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 HeathI tried this code for nomalization but unfortunately it did not work. Any suggestions?
Paweł BoskyI have tried this within the code of skrybotdomowy.sf.net
what do you mean it did not work? what were you expecting to happen that didn't?
Mark HeathHi, 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 Caycemost 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 HeathMark 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 CayceIf 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 HeathMark I appreciate your patience here. OK, so I have...
Edgar Caycepublic 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
}
}
Ahhhhh would VSP.ToWaveProvider16() be what I want?
Edgar CayceWell 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 CayceAnd... I did read all of https://www.codeproject.com...
Edgar CayceRawSourceWaveStream 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 HeathMark 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 CayceJust want to offer that I'd pay for help on this one, I'm at edgar at medtek dot net
Edgar CayceHello,
Tony KindschuhI'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
For Linux I'd recommend using something like ffmpeg to convert WAV back into MP3 once you've done the normalization
Mark HeathHello,
Elif ŞahinI 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.