• WANTED: Happy members who like to discuss audio and other topics related to our interest. Desire to learn and share knowledge of science required. There are many reviews of audio hardware and expert members to help answer your questions. Click here to have your audio equipment measured for free!

Offline Loudness (LUFS) plotting

L5730

Addicted to Fun and Learning
Joined
Oct 6, 2018
Messages
669
Likes
436
Location
East of England
Hi all,
Not sure if this is the correct section, but it makes the most sense (unless I am corrected).
I've a few loudness meters to use in the DAW, but nothing that can scan an audio file and provide a nice graphed plot.

I knew ffmpeg could utilise the ebur128 audio filter to generate all the necessary stats every 100ms (Momentary, Short-Term, Integrated), so it was just a matter of formatting that data into a CSV file which could be handed over to a program to plot a graph.
..and after messing around with gnuplot recently, I thought this should work for this purpose.

Here is the Powershell code for a "Send-To" (right-click context menu) item in Windows 10 (and maybe 7 if updated).
Of course, you'll have to download and compile or just download the already compiled gnuplot binary.
The correct path to gnuplot.exe needs to be entered for the variable "$gnuplot".
It is assumed ffmpeg.exe (at least v3.4) is in the system path, like C:\Windows\System32. If not, then it should be properly pathed in this script.
It uses the system 'Temp' folder for temporary files, which are deleted afterwards. The plot image also ends up there unless changed "$outplot"

Save this as a .ps1 file somewhere.
To make it run as a send to, create a shortcut to the .ps1 file in the Windows SendTo folder (hit WinKey+'R', then type 'shell:sendto' without the quotes, then Enter key).
Make the shortcut (hold shift and ctrl and drag the .ps1 in to create a shortcut). Open the shortcut properties and add 'Powershell.exe -ExecutionPolicy bypass -file ' before the path to the ps1 file. Then follow after the path with ' -infile'. OK and close out (all test without quotes).
Windows security is probably setup to prevent running unsigned and scripts not verified locally. It seems a bit pointless when a simple argument turns that off. Oh well.

Code:
# takes selected audio file,
# uses ffmpeg ebur128 filter for lufs stats
# and converts data to csv for graphing.
# uses gnuplot to plot the graph.

Param($infile)

$outcsv = "$env:Temp\loudnesscsv.txt"
$gnuplotcommandfile = "$env:Temp\gnuplotcmdfile.txt"
$outplot = "$env:Temp\LoudnessPlot.png"
$gnuplot = "C:\Program Files (x86)\gnuplot\gnuplot.exe"


Write-Host "Getting Loudness Data with ffmpeg."
$lufs_data = & ffmpeg.exe -i $infile -af ebur128 -f null - 2>&1
#$lufs_data | Out-File "t:\lufsdata.txt" -Encoding ASCII

Write-Host "Data Collected, now formatting to csv file."
$csv = $(
ForEach($rawentry in $lufs_data){
    $entry = if($rawentry -match "parsed_ebur128"){$rawentry}
    $array_tmsir = $entry -split(":") -split (" ") | ?{$_ -ne ""} | Select-Object -index 4,9,11,13,16

    if($array_tmsir){
        $time = $array_tmsir[0]
        $Momentary = $array_tmsir[1]
        $Short = $array_tmsir[2]
        $Integrated = $array_tmsir[3]
        $lra = $array_tmsir[4]

        "$([math]::Round($time,1))" + ",$Momentary,$Short,$Integrated,$lra"
    }
}
)
$csv | Out-String | Out-File $outcsv -Encoding ASCII
$entries = $csv.count


Write-Host "Running GNUPlot..."
$(
"set terminal png size 1280,720;"
"set output '$outplot';"
"set datafile separator ',';"
"set title `"$($infile -replace("\\","\\"))`" noenhanced;"
"set yrange [-60:0];"
"set ylabel 'Loudness (LUFS)';"
"set ytic 6;"

"set ytics nomirror;"
"set y2tics;"
"set y2range [0:30];"
"set y2tic 1;"
"set y2label 'Loudness Range (LU)';"

"set grid;"
"set key top left;"

"set xdata time;"
"set format x '%M:%S';"
"set xlabel 'Time (MM:SS)';"
"set autoscale xfix;"

"plot '$outcsv' using (`$0/10):2 axes x1y1 with lines lc rgb '#ff0000' lw 1 title 'Momentary', \"
"               '' using (`$0/10):3 axes x1y1 with lines lc rgb '#8080ff' lw 3 title 'Short-Term', \"
"               '' using (`$0/10):4 axes x1y1 with lines lc rgb '#0000ff' lw 4 title 'Integrated', \"
"               '' using (`$0/10):5 axes x1y2 with lines lc rgb '#00aa00' lw 2 title 'Loudness Range';"
) | Out-String | Out-File $gnuplotcommandfile -Encoding ASCII

