Usage of 32bit float headroom

Hello! Did you read this?
https://www.sounddevices.com/sample-32-bit-float-and-24-bit-fixed-wav-files/

I think, the equivalent functionality in audacity is effects-amplify -26db to restore file uncipped.

The max level now is 0 and clipping is fixed but
I see audacity still shows graphically the quietest (0 level) is about -144db.
So it seems amplify acts as a compressor which attenuates loudest sounds to 0db but leaves the quetest ones at -144db.
Is it the case? Or in reality it attenuates also the lower border to -26db so it becomes about -144-26=-170db?

We know that 32bit float can store this low value, since 0 voltage level is -758 db.
Also in this case would be nice to have a plugin which shows not only the max (peak) values like Find Peaks, but also Find Mins.
If after amlification-attenuation audacity really preserves the dynamic range and saves it back in 32bit float wav, what will happen if I bouce this file to 24 bit signed integer wav which cannot keep dynamic range 170 db?

Where are you seeing that?

I’m guessing that you have switched the track’s vertical scale to show “dB”, in which case it is simply that the zoom level is restricted to a minimum of -145 dB. The actual audio can go down to -infinity dB (absolute silence).


0 voltage is -inf dB (negative infinity).


Perhaps not as useful as one might intuitively think.
Waveforms generally oscillate around zero (zero voltage), so twice on each oscillation the waveform is either at, or very close to zero voltage.

In the simple case, when converting from a higher bit depth to a lower bit depth, (such as 32-bit float to 24-bit integer, or 24-bit integer to 16-bit integer), the values are rounded to exact values that can be expressed by the new format. This would create a stepped waveform like this (greatly magnified):


However, high quality audio processing apps rarely do this because the “steps” create unpleasant and unmusical harmonics, which is called “quantization noise”.

To avoid this problem, conversion to a lower bit depth is usually done by adding a small amount of randomization to the values, which is called “dither”. The randomization is carefully calculated to minimize audible noise, which is called “shaped dither”.

This shows a waveform with dither applied (greatly magnified), which looks more messy than the stepped shape without dither, but subjectively sounds better.


An interesting aspect of dither is that it actually increases the available dynamic range for a give bit depth. For example, for 16-bit, the maximum dynamic range without dither is 90 or 96 dB (depending on how you measure it), but with dither it is possible to reproduce sounds well below -100 dB (albeit with some noise).


If you are interested, there’s more information about dither in the manual: Dither - Audacity Manual

Where are you seeing that?

Yes, when switched to db scale. In that mode center is marked as -145db which corresponds to mininum (0 or very close to 0 value of wav-data of 24bit int wav-file).

0 voltage is -inf dB (negative infinity).

Ok 0-value (not 0 voltage) in wav-data corresponds to approx -758db for 32bit float wav-file.
In db-scale view, instead of -145db mark I should see the -XXXdb value which corresponds to min value in wav-data (if 0 found, it should be -759db for 32bit float or -145db for 24 bit int), isn’t it?.

Actually from your post I understand that audacity is working correctly and “amlify-effect” attenuates every waveform value to -26db, including max and min values, no compression. Thank you!

Tell me, please, how you calculated -247db, -254db, -243db in your picture? What plugin?
If I could do it for my case I would see that my min db-values decreased after attenuation as well as I see peaks decreased to 0db.

And thank you for explanation about dithering!

Another benifit from displaying min db values is we can see if after different manipulations (e.g. amplification) our result could fit in 24bit-int wav or not. If min value became less then -145 db, then the answer is “no” and we have to think about dithering or something else.

Not quite right.

0 value corresponds to negative infinity dB. It’s a logarithmic scale that takes its reference as “full scale” (+/- 1.0). As the linear value approaches zero, the logarithmic value approaches negative infinity.

The smallest non-zero value for a 32-bit float is 1.17549e-38, which corresponds to about -758 dB.


I used a bit of “Nyquist” code.
“Nyquist” is a programming language for audio that is built into Audacity. It is mostly used for writing Nyquist plug-ins, but you can also run Nyquist code in the Nyquist Prompt effect (see: Nyquist Prompt - Audacity Manual)

The code that I used:

(setf val  (snd-fetch *track*))
(print val)
(print (linear-to-db (abs val)))

The first line sets the value of “val” to the value of the first selected sample.
SND-FETCH is a built-in function that grabs a sample and returns its numeric value.
TRACK is a special variable that points to the selected audio.

The second line prints the value of “val”.

The third line converts the absolute value of “val” to dB and prints it.

To run the code:

  1. Select some audio in a mono track
  2. Open the Nyquist Prompt
  3. Enter the code
  4. Click the “Debug” button.

Using the Debug button is required if we want to see both printed lines, not just the final value returned by the code.

