How to compute SPL from sound inputed from a microphone.

Dear all
I am new to audacity.
I want to develop an ap with function of measure sound level. I have not enough knowlegde about that. May I have your suggestion? Is there some code in audacity I can reference?
My programme language is C# and MFC.
Any suggestion will be greatly appreciated.

It is not as simple as that.

To measure sound level, you have to know the following:

With [this microphone], a sound pressure level (SPL) of [this number of decibels] at [this frequency] at a distance of 1 metre from the mic gives an output of [this number of millivolts].
With [this sound card] on [this computer] using [this operating system], and with [these input settings in Control Panel] and [these input settings in Audacity], an input of [this number of millivolts] gives a level of [this number of decibels] on the recording level meters in Audacity.

That’s eight variables between the sound and the display you will get in Audacity. Change any one of them and your answer will be wrong.

Now, having completely discouraged you, there is one way you could get an appoximate value for SPL using Audacity;
Beg, borrow or rent a Sound Pressure Level meter. Put its microphone and your microphone (connected to Audacity) side by side and record a range of sounds.
Note the reading on the SPL meter and Audacity’s level meter for each sound.

Now you know that with that microphone, that computer and that setup, a level of “x” dB on the Audacity meter means that the sound level is “y” dB.

That’s about as close as you will get.

PO’L

Dear PO’L
Thanks for your reply.
I will make the solution according your suggestion.
Could you tell me which file the code is used to compute sound level locate at?
I have read the code for a day, but I am so dizzy because of too many source code files.

I don’t think the source code will tell you a lot.

Audacity doesn’t calculate SPL values; it just converts the analogue input to digital and assigns 0dB on the recording level meters to a certain input level, which corresponds to the maximum value of the digital signal. I’m not familiar with the source code, but I don’t think you will find a part of it that you can take out and use as a separate application.

Maybe someone else can point you to the part of the code that generates the meter display.

PO’L

OK!
I should try to look up some other solution.
Thanks!
Lance

<<<That’s about as close as you will get.>>>

Real SPL meters also have curves. Workplace rules for dangerous noises are written in “A” curve which gives less importance to very high and very low pitch sounds. The “C” curve isn’t a curve at all, but gives equal importance to all pitches of sound. So even if you got the code to work, you’d still only have one of the two meters.

dBSPL meters are not all that big a deal to walk out and buy one.

http://www.radioshack.com/search/index.jsp?kwCatId=&kw=sound%20meter&origkw=sound%20meter&sr=1

I have an analog one and the company owns a digital one. They still make both because you can’t measure music with the digital one.

Koz

I posted a Nyquist plugin to the audacity-nyquist list a long time ago that can calculate the equivalent and maximum A-weighted level of a selection in an audio track (only mono, and must be 44.1 kHz or higher sampling rate). I have verified it against professional software with deviations less than 0.1 dB.

However, the absolute level must be calibrated which can be a snag for the non professional user. Right now I have set it so that full scale in audacity gives 94 dB(A) SPL at 1000 Hz (94 dB SPL corresponds to a pressure of 1 Pa rms). To use it for more than simple tests you must also ensure that your microphone and soundcard are “good enough”.

Thanks to Edgar for developing the x-lisp code for the A-weighting filter!

;nyquist plug-in
;version 1
;type analyze
;name "Equivalent and maximum dB(A)..."
;action "Calc. A-weighted equivalent level (LAeq) and maximum level with time weighting FAST (LAFmax)..."

; Mikael Ogren, mr.ogren@gmail.com
; 2007-01-12
; Licensed under GPL, no warranty, use at your own risk...
 
; Calibration so that a 1000 Hz tone with amplitude 1.0 gives 94 dB
(setq calibration (+ 94 28.2))

; A-weighting by Edgar (thanks!)
(setq sa (lp (lp (hp (hp (hp (hp s 20.6) 20.6) 107.7) 737.9) 12200) 12200) )