cmd.exe /c "`"$gnuplot`" <$gnuplotcommandfile"

Remove-Item -LiteralPath $outcsv
Remove-Item -LiteralPath $gnuplotcommandfile

& $outplot
exit

Yeah, it's a bit clumsily done. There are plenty of options that can be changed for colours, line thickness and stuff, as well as plot size, axis ranges etc.
I'm working on some code ideas to handle larger data sets and reducing it for speed. No point supplying data that can't be drawn as there isn't enough pixel space to plot them!


Examples:
LoudnessPlot.png

LoudnessPlot.png

LoudnessPlot.png
 
OP
L5730

L5730

Addicted to Fun and Learning
Joined
Oct 6, 2018
Messages
669
Likes
436
Location
East of England
edit: added loudness range low and high range (10th and 95th percentile), integrated and range values to the legend.
Obviously change paths for your system in the code.
LoudnessPlot.png

Code:
Param($infile)

$outcsv = "$env:Temp\loudnesscsv.txt"
$gnuplotcommandfile = "$env:Temp\gnuplotcmdfile.txt"
$outplot = "T:\LoudnessPlot.png"
$gnuplot = "d:\0\gnuplot\gnuplot.exe"

Write-Host "Getting Loudness Data with ffmpeg."
$lufs_data = & ffmpeg.exe -i $infile -af ebur128 -f null - 2>&1
#$lufs_data | Out-File "t:\lufsdata.txt" -Encoding ASCII

$lra_high = ($lufs_data | select-string "LRA high:") -replace("[^0-9-.]")
$lra_low = ($lufs_data | select-string "LRA low:") -replace("[^0-9-.]")
$I_LUFS = ($lufs_data | select-string "I:      ") -replace("[^0-9-.]")
$lrange = ($lufs_data | select-string "LRA:      ") -replace("[^0-9-.]")

Write-Host "Data Collected, now formatting to csv file."
$csv = $(
ForEach($rawentry in $lufs_data){
    $entry = if($rawentry -match "parsed_ebur128"){$rawentry}
    $array_tmsir = $entry -split(":") -split (" ") | ?{$_ -ne ""} | Select-Object -index 4,9,11,13,16

    if($array_tmsir){
        $time = $array_tmsir[0]
        $Momentary = $array_tmsir[1]
        $Short = $array_tmsir[2]
        $Integrated = $array_tmsir[3]
        $lra = $array_tmsir[4]

        "$([math]::Round($time,1))" + ",$Momentary,$Short,$Integrated,$lra"
    }
}
)
$csv | Out-String | Out-File $outcsv -Encoding ASCII
$entries = $csv.count

Write-Host "Running GNUPlot..."
$(
"set terminal png truecolor size 1280,720;"
"set output '$outplot';"
"set datafile separator ',';"
"set title `"$($infile -replace("\\","\\"))`" noenhanced;"
"set yrange [-60:0];"
"set ylabel 'Loudness (LUFS)';"
"set ytic 6;"

"set ytics nomirror;"
"set y2tics;"
"set y2range [0:30];"
"set y2tic 1;"
"set y2label 'Loudness Range (LU)';"

"set grid;"
"set key top left;"

"set xdata time;"
"set format x '%M:%S';"
"set xlabel 'Time (MM:SS)';"
"set autoscale xfix;"

"set timefmt '%M:%S';"

"set label 1 'LRA Low: $lra_low LUFS' at '04:30',($lra_low -3) front;"
"set label 2 'LRA High: $lra_high LUFS' at '04:30',($lra_high +3) front;"

"plot \"
"$lra_high with filledcurves above y=$lra_low fc rgb '#00aa00' fs transparent solid 0.1 noborder title 'Loudness Range Low to High', \"
"'$outcsv' using (`$0/10):2 axes x1y1 with lines lc rgb '#ff0000' lw 1 title 'Momentary', \"
"               '' using (`$0/10):3 axes x1y1 with lines lc rgb '#8080ff' lw 3 title 'Short-Term', \"
"               '' using (`$0/10):4 axes x1y1 with lines lc rgb '#0000ff' lw 4 title 'Integrated: $I_LUFS LUFS', \"
"               '' using (`$0/10):5 axes x1y2 with lines lc rgb '#008000' lw 3 title 'Loudness Range: $lrange LU'; \"
) | Out-String | Out-File $gnuplotcommandfile -Encoding ASCII

