Recursive AI

Measure loudness (LUFS, true peak, LRA)

All oneaudio recipes

Measure integrated loudness in LUFS, loudness range and true peak in C# with oneaudio’s ITU-R BS.1770-5 and EBU R 128 meter, for a whole file or live, block by block.

The input is game-mix.wav, the file Mix audio files writes.

C#
using OneAudio;
using OneAudio.Loudness;
 
// One call: integrated loudness, loudness range and true peak.
LoudnessResult r = Audio.Measure("game-mix.wav");
Console.WriteLine($"game-mix.wav: {r.IntegratedLufs:F1} LUFS, LRA {r.LoudnessRange:F1} LU, " +
                  $"true peak {r.TruePeakDbtp:F1} dBTP");
Console.WriteLine($"max momentary {r.MaxMomentaryLufs:F1}, max short-term {r.MaxShortTermLufs:F1}");
 
// Skip true peak (4x oversampling) when you only need LUFS: it's the costly part.
double lufs = Audio.Measure("game-mix.wav", truePeak: false).IntegratedLufs;
Console.WriteLine($"without true peak: {lufs:F1} LUFS");
 
// A live meter: feed blocks as they come, and read momentary (400 ms) and
// short-term (3 s) loudness at any time.
using IAudioDecoder decoder = Audio.Open("game-mix.wav");
var meter = new LoudnessMeter(decoder.Format, truePeak: true, loudnessRange: true);
int channels = decoder.Format.Channels;
float[] block = new float[4800 * channels];         // 100 ms
int read, n = 0;
while ((read = decoder.Read(block)) > 0)
{
    meter.Process(block.AsSpan(0, read * channels));
    if (++n % 20 == 0)                              // every 2 s
        Console.WriteLine($"{n / 10,3} s  momentary {meter.MomentaryLufs,6:F1}" +
                          $"  short-term {meter.ShortTermLufs,6:F1}");
}
Console.WriteLine($"integrated {meter.IntegratedLufs:F1} LUFS, range {meter.LoudnessRange:F1} LU, " +
                  $"true peak {meter.TruePeakDbtp:F1} dBTP");
Text
game-mix.wav: -18.7 LUFS, LRA 2.6 LU, true peak -1.8 dBTP
max momentary -14.1, max short-term -16.7
without true peak: -18.7 LUFS
  2 s  momentary  -20.7  short-term  -21.1
  4 s  momentary  -20.8  short-term  -19.4
  6 s  momentary  -16.6  short-term  -17.3
  8 s  momentary  -18.3  short-term  -17.6
 10 s  momentary  -23.6  short-term  -19.1
 12 s  momentary  -20.8  short-term  -19.9
 14 s  momentary  -22.0  short-term  -19.1
 16 s  momentary  -21.0  short-term  -20.2
integrated -18.7 LUFS, range 2.6 LU, true peak -1.8 dBTP

ffmpeg’s ebur128 filter gives the same integrated loudness (−18.7 LUFS) and true peak (−1.8 dBTP) for this file.

The readings

ReadingWhat it is
IntegratedLufsThe loudness of the whole program, gated: blocks below −70 LUFS, and blocks 10 LU below the ungated level, are left out
MomentaryLufs, MaxMomentaryLufsOver the last 400 ms
ShortTermLufs, MaxShortTermLufsOver the last 3 s
LoudnessRangeThe spread of short-term loudness in LU, per EBU Tech 3342
TruePeakDbtpThe highest peak between samples as well as on them, found by 4× oversampling, across all channels

Before any audio, the readings are negative infinity and the range is 0.

Good to know

  • The meter passes EBU Tech 3341 cases 1 to 23 and Tech 3342 cases 1 to 6 within their tolerances.
  • It works at any sample rate from 4 kHz up, and the K-weighting filters are designed for the actual rate.
  • Channels are weighted from the channel mask: 1.41 for surround and side channels, and the LFE channel is left out of the loudness but not the true peak.
  • The results don’t depend on how the audio is split into blocks. Everything is allocated in the constructor, so a meter can run on a real-time thread.
  • True peak is the expensive part. Pass truePeak: false when you only need LUFS.
  • The true-peak filter lags by 12 samples, so a peak in the last 12 frames shows only after more audio arrives.
  • The mixer has the same meters built in, per bus. See Mix audio files.

To change a file’s loudness, see Normalize loudness.

A longer guide to EBU R 128 and streaming targets: Loudness normalization in C#.