; Exponential time-weighting filter FAST (125 ms)
; snd-avg is used to downsample to 100 Hz (by averaging over 441 samples)
; This only works for 44.1 kHz sampling frequency, perhaps someone can help out here
; by making a more general approach that works for all sampl. frq?
; The filtering part is OK for all frequencies, but the "441" constant is not.
; -
; The constant 0.000001 is to avoid clipping at filtered squared pressure > 1.0
(setq saf2
(mult 0.000001
(snd-avg (snd-biquad (mult sa sa ) 1 0 0 (exp (/ 1 (mult (snd-srate sa) -0.125))) 0 0 0) 441 441 OP-AVERAGE)
)
)

; Length of the downsampled pressure squared signal
(setq mlength
(snd-length saf2 99999999999)
)

; Calc. the equivalent level
(setq leq
(+ calibration (* 0.5 (linear-to-db (snd-maxsamp (snd-avg saf2 mlength mlength OP-AVERAGE) ))))
);

; Calc. the maximum level
(setq lmax
(+ calibration (* 0.5 (linear-to-db (snd-maxsamp saf2))))
);

; Set the output format to 3 digits (example: 53.3 dB)
(setq *float-format* "%#3.3g");

; Output result as a label track (or append into existing label track)
(setq u (format NIL "LAeq= ~A  LAFmax= ~A" leq lmax))
(list (list 0.0 u))

Can you give more details about what tests you did? Does it work accurately at low levels, for measuring noise floors of audio converters? It looks like the same filter coefficients as the MATLAB thing I translated to make this: A-weighting audio files in Python · GitHub

I made a few example noise and click like waveforms and analyzed with Audacity and two other systems, PULSE by B&K and Artemis by Head Acoustics.

Performance at low input levels will be determined by the hardware, not the filter implementation. The difference between your filter and the one I use (which is a reversed engineered A-filter posted by Edgar on the Audacity Nyquist e-mail list) and the link in your post is the use a bilinear transform. Edgars solution instead uses the filter design tools of nyquist (lp and hp) to create a chain of multiple filters.

Ok, so, I learned a little Lisp :astonished: and tried to figure out how to return the RMS level as a dialog box. It seemed to work with sine waves, but when I tried other sources, it didn’t match my other measurements. I’m guessing I’m not using the RMS function correctly. It doesn’t output a single number? So then what does it output?

Can anyone explain?

;nyquist plug-in
;version 1
;type analyze
;name "Amplitude statistics..."
;action "Calculate amplitude statistics..."

; Mikael Ogren, mr.ogren@gmail.com
; 2007-01-12
; Licensed under GPL, no warranty, use at your own risk...

; Set the output format (example: 53.3 dB)
(setq *float-format* "%#3.2f");

(if (arrayp s) ; stereo
    (progn
        ; Calculate the maximum and RMS levels
        ; RMS of full scale square wave is 0 dBFS
        (setq lmax (snd-maxsamp (aref s 0)))
        (setq rmax (snd-maxsamp (aref s 1)))
        (setq lrms (snd-maxsamp (rms (aref s 0))))
        (setq rrms (snd-maxsamp (rms (aref s 1))))

        ; A-weighted version of sound - by Edgar (thanks!)
        (setq sa (lp (lp (hp (hp (hp (hp s 20.6) 20.6) 107.7) 737.9) 12200) 12200) )

        ; Calculate the RMS level of the A-weighted signal
        ; constant is a fudge factor to normalize to 0 dB at 1 kHz
        (setq larms (* 1.34374 (snd-maxsamp (rms (aref sa 0)))))
        (setq rarms (* 1.34374 (snd-maxsamp (rms (aref sa 1)))))

        (format NIL 
            "Left:~%Peak level ~a (~a dBFS)~%RMS level ~a (~a dBFS)~%A-weighted: ~a dBFS(A)~%~%Right:~%Peak level ~a (~a dBFS)~%RMS level ~a (~a dBFS)~%A-weighted: ~a dBFS(A)"
            lmax (linear-to-db lmax) lrms (linear-to-db lrms) (linear-to-db larms)
            rmax (linear-to-db rmax) rrms (linear-to-db rrms) (linear-to-db rarms))
    )
    
    ; mono
    (progn
        ; Calculate the maximum and RMS levels
        ; RMS of full scale square wave is 0 dBFS
        (setq lmax (snd-maxsamp s))
        (setq lrms (snd-maxsamp (rms s)))

        ; A-weighted version of sound - by Edgar (thanks!)
        (setq sa (lp (lp (hp (hp (hp (hp s 20.6) 20.6) 107.7) 737.9) 12200) 12200) )

        ; Calculate the RMS level of the A-weighted signal
        ; constant is a fudge factor to normalize to 0 dB at 1 kHz
        (setq larms (* 1.34374 (snd-maxsamp (rms sa))))

        (format NIL 
            "Peak level ~a (~a dBFS)~%RMS level ~a (~a dBFS)~%A-weighted: ~a dBFS(A)"
            lmax (linear-to-db lmax) lrms (linear-to-db lrms) (linear-to-db larms))
    )
)