cmd.exe /c "`"$gnuplot`" <$gnuplotcommandfile"

Remove-Item -LiteralPath $outcsv
Remove-Item -LiteralPath $gnuplotcommandfile

& $outplot
exit
 
Last edited:
OP
L5730

L5730

Addicted to Fun and Learning
Joined
Oct 6, 2018
Messages
669
Likes
436
Location
East of England
For dynamic placement of LRA High/Low labels @ 45 seconds in from the right side of the plot, and right justified, with a solid box underneath and border around:

Replace:
Code:
"set timefmt '%M:%S';"

"set label 1 'LRA Low: $lra_low LUFS' at '04:30',($lra_low -3) front;"
"set label 2 'LRA High: $lra_high LUFS' at '04:30',($lra_high +3) front;"
with
Code:
"set obj 1 rect at (($entries/10)-45),($lra_high +3) size char strlen('LRA High: $lra_high LUFS'), char 1 fc 'white' front;"
"set label 1 'LRA High: $lra_high LUFS' at (($entries/10)-45),($lra_high +3) front center font `",10`";"
"set obj 2 rect at (($entries/10)-45),($lra_low -3) size char strlen('LRA Low: $lra_low LUFS'), char 1 fc 'white' front;"
"set label 2 'LRA Low: $lra_low LUFS' at (($entries/10)-45),($lra_low -3) front center font `",10`";"
(yes, it gets rid of the "set timefmt" bit, but means that the total count of the number of lines in the plot data can be easily used to reference the last data point on x-axis, or the far right of the plot if you will).


For a better key legend, that takes up less vertical space:

Replace:
Code:
"set key top left;"
with:
Code:
"set key inside center top Left reverse maxrows 2 width -5;"
It also might be worth changing the y-axis to go up to +3 even though no data can plot there (LUFS - 'Full Scale' as in 'from zero and down'.), it gives a bit of headroom to draw the legend.
Code:
"set yrange [-60:3];"
Code:
"set y2range [0:33];"


To set the title to the "ARTIST - ALBUM - TRACK## - TITLE", or the full file path if those are present:

add in the following, just above " Write-Host "Data Collected, now formatting to csv file." ".

Code:
$artist = (($lufs_data | select-string ("ARTIST  ")) -split ": ")[1]
$album = (($lufs_data | select-string ("ALBUM  ")) -split ": ")[1]
$title = (($lufs_data | select-string ("TITLE  ")) -split ": ")[1]
$track = (($lufs_data | select-string ("TRACK  ")) -split ": ")[1]

$plottitle = @($artist, $album, $('{0:d2}' -f [int]$track), $title) -join " - "
if($plottitle -eq " -  -  - "){$plottitle = $infile}
and replace:
Code:
"set title `"$($infile -replace("\\","\\"))`" noenhanced;"
with:
Code:
"set title `"$($plottitle -replace("\\","\\"))`" noenhanced;"
 
Last edited:
OP
L5730

L5730

Addicted to Fun and Learning
Joined
Oct 6, 2018
Messages
669
Likes
436
Location
East of England
Things now look like this:
LoudnessPlot.png

LoudnessPlot.png

- removed LRA labels and put that numeric data in the key legend.
- wider spaced key
- y-axis allows headroom for key
- 2nd y-axis every 2 units
- title formatting

With longer material, such as a whole album or orchestral piece, it takes while longer to generate the data, format the data and plot it, but I don't see any other tools that can do this. There are some Loudness Meter plugins that can log to csv, and I guess they could be ran in offline mode/rendering mode. This scripted setup just seems convenient to me.

Code:
Param($infile)

$outcsv = "$env:Temp\loudnesscsv.txt"
$gnuplotcommandfile = "$env:Temp\gnuplotcmdfile.txt"
$outplot = "$env:Temp\LoudnessPlot.png"
$gnuplot = "C:\Program Files (x86)\gnuplot\gnuplot.exe"

Write-Host "Getting Loudness Data with ffmpeg."
$lufs_data = & ffmpeg.exe -i $infile -af ebur128 -f null - 2>&1 
#$lufs_data | Out-File "t:\lufsdata.txt" -Encoding ASCII

$lra_high = ($lufs_data | select-string "LRA high:") -replace("[^0-9-.]")
$lra_low = ($lufs_data | select-string "LRA low:") -replace("[^0-9-.]")
$I_LUFS = ($lufs_data | select-string "I:      ") -replace("[^0-9-.]")
$lrange = ($lufs_data | select-string "LRA:      ") -replace("[^0-9-.]")

$artist = (($lufs_data | select-string ("ARTIST  ")) -split ": ")[1]
$album = (($lufs_data | select-string ("ALBUM  ")) -split ": ")[1]
$title = (($lufs_data | select-string ("TITLE  ")) -split ": ")[1]
$track = (($lufs_data | select-string ("TRACK  ")) -split ": ")[1]

$plottitle = @($artist, $album, $('{0:d2}' -f [int]$track), $title) -join " - "
if($plottitle -eq " -  -  - "){$plottitle = $infile}

Write-Host "Data Collected, now formatting to csv file."
$csv = $(
ForEach($rawentry in $lufs_data){
    $entry = if($rawentry -match "parsed_ebur128"){$rawentry}
    $array_tmsir = $entry -split(":") -split (" ") | ?{$_ -ne ""} | Select-Object -index 4,9,11,13,16

    if($array_tmsir){
        $time = $array_tmsir[0]
        $Momentary = $array_tmsir[1]
        $Short = $array_tmsir[2]
        $Integrated = $array_tmsir[3]
        $lra = $array_tmsir[4]

        "$([math]::Round($time,1))" + ",$Momentary,$Short,$Integrated,$lra"
    }
}
)
$csv | Out-String | Out-File $outcsv -Encoding ASCII
$entries = $csv.count

Write-Host "Running GNUPlot..."
$(
"set terminal png truecolor size 1280,720;"
"set output '$outplot';"
"set datafile separator ',';"
"set title `"$($plottitle -replace("\\","\\"))`" noenhanced;"

