Search code examples
pythonpcmrms

python pyloudnorm get RMS values (loudness metering) for PCM WAV


Currently I am using this code:

import sys
import soundfile as sf
import pyloudnorm as pyln

f = open( "c:\\temp\\wav_analysis.txt", "w" )
data, rate = sf.read(sys.argv[1]) # load audio (with shape (samples, channels))
meter = pyln.Meter( rate ) # create BS.1770 meter
loudness = meter.integrated_loudness(data) # measure loudness
'''output analysis data'''
for i in range(1, len(data)):
    if abs(data[i]) > 0.4:
        f.write(str( i / rate ) + "," + str(abs(data[ i ])) + "\n")

The WAV file is passed in as an argument, it's read in and then analyzed for loudness across all of "data".

I don't want that. I want to analyze 100ms windows of data (i.e. 4410 samples at a time, while shifting my window by 50 milliseconds, thus creating lots of loudness values.

Is there a way to call meter.integrated_loundess() in such a way that it does that?

Or do I need to somehow create a bunch of 4410-value long data arrays derived from "data" and then feed each one of those to meter.integrated_loudness() one by one?

(The stuff below '''output analysis data''' is just a place holder. I want to replace it with what I need.)

EDIT: See the "slicing" answer below. Also, keep in mind that through trial and error I discovered that integrated_loudness requires the data to be at least 17640 samples long (i.e. 400ms at 44100).

EDIT2: During random searching for something else, I came across this site:https://pysoundfile.readthedocs.io/en/0.8.0/

There, this code snippet was exactly what I was initially looking for for quickly getting the RMS values of the WAV file:

import numpy as np
import soundfile as sf

rms = [np.sqrt(np.mean(block**2)) for block in
       sf.blocks('myfile.wav', blocksize=1024, overlap=512)]

Not only is it much faster, but it is also not limited by the "0.4second" window limit that I ran into with meter.integrated_loudness.


Solution

  • If I understand your question correctly, perhaps you can slice the data doing something like the following:

    import sys
    import soundfile as sf
    import pyloudnorm as pyln
    
    with open("c:\\temp\\wav_analysis.txt", "w") as f:
        data, rate = sf.read(sys.argv[1]) # load audio (with shape (samples, channels))
        meter = pyln.Meter(rate) # create BS.1770 meter
    
        window_size = int(rate * 0.1) # window size of 100ms in samples
        hop_size = int(rate * 0.05) # hop size of 50ms in samples
    
        for i in range(0, len(data)-window_size, hop_size):
            window = data[i:i+window_size] # extract a 100ms window of data
            loudness = meter.integrated_loudness(window) # measure loudness of the window
            f.write(f"{str(i / rate)},{str(loudness)}" + "\n")