not quite right white and pink.PNG
sine waves in three programs.PNG

Ok, I wrote my own rms function and it’s still not producing the same values as other things. Any ideas?

;nyquist plug-in
;version 1
;type analyze
;name "Amplitude statistics..."
;action "Calculate amplitude statistics..."

; Set the output format (example: 53.3 dB)
(setq *float-format* "%#3.2f");

;; RMS -- compute the RMS of a sound
;;
(defun rms-flat (a)
    (sqrt (snd-maxsamp (snd-avg (prod a a) (round len) (round len) OP-AVERAGE)))
    )

(if (arrayp s) ; stereo
    (progn
        ; Calculate the maximum and RMS levels
        ; RMS of full scale square wave is 0 dBFS
        (setq lmax (snd-maxsamp (aref s 0)))
        (setq rmax (snd-maxsamp (aref s 1)))
        (setq lrms (rms-flat (aref s 0)))
        (setq rrms (rms-flat (aref s 1)))

        ; A-weighted version of sound - by Edgar (thanks!)
        (setq sa (lp (lp (hp (hp (hp (hp s 20.6) 20.6) 107.7) 737.9) 12200) 12200) )

        ; Calculate the RMS level of the A-weighted signal
        ; constant is a fudge factor to normalize to 0 dB at 1 kHz
        (setq larms (* 1.3363917 (rms-flat (aref sa 0))))
        (setq rarms (* 1.3363917 (rms-flat (aref sa 1))))

        (format NIL 
            "Left:~%Peak level ~a (~a dBFS)~%RMS level ~a (~a dBFS)~%A-weighted: ~a dBFS(A)~%~%Right:~%Peak level ~a (~a dBFS)~%RMS level ~a (~a dBFS)~%A-weighted: ~a dBFS(A)"
            lmax (linear-to-db lmax) lrms (linear-to-db lrms) (linear-to-db larms)
            rmax (linear-to-db rmax) rrms (linear-to-db rrms) (linear-to-db rarms))
            
    )
    
    ; mono
    (progn
        ; Calculate the maximum and RMS levels
        ; RMS of full scale square wave is 0 dBFS
        (setq lmax (snd-maxsamp s))
        (setq lrms (rms-flat s))

        ; A-weighted version of sound - by Edgar (thanks!)
        (setq sa (lp (lp (hp (hp (hp (hp s 20.6) 20.6) 107.7) 737.9) 12200) 12200) )

        ; Calculate the RMS level of the A-weighted signal
        ; constant is a fudge factor to normalize to 0 dB at 1 kHz
        (setq larms (* 1.3363917 (rms-flat sa)))

        (format NIL 
            "Peak level ~a (~a dBFS)~%RMS level ~a (~a dBFS)~%A-weighted: ~a dBFS(A)"
            lmax (linear-to-db lmax) lrms (linear-to-db lrms) (linear-to-db larms))
    )
)

Which part are you having trouble with? Is it just the A-weighted measurement?
What’s the “fudge factor”?

I may be able to help with the coding side, but how is an A-weighted measurement supposed to be calculated?

Oh, I know what it is. The other two measurements remove DC before measuring RMS, and this doesn’t. That’s why there is only a discrepancy with certain files.

So uh, how would I add that? Do a snd-avg of the entire wave and subtract it? Is maxsamp the correct way to extract a regular number from the snd-avg function? I don’t really get why it returns a single audio sample instead of a number.

