Skip to content

Meter loudness and check deliveries

core/loudness measures loudness to ITU-R BS.1770-4 and EBU R 128, runs the classic programme meters (peak, PPM, VU, RMS), computes file statistics for quality control and the gain that reaches a delivery target. Meters run on the audio thread without allocating; any thread reads them lock-free.

Uses: core/loudness (USES loudness). Runnable version: examples/loudness/meter.

Construct and prepare on the control thread. This is where memory is allocated (LD-R34, LD-R69). Each channel carries a role, so that BS.1770 weights apply (LD-R7, LD-R8):

loudness::LoudnessMeter meter (2); // channel capacity, up to 64
auto layout = meter.prepare ({ 48'000, { loudness::ChannelRole::named ("left"), loudness::ChannelRole::named ("right") }, false });
if (! layout)
show (layout.error); // names the parameter: sample_rate, channels, ...

For 5.1, use *loudness::standardLayout (6).value (L R C LFE Ls Rs); for text such as “L, R, C”, parseChannelRoles.

meter.process (channels, frames); // noexcept SHUFFLE_RT: no allocation, lock or I/O

process takes float or double channel pointers. A reading set is published at each 100 ms step (LD-R10, LD-R36). A reset from any thread (meter.requestReset()) takes effect at the next block and starts a new generation (LD-R35).

To meter the device output, implement the OutputStage port. Call meter.process from its process, which runs on the audio thread after the mix. Prepare the meter on the control thread before you attach the stage. For a point inside the graph, the engine’s TapNode carries a ProgrammeMeter on any audio pin (EN-R129 to EN-R131).

const auto r = meter.readings(); // any thread, lock-free, one instant
if (r.integrated.valid && ! r.integrated.belowFloor)
draw (r.integrated.value); // LUFS; also momentary, shortTerm, loudnessRange, truePeak
meter.takePeaksSinceLastRead (samplePeaksDb, truePeaksDb); // the display owner only

An invalid reading never looks real (LD-R30); peaks between two refreshes are never lost (LD-R38). True peak oversamples by rate (LD-R27) and never reads below the sample peak (LD-R28).

loudness::ProgrammeMeter ppm (2);
loudness::ProgrammeMeterSettings settings;
settings.type = loudness::ProgrammeMeterType::ppmType2; // samplePeak, truePeak, ppmType1, vu, rms
ppm.prepare (settings); // control thread
ppm.process (channels, frames); // audio thread
const auto level = ppm.readings().channels[0].levelDb;

Ballistics are defined in time, not blocks (LD-R46); peak-type meters have a hold and a latched clip (LD-R41, LD-R42).

A target is plain data: integrated loudness, tolerance, true-peak ceiling and an optional maximum LRA (LD-R63). Four are built in: EBU R 128 broadcast, ATSC A/85, Streaming −14 and Streaming −16 (LD-R64).

const auto measured = loudness::measuredLoudness (meter.readings());
for (const auto& target : loudness::builtInTargets())
if (auto n = loudness::normalisation (measured, target))
report (target.name, n.value->gainDb, n.value->ceilingBinds, n.value->withinTolerance);

The proposed gain never exceeds the true-peak ceiling; when the ceiling binds, shortfallLu says how far the target is missed (LD-R66). Applying the gain is another domain’s work.

FileAnalyser runs on a worker. formats decodes the file and hands over the blocks:

loudness::FileAnalyser analyser;
analyser.start ({ 48'000, roles }); // FileAnalysisSettings
// for each decoded block; false means stop (cancelled or failed):
if (! analyser.add (channels, frames)) stopDecoding();
auto stats = analyser.finish(); // FileStatistics, or the error of a cancel

FileStatistics reports peaks, RMS, DC offset, clip events, silence, integrated loudness, LRA, maximum momentary and short-term, true peak and PSR (LD-R56 to LD-R59); a statistic the file is too short for is absent with a reason (LD-R60). measuredLoudness (*stats.value) feeds normalisation.

Users, remotes and assistants act through a LoudnessCommandTarget: loudness.reset_meter, loudness.set_meter_options, loudness.analyse_file, loudness.estimate_delay, loudness.compute_normalisation_gain, loudness.create_target, loudness.edit_target and loudness.delete_target (LD-R71). Resets, analyses and delay estimates are queued for the host (takeResetRequests, takeAnalyseRequests, takeDelayRequests); give readings back with setReadings for snapshots and the gain (LD-R72).

Reference: specs/loudness.md, core/loudness.