Lots more information about Nyquist here: Nyquist - Audacity Manual

Steve, you’ve inspired me to try to write my first Audacity plugin FindMins. I will think how better handle 0-level -inf case.

About db scale: can we display -inf db at the center of waveform but slightly higher and lower -min db in accordance with full scale?

Or instead of -inf it might be -759 (=-758-1) which is unreachable for 32bit float wav and can be considered as -inf.
Seems more informative than the current -145 db.

I suspect that even if we open 24bit int wav in audacity, internally it converts it to 32bit float and then builds waveform, right?
So -145 db is always irrelevant unless this is really min db value for the wav-file.

That’s something that I’ve been requesting for years, but it’s a bit tricky to do.
The way that other apps do it is to “stretch” the vertical scale against a linear scaled waveform and fudge it a bit, so that the scale looks something like this:

 dB
  0
 -3              x  x
-10           x       x
-30         x          x                  x
-72        x            x                x
-inf   ---x--------------x--------------x--
-72      x                x            x
-30     x                  x          x
-10                          x       x
 -3                             x  x
  0



All processing of the audio data is done in 32-bit float.


-145 dB is well below what anyone can hear (assuming that the playback volume is set normally). Even if you turn up the volume so that loud parts of the recording are deafening, anything below about -100 dB is inaudible, and -124 dB is about 16 times quieter than that.
Also, even expensive professional quality playback systems produce far more noise than that, so no-one is ever going to hear such quiet sounds.


Pleased to hear that I’ve inspired you to try using Nyquist, but I still don’t really see the point of measuring the minimum levels. As I wrote previously, the waveform passes through zero (-inf dB) many times per second. Even if there isn’t a sample value at exactly zero, the waveform, when reconstructed to analog form, has to pass through zero, just as the hand of a clock passes 12 o’clock twice each day.

Maybe a more useful measurement would be to detect when the level remains below a specified threshold for a certain amount of time. (similar to using the “Label Sounds” effect to label “Region between sounds”. See: Label Sounds - Audacity Manual)

Thought you might be interested in this (code below)
It’s not very fast, so don’t apply to long audio selections.
Try it out, and see if you can work out how it works.

(defun find-lowest (sig)
  (let ((lowest 1)
        (samplenum 0))
    (do ((val (snd-fetch sig) (snd-fetch sig))
         (i 0 (1+ i)))
        ((not val))
      (setf val (abs val))
      (when (< val lowest)
        (setf lowest val)
        (setf samplenum i))
      (if (= lowest 0)
          (return)))
    (push (list (/ samplenum *sound-srate*)
                (format nil "~a dB" (linear-to-db lowest)))
          labels)))

; Initialize 'labels' as an empty list (global scope)
(setf labels ())

; Look for lowest sample in each audio channel
(multichan-expand #'find-lowest *track*)

; Return labels
labels

Yep, the function find-lowest works perfectly! It created label track with min db. I ran it twice: before amplification and after and it shows me exactly what I want to see.
Although I am a software developer I am not familiar with nyquist syntax. But I will learn…

Again about db-scale:
Ask yourself what labels would be more useful to see?
-inf, min, mid, max and 0 isn’t it? Rather then just numbers of scale, where these values are somewhere in-between.
This scale cannot be completely fair because the range from -inf to any next label is unknown.
Additionally, somewhere between -inf and min we can place label -758 db.
I think these values are calculated anyway during the building of the waveform.

dB
  0
 max            x  x
-10           x       x
mid         x          x                  x
min        x            x                x
-inf   ---x--------------x--------------x--
min      x                x            x
mid     x                  x          x
-10                          x       x
max                           x  x
  0

But if it is hard to implement for developers of audacity, we can live with find-peak and find-lowest plugins.

There’s quite a lot of documentation available these days (a lot more than when I started :wink:)
See the links in this post: Manuals and reference material

Unfortunately there aren’t many “beginners tutorials” available yet, but I’m hoping to address that on my “audionyq.com” blog as and when I have time.

Thank you Steve for useful links.

I tried your script on another file and it produced me the lowest value -inf.
If mans it searches for min value, including zero.

I think it should loop through the waveform selection and search for value closest to 0, but not 0 (and then convert it to db full scale).

In plugin version of find-lowest plugin we can make options:
including 0 (or -759 db): yes / no
and
convert to db full scale: yes/no

I know there might be the issues with float subnormals (maybe). We have to find a solution.

You will just need to modify the logic in this part of the code:

      (when (< val lowest)
        (setf lowest val)
        (setf samplenum i))
      (if (= lowest 0)
          (return)))

Have a go at modifying it.
If you can’t work out the syntax, just try to work out the necessary logic and I’ll help you with the syntax.