# Normalizing audio waveforms code implementation (Peak, RMS)

• I have some audio data (array of floats) which I use to plot a simple waveform.
• When plotted, the waveform doesn't max out at the edges.
• No problem - the data just needs to be normalized. I iterate once to find the max, and then iterate again dividing each by the max. Plot again and everything looks great!
• But wait videos which have a loud intro, or loud explosion, causes the rest of the waveform to still be tiny.
• After some research, I come across RMS that is supposed to address this. I iterate through the samples and calculate the RMS, and again divide each sample by the RMS value. This results in considerable "clipping":

• What is the best method to solve this?
• Intuitively, it seems I might need to calculate a local max or average based on a moving window (rather than the entire set) but I'm not entirely sure. Help?
• Note: The waveform is purely for visual purposes (the audio will not be played back to the user).

• What is the best method to solve this?
• Intuitively, it seems I might need to calculate a local max or average based on a moving window (rather than the entire set) but I'm not entirely sure. Help?

What you are describing with your second of these two quoted bullet points is Automatic Gain Control, which eventually will take us to compression. What you might find useful in your case, i.e for visual purposes only, is a simple logarithmic transformation which is very close to what compression is about but without the complex controls it demands.

The most common way to solve such problems in the audio world is to use a compressor.

The compressor is monitoring the amplitude of the incoming signal. If that amplitude crosses a user-defined threshold it decreases the amplification in an attempt to keep the signal within certain limits. As you can understand, imposing such limitations to a signal without special care could result in annoying audible artifacts. For this purpose, the compressor has an additional set of controls called "Attack" and "Release". These are two parameters that control how fast does the compressor impose its limitation once the threshold has been crossed (Attack) and how fast does the compressor return to the initial amplification of 1:1 after the amplitude of the signal has returned below the predefined threshold. These three parameters, the threshold and attack and release times are what make the compressor "complex" in setting it up properly. Extreme values in these settings can result in more artifacts.

You may have "heard" a compressor working in radio shows. It is the effect that you hear when a speaker suddenly stops talking and after a very brief period of time the background sounds seem to rise in volume. When the speaker starts talking again, the background sounds seem to be diminishing abruptly again and the speaker's voice now dominates the dynamic range of the signal. That's the effect of a compressor.

The main idea here is that the compressor is making soft sounds louder and loud sounds softer.

Note: The waveform is purely for visual purposes (the audio will not be played back to the user).

If you are not looking to actually apply dynamic range compression and you simply want to make sure that your viewer can see all details within a signal, you can apply a logarithmic transformation, which is very similar to how a compressor / limiter would operate but without the complexity of setting it up.

Given a signal $x(n)$, you can generate a signal $x_{transformed}$ with:

$$x_{transformed}(n) = c \times log(1+x(n))$$

Where $c$ is a normalisation parameter. Due to its shape the $log(\cdot)$ function will boost small amplitude values and limit high amplitude values. To get a feel about how the waveform might look like, you might want to have a quick look at the way Audacity does its 'dB View' of waveforms.

You might find both the compression and logarithmic transformation concepts useful to what you are trying to achieve.

Hope this helps.

• Using log() to taper off the extremes and bring up the smaller values is brilliant. Thank you. Can you elaborate a bit on what "c" is? @A_A – Andy Hin Sep 24 '17 at 18:37
• Glad you thought it was helpful. $c$ is simply a scaling parameter. Obviously, after applying the transformation, the max / min values will change. So, you can calculate a $c$ to bring the transformed waveform within specific limits (e.g. between 1 and -1). For example, $c=\frac{1}{max(x)}$ which is exactly the same as the normalisation you already apply (but now, instead, apply it on $x_{transformed}$). (@AndyHin – A_A Sep 24 '17 at 20:08
• Perfect, that's what I figured. So I implemented this, and it does seem to work properly. However, the logarithmic scaling seems to have only a tiny effect. I believe it is due to the range of my sample values (they are between 0 -> 1) - do I need to modify the log curve to match these ranges? My code if you are curious: pastebin.com/W61TBdrN – Andy Hin Sep 24 '17 at 20:19
• You might want to log(1+ a * oldValue), where a is yet another scaling parameter, but ultimately depends on signal. Do the max pass first, then coef=1/log(max) and in the second pass newValue=log(oldvalue)*coef. Obviously, the log arguments adapted for whatever calculation you settle with in the end. Any particular reason why log2? (BTW, if oldvalue<0 you will get complex logs) – A_A Sep 24 '17 at 20:43