"set key inside center top Left reverse maxrows 2 width -5;"

"set yrange [-60:3];"
"set ylabel 'Loudness (LUFS)';"
"set ytic 6;"
"set mytics 2;"

"set ytics nomirror;"
"set y2tics;"
"set y2range [0:33];"
"set y2tic 2;"
"set y2label 'Loudness Range (LU)';"

"set grid;"

"set xdata time;"
"set format x '%M:%S';"
"set xlabel 'Time (MM:SS)';"
"set autoscale xfix;"

#"set timefmt '%M:%S';"

#"set obj 1 rect at (($entries/10)-45),($lra_high +3) size char strlen('LRA High: $lra_high LUFS'), char 1 fc 'white' front;"
#"set label 1 'LRA High: $lra_high LUFS' at (($entries/10)-45),($lra_high +3) front center font `",10`";"
#"set obj 2 rect at (($entries/10)-45),($lra_low -3) size char strlen('LRA Low: $lra_low LUFS'), char 1 fc 'white' front;"
#"set label 2 'LRA Low: $lra_low LUFS' at (($entries/10)-45),($lra_low -3) front center font `",10`";"

"plot \"
"$lra_high with filledcurves above y=$lra_low fc rgb '#00aa00' fs transparent solid 0.1 noborder title 'Loudness Range: ($lra_low to $lra_high LUFS)', \"
"'$outcsv' using (`$0/10):2 axes x1y1 with lines lc rgb '#ff0000' lw 1 title 'Momentary', \"
"               '' using (`$0/10):3 axes x1y1 with lines lc rgb '#8080ff' lw 3 title 'Short-Term', \"
"               '' using (`$0/10):4 axes x1y1 with lines lc rgb '#0000ff' lw 4 title 'Integrated: $I_LUFS LUFS', \"
"               '' using (`$0/10):5 axes x1y2 with lines lc rgb '#008000' lw 3 title 'Loudness Range: $lrange LU'; \"
) | Out-String | Out-File $gnuplotcommandfile -Encoding ASCII

cmd.exe /c "`"$gnuplot`" <$gnuplotcommandfile"

Remove-Item -LiteralPath $outcsv
Remove-Item -LiteralPath $gnuplotcommandfile

& $outplot
exit
 
OP
L5730

L5730

Addicted to Fun and Learning
Joined
Oct 6, 2018
Messages
669
Likes
436
Location
East of England
LoudnessPlot.png

Added truePeak to Momentary Loudness Ratio (tPLR) with coloring (0-6 red, to orange at 9, to green at 13 and to blue at 21 dB).
Can be called from cmdline and extra switched can be used:
Powershell.exe -file <this script.ps1> -infile <some audio file> -w <img width pxls> -h <img height pxls> -outplot <output plot image path .png>

(this is getting scrappy as I play with ideas)
Code:
# Generates Loudness plots
# uses ffmpeg to generate loudness data
# formats into csv
# uses gnuplot to draw graphs

Param(
    $infile,
    $w,
    $h,
    $outplot
    )


$outcsv = "$env:Temp\loudnesscsv.txt"
$gnuplotcommandfile = "$env:Temp\gnuplotcmdfile.txt"
$gnuplot_histodata = "$env:Temp\gnuplot_histo_data.txt"
if(!$outplot){$outplot = "T:\LoudnessPlot.png"}
$gnuplot = "d:\0\gnuplot\gnuplot.exe"

#
if(!$w){$w=1280}
if(!$h){$h=720}


Write-Host "Getting Loudness Data with ffmpeg."
$lufs_data = & ffmpeg.exe -i $infile -af ebur128=peak=true -ar 4410 -f null - 2>&1
#$lufs_data | Out-File "t:\lufsdata.txt" -Encoding ASCII

$lra_high = ($lufs_data | select-string "LRA high:") -replace("[^0-9-.]")
$lra_low = ($lufs_data | select-string "LRA low:") -replace("[^0-9-.]")
$I_LUFS = ($lufs_data | select-string "I:      ") -replace("[^0-9-.]")
$lrange = ($lufs_data | select-string "LRA:      ") -replace("[^0-9-.]")

$artist = (($lufs_data | select-string ("ARTIST  ")) -split ": ")[1]
$album = (($lufs_data | select-string ("ALBUM  ")) -split ": ")[1]
$title = (($lufs_data | select-string ("TITLE  ")) -split ": ")[1]
$track = (($lufs_data | select-string ("TRACK  ")) -split ": ")[1]

$plottitle = @($artist, $album, $('{0:d2}' -f [int]$track), $title) -join " - "
if($plottitle -eq " -  - 00 - "){$plottitle = $infile}

Write-Host "Data Collected, now formatting to csv file."
$csv = $(
ForEach($rawentry in $lufs_data){
    $entry = if($rawentry -match "parsed_ebur128"){$rawentry}
    $array_tmsir = $entry -split(":") -split (" ") | ?{$_ -ne ""} | Select-Object -index 4,9,11,13,16,19,20

    if($array_tmsir){
        $time = $array_tmsir[0]
        $Momentary = $array_tmsir[1]
        $Short = $array_tmsir[2]
        $Integrated = $array_tmsir[3]
        $lra = $array_tmsir[4]
        $tpk_l = $array_tmsir[5]
        $tpk_r = $array_tmsir[6]

        "$([math]::Round($time,1))" + ",$Momentary,$Short,$Integrated,$lra,$tpk_l,$tpk_r"
    }
}
)
$csv | Out-String | Out-File $outcsv -Encoding ASCII
$entries = $csv.count

Write-Host "Running GNUPlot..."
$gnuplotinstructions = $(
"set terminal png truecolor size $w,$h;"
"set output '$outplot';"
"set title `"$($plottitle -replace("\\","\\"))`" noenhanced;"

<# for plotting an additional distribution plot

"set multiplot layout 1,2;"

"set datafile separator ',';"
"bin_width = 0.1;"
"bin_number(x) = floor(x/bin_width);"
"rounded(x) = bin_width * ( bin_number(x) + 0.5 );"
"set table '$gnuplot_histodata';"
"plot '$outcsv' using (rounded(`$2)):(1) smooth frequency;"
"unset table;"
"unset datafile separator;"

"set key left top;"
"stats '$gnuplot_histodata' using 2:1 nooutput;"
"set xrange [1:(STATS_max_x)];"
"set yrange [-60:0];"
"set xtics 1;"
"set ytics 6;"
"set grid;"
"plot '$gnuplot_histodata' using (`$2 * 1.41):1 smooth bezier title 'Loudness Distribution';"
"unset grid;"
"unset xrange;"
"unset yrange;"
"unset xtics;"
"unset ytics;"
"unset key;"
#>

"set datafile separator ',';"
"set key inside center top Left reverse maxrows 2 width -4;"

"set yrange [-60:3];"
"set ylabel 'Loudness (LUFS)';"
"set ytic 6;"
"set mytics 2;"

"set ytics nomirror;"
"set y2tics;"
"set y2range [0:31.5];"
"set y2tic 2;"
"set y2label 'Loudness Range (LU)';"

"set grid;"

"set xdata time;"
"set format x '%M:%S';"
"set xlabel 'Time (MM:SS)';"
"set autoscale xfix;"

#"set timefmt '%M:%S';"

#"set obj 1 rect at (($entries/10)-45),($lra_high +3) size char strlen('LRA High: $lra_high LUFS'), char 1 fc 'white' front;"
#"set label 1 'LRA High: $lra_high LUFS' at (($entries/10)-45),($lra_high +3) front center font `",10`";"
#"set obj 2 rect at (($entries/10)-45),($lra_low -3) size char strlen('LRA Low: $lra_low LUFS'), char 1 fc 'white' front;"
#"set label 2 'LRA Low: $lra_low LUFS' at (($entries/10)-45),($lra_low -3) front center font `",10`";"

"set palette model RGB defined (0 'red', 6 'red', 9 'orange', 13 'green', 21 'blue');"
"set cbrange [0:21];"
"unset colorbox;"

"plot \"
"$lra_high with filledcurves above y=$lra_low fc rgb '#00aa00' fs transparent solid 0.1 noborder title 'LRA H: $lra_high - L: $lra_low LUFS', \"
"'$outcsv' using (`$0/10):2 axes x1y1 with lines lc rgb '#ff0000' lw 1 title 'Momentary', \"
"               '' using (`$0/10):( (`$6>`$7?(`$6-`$2):(`$7-`$2)) ) : ( (`$6>`$7?(`$6-`$2):(`$7-`$2)) ) with boxes lc palette fs transparent solid 0.1 noborder axes x1y2 title 'tPLR', \"
#"               '' using (`$0/10):( (`$6>`$7?(`$6-`$2):(`$7-`$2)) ):( (`$6>`$7?(`$6-`$2):(`$7-`$2)) ) axes x1y2 with filledcurves above y1=0 fc palette z fs transparent solid 0.1 noborder title 'PLR', \"
#"               '' using (`$0/10):2 axes x1y1 with histeps lc rgb '#ff8000' notitle, \"
"               '' using (`$0/10):3 axes x1y1 with lines lc rgb '#8080ff' lw 3 title 'Short-Term', \"
"               '' using (`$0/10):4 axes x1y1 with lines lc rgb '#0000ff' lw 4 title 'Integrated: $I_LUFS LUFS', \"
"               '' using (`$0/10):5 axes x1y2 with lines lc rgb '#008000' lw 3 title 'Loudness Range: $lrange LU'; \"

) | Out-String
$gnuplotinstructions | Out-File $gnuplotcommandfile -Encoding ASCII

<#
$bytes = [System.Text.Encoding]::UTF8.GetBytes($gnuplotinstructions)
$str = [system.text.Encoding]::UTF8.GetString($bytes)
$str
#>