Generally weighted measurements are normalized so that 1 kHz sine measures the same way unweighted or weighted. File:Acoustic weighting curves.svg - Wikipedia That’s what the fudge factor tries to do, though there are discrepancies at low and high frequencies, probably because the filters are different.

Is there a better way to handle mono/stereo? Lisp is really confusing.

Unless I’m missing something, I don’t think that they do.

(snd-maxsamp sound) computes the maximum of the absolute value of the samples in sound.
The Nyquist manual recommends that you should use (peak …) instead. Nyquist Functions
Note that with (peak …) you must use a maxlen parameter. If you want to set it to a very large number you can use (peak sound NY:All), though to protect machines that have little available ram you may be better to set it to a smaller value.

(setq bignum 1000000) ; 'bignum' is set to a big number, in this case 1 million
....
....
(peak s bignum) ; finds the maximum sample value in the first 1 million samples

snd-maxsamp and peak both return a number.

snd-avg returns a sound. In your code it is finding the average value of the square of the input sound. The input sound is calculated with a window size equal to the length of the sound, thus only one step is processed, so the output sound is one sample. Nyquist Functions

In that case I think that your “fudge factor” should be 1.313431 (based on measuring the attenuation of your A-weighting filter of a 1 kHz sine wave).

Yes there is, using (multichan-expand #'function arg1 arg2 …)
I suggest getting the plug-in working just with mono tracks. Once it is working with mono tracks it is easy to convert it to accept multi-channel sounds.

One other thing, it is more usual to use mult rather than prod. They both mean exactly the same so it does not really matter which you use, but for collaborative working it’s possibly less confusing to stick with mult purely for consistency (and as you say, Lisp is quite confusing enough at it is :slight_smile: )

An essential tool for writing Nyquist/Lisp is a text editor that has parentheses matching. On Windows I use Notepad++ http://notepad-plus-plus.org/
On Linux I use SciTE Scintilla and SciTE

I mean the other two tools I’m comparing to. In order for RMS measurement to match them, I need to remove DC first.

One other thing, it is more usual to use > mult > rather than > prod> .

I just copied that from the rms function. :slight_smile:

OK, I understand.

When you use the A-weighting filter, that will automatically remove DC off-set due to the high-pass filter stages.
For the peak and flat-rms measurements you could just apply a high-pass filter with a suitably low frequency.

(setf s (hp s 20))

Yes, but if someone measures a wave with a strong .1 Hz component in it, it should be measured. :slight_smile: I want it to be as general as possible. The A-weighting should be subjective but the unweighted should not. So something like

(sum s -dc-offset)

, but how do I calculate the dc offset if the snd-avg function does an absolute value first? I want to report the DC offset as a measurement anyway.

Probably not for the A-weighted measurement. The link you posted File:Acoustic weighting curves.svg - Wikipedia implies that A-weighting is only defined from 20 Hz to 20 kHz.

You will need to define exactly what you mean by DC off-set.
Some signals are naturally asymmetrical, even if the silent (no signal) state is at zero.
DC off-set is not sound. If it were possible to translate DC off-set through loudspeakers it would be a constant flow of air into or out of the speaker (wind).
Similarly, microphones are incapable of recording DC off-set.

DC off-set is usually defined as the amount that the mean signal level deviates from zero.
To measure that you could calculate the average level for the positive part of the wave, and the average level of the negative part of the wave. The amount of DC off-set will be half of the difference.

You can isolate the positive and negative parts of the waveform like this:

(setf positive (sum 1 (clip (sum s -1) 1)))
(setf negative (sum -1 (clip (sum s 1) 1)))

Yeah, only for the unweighted measurement. A-weighting filters out low frequencies and DC already.

You will need to define exactly what you mean by DC off-set.
DC off-set is usually defined as the amount that the mean signal level deviates from zero.

Yes, that.

To measure that you could calculate the average level for the positive part of the wave, and the average level of the negative part of the wave. The amount of DC off-set will be half of the difference.

That’s a rather roundabout way of doing it. Can I re-implement the snd-avg function but without the absolute value part?

Worse than that, it’s wrong :astonished: (though the method is correct for measuring peak offset).



(peak (snd-avg ...... op-average) maxlen)

Yes, that’s the way to do it.