cmd.exe /c "`"$gnuplot`" <$gnuplotcommandfile"

Remove-Item -LiteralPath $outcsv
Remove-Item -LiteralPath $gnuplotcommandfile
Remove-Item -LiteralPath $gnuplot_histodata -ErrorAction silentlycontinue

#cmd.exe /c pause
& $outplot
exit
 

RayDunzl

Grand Contributor
Central Scrutinizer
Joined
Mar 9, 2016
Messages
13,204
Likes
16,985
Location
Riverview FL
Is there an example of a music file mastered "the old way" and mastered "the LUFS" way?
 
OP
L5730

L5730

Addicted to Fun and Learning
Joined
Oct 6, 2018
Messages
669
Likes
436
Location
East of England
Is there an example of a music file mastered "the old way" and mastered "the LUFS" way?
I'm not sure what you mean, do you mean compare an older song/album that would have been transferred from tape and mastered the analogue way, but recorded to CD for distribution, and a later remaster of the same song, which would have come from tape again, but been remastered 'in the box' so to speak and with likely an all digital chain?

I don't think there is a 'LUFS way' to master music. LUFS is just a replacement for VU, really. An improved set of tools to better understand the volume of the material. Clearly many engineers aren't using the LUFS toolset or if they are they are ignoring it, as many are extremely loud, have hardly any loudness range and are full of true-peak overs.
 
OP
L5730

L5730

Addicted to Fun and Learning
Joined
Oct 6, 2018
Messages
669
Likes
436
Location
East of England
PurpleHaze_85.png

PurpleHaze_97.png

There's a comparison of two different remasters of the same track.
In this case the '97 doesn't sound utterly terrible. It has been peak limited and some EQ changes were done creating a slightly harder sound IMO.

Generally speaking I don't hold on to multiple versions unless there is a reason. Most of the time an earlier version (or a later premium version) outdoes the others in all qualities, so there's no point taking up space with inferior versions.
 

danadam

Addicted to Fun and Learning
Joined
Jan 20, 2017
Messages
957
Likes
1,497
For true peaks, I think I would be most interested if they go over 0dB.
 
OP
L5730

L5730

Addicted to Fun and Learning
Joined
Oct 6, 2018
Messages
669
Likes
436
Location
East of England
For true peaks, I think I would be most interested if they go over 0dB.
As the data is there for Left and Right peaks only every 100ms, that could be plotted. Some overs are captured as I've seen 0.1 or 0.2 dB listed, and if it was straight sample peaks it'd only go to 0 dB.
 

BethHarmon

Member
Joined
Mar 7, 2021
Messages
12
Likes
0
Great work on these loudness graphs! I'm in the process of converting your scripts to bash to be used on Linux without needing to rely on PowerShell: https://github.com/ElizabethHarmon/ebu-norm (towards the bottom of the readme file). For now I'm using a bash wrapper script with a slightly adapted PowerShell script that creates Linux temp directories. There were a few other tweaks I had to do in order to make it work. I've thanked you via your username but if you'd like a proper attribution, please let me know! I've built in batch capabilities to go along with my loudness normalization scripts.

A few questions/comments:

1) The green LRA spikes at the c. 3-second mark are decidedly odd. It's clearly an ffmpeg issue as I see the numbers jump up to ~20 and then back down to single digits on every file I analyze. Comparing to other meters that deliver CSV, something is definitely wrong here. The question: is it easy enough in gnuplot or via script to smooth out that spike or simply ignore it? I think we are talking about 400ms or so :) If not, I'm at the stage where I might just simply remove the green line completely as the loudness range final number feels way more important in any case.

2) Have you considered a thin vertical red line whenever true peaks move above 0 (or some other defined number like -1 dBTP for EBU R 128)? I think it would be a really nice addition!

Thanks again for sharing these scripts.
 
OP
L5730

L5730

Addicted to Fun and Learning
Joined
Oct 6, 2018
Messages
669
Likes
436
Location
East of England
@BethHarmon Thank you for the credit. Really, I just found a way to leverage ffmpeg and gnuplot that best suited me. Sharing it as a copy and paste solution for others and for others to improve upon.

Powershell is soo much easier than Windows Batch, both require intermediate files for purpose, as would Linux/Unix Bash. If I fire up a Linux VM, I'll take a swing at sorting it out in Bash if you like. It shouldn't be all that tough to re-write for purpose.

(1) Interesting. That LRA value would be product of libebur128 in ffmpeg builds. Manipulating the data from ffmpeg isn't all that tough, although what to actually do to that data to get something that resembles other loudness tools would be trickier. I'll have to look and see what comes out of other tools. It could just be that the other tools gate the first 1 second of detected audio, solving the issue.

(2) True peaks are a pain with the output of the libebur128 filter.
What I wanted was to have true peak shown in one colour and then change to red when over 0 dBFS. Gnuplot doesn't really do that, one has to sort of split the data up, show two plots, data over and under the 0 dBFS line as separate entities. It then draws a horizontal line when there are no data plots for that data, so it's not great.
 

q3cpma

Major Contributor
Joined
May 22, 2019
Messages
3,060
Likes
4,416
Location
France
Thanks, might adapt it for POSIX sh or Tcl/Tk with plotchart.
Powershell is soo much easier than Windows Batch, both require intermediate files for purpose, as would Linux/Unix Bash.
Not really, you can always use named pipes (mkfifo(1)) when your tools don't like pipes. Or simply the quite portable (not POSIX, even if the C function strangely is) mktemp(1) to use the almost always mounted tmpfs.
 

BethHarmon

Member
Joined
Mar 7, 2021
Messages
12
Likes
0
It could just be that the other tools gate the first 1 second of detected audio, solving the issue.

From what I can gather, it seems like only 4 measurements that shoot to ~20 around 3s mark. Plotting LRA from t: 3.39998 onwards seems like it would fix things.

I'll take a swing at sorting it out in Bash if you like. It shouldn't be all that tough to re-write for purpose.

I've made a start myself. The bulk of the change seems to be creating a CSV file using awk and using bash parlance to string together the various titles. After that, gnuplot should take the same commandfile given that it seems to work fine on my system. Having said this, if I run into difficulties, I know who to ask :)
 

BethHarmon

Member
Joined
Mar 7, 2021
Messages
12
Likes
0
Plotting LRA from t: 3.39998 onwards seems like it would fix things.

OK, I've figured out every to make gnuplot start plotting LRA from line 33 using every but it shifts everything left instead of just ignoring the values. Argh...
 

danadam

Addicted to Fun and Learning
Joined
Jan 20, 2017
Messages
957
Likes
1,497
OK, I've figured out every to make gnuplot start plotting LRA from line 33 using every but it shifts everything left instead of just ignoring the values. Argh...
Replace the values at the beginning with "-":
Code:
]$ cat data.txt
0.00  0.00     -  0.30
0.10  0.09     -  0.31
0.20  0.19     -  0.34
0.30  0.29     -  0.39
0.40  0.38  0.92  0.46
0.50  0.47  0.88  0.55
0.60  0.56  0.83  0.66
0.70  0.64  0.77  0.79
0.80  0.71  0.70  0.94
0.90  0.78  0.62  1.11
1.00  0.84  0.54  1.30
data.txt.png
 

danadam

Addicted to Fun and Learning
Joined
Jan 20, 2017
Messages
957
Likes
1,497
Cheers! Now how to figure out how to do that automatically using a bash tool...
If you know the number of lines, then:
Code:
]$ cat data.in
0.00  0.00  0.99  0.30
0.10  0.09  0.99  0.31
0.20  0.19  0.98  0.34
0.30  0.29  0.95  0.39
0.40  0.38  0.92  0.46
0.50  0.47  0.88  0.55
0.60  0.56  0.83  0.66
0.70  0.64  0.77  0.79
0.80  0.71  0.70  0.94
0.90  0.78  0.62  1.11
1.00  0.84  0.54  1.30

]$ awk '{ if (NR <= 4) { $3="-" } print $0}' data.in
0.00 0.00 - 0.30
0.10 0.09 - 0.31
0.20 0.19 - 0.34
0.30 0.29 - 0.39
0.40  0.38  0.92  0.46
0.50  0.47  0.88  0.55
0.60  0.56  0.83  0.66
0.70  0.64  0.77  0.79
0.80  0.71  0.70  0.94
0.90  0.78  0.62  1.11
1.00  0.84  0.54  1.30
Or to make the output nice like before (though gnuplot won't mind either way):
Code:
]$ awk 'BEGIN { OFS="  " } { if (NR <= 4) { $3="   -" } print $0}' data.in
0.00  0.00     -  0.30
0.10  0.09     -  0.31
0.20  0.19     -  0.34
0.30  0.29     -  0.39
0.40  0.38  0.92  0.46
0.50  0.47  0.88  0.55
0.60  0.56  0.83  0.66
0.70  0.64  0.77  0.79
0.80  0.71  0.70  0.94
0.90  0.78  0.62  1.11
1.00  0.84  0.54  1.30
 
OP
L5730

L5730

Addicted to Fun and Learning
Joined
Oct 6, 2018
Messages
669
Likes
436
Location
East of England
I've just re-read the EBU R128 specs. Short-term loudness is measured in a 3 second sliding window. Loudness Range (LRA) is derived from the distribution of (short-term) Loudness, so it makes perfect sense as to why there is that erroneous high LRA spike at the 3 second mark.

Good spot on the 3.3 seconds, and line 33.
This is an easy fix (dirty) in the existing PoSh.

Code:
Write-Host "Data Collected, now formatting to csv file."
$csv = $(
ForEach($rawentry in $lufs_data){
    $entry = if($rawentry -match "parsed_ebur128"){$rawentry}
    $array_tmsir = $entry -split(":") -split (" ") | ?{$_ -ne ""} | Select-Object -index 4,9,11,13,16,19,20

    if($array_tmsir){
        $time = $([math]::Round($array_tmsir[0],1))
        $Momentary = $array_tmsir[1]
        $Short = $array_tmsir[2]
        $Integrated = $array_tmsir[3]
        $lra = if($time -lt 3.4){"-"}else{$array_tmsir[4]}
        $tpk_l = $array_tmsir[5]
        $tpk_r = $array_tmsir[6]

        "$time,$Momentary,$Short,$Integrated,$lra,$tpk_l,$tpk_r"
    }
}
)

This being the important line here:
Code:
$lra = if($time -lt 3.4){"-"}else{$array_tmsir[4]}
Which sets values for LRA to "-" if the time is less than 3.4 seconds.

To facilitate Powershell understanding the values and not terminating the LRA data at 100s, I switched up formatting the time value to a single decimal earlier. It also makes the creation of the csv line easier on the eye.

I see a bigger problem with true peaks.
I think libebur128 is sampling at 10 Hz, and probably missing many true peak overs. This coupled with the pixel resolution of the plot, it doesn't seem like we'll really see the overs if they are just the odd fleeting transient. Perhaps using another tool to point out where they are in time, and overlaying that onto the plot might work.
 
Top Bottom