Files
skills/docs/media/audio/audio-analysis-loudness/src/meter/loudness-meter.md
2026-05-29 08:20:56 +01:00

3.3 KiB
Raw Blame History

loudness-meter

(no submodules)

Purpose

LoudnessMeter is the orchestration layer of @suaveplan/audio-analysis-loudness. It wires the K-weighting filter, the gated-integration engine, the short-term LUFS accumulator (for loudness range), the 4× oversampled true-peak detector, and the RMS / sample-peak / DC-offset accumulators together behind three entry points — full-document analyze, streaming analyzeStream, and the real-time analyzeBlock. It is a sidecar processor (plugin slot 3) and never participates in the render pipeline.

The class also threads OTel spans, the run counter, and per-block histograms through every entry point, validates all options at the Zod boundary, and propagates AbortSignal cancellation to mid-stream block boundaries.

Features

  • ITU-R BS.1770-4 integrated LUFS via two-gate (absolute + relative).
  • EBU Tech 3342 loudness range (LRA in LU).
  • 4× oversampled true-peak in dBTP (per BS.1770-4 Annex 2).
  • Sample peak in dB, RMS in dB, per-channel DC offset.
  • Streaming analyzeStream whose final yield equals batch analyze.
  • Real-time analyzeBlock for live meters — synchronous, allocation-light.
  • Finite silence floors (no -Infinity ever leaks to consumers).
  • AbortSignal propagation at gating-block boundaries.
  • OTel span, counter, histograms — inert without an exporter.

Usage: Basic

import { LoudnessMeter } from "@suaveplan/audio-analysis-loudness";

const meter = new LoudnessMeter();
const result = await meter.analyze(doc);
console.log(result.lufs, result.truePeak, result.loudnessRange);

Usage: Advanced

import { LoudnessMeter } from "@suaveplan/audio-analysis-loudness";

const meter = new LoudnessMeter({ blockSize: 400, overlap: 0.75, targetLufs: -23 });

// Streaming integrated loudness — final yield equals batch analyze.
const ctrl = new AbortController();
for await (const partial of meter.analyzeStream(doc, { signal: ctrl.signal })) {
  ui.updateLoudness(partial.lufs);
}

// Real-time block meter — call on every audio callback.
const partial = meter.analyzeBlock([left, right], {
  sampleRate: 48000,
  channelCount: 2,
});
console.log(partial.lufs, partial.peak, partial.rms);

API Reference

  • class LoudnessMeter
    • readonly name: "loudness-meter", readonly description: string, readonly version: string.
    • constructor(options?: LoudnessMeterOptions).
    • analyze(doc, opts?) => Promise<LoudnessResult>.
    • analyzeStream(doc, opts?) => AsyncGenerator<LoudnessResult>.
    • analyzeBlock(block, ctx) => Partial<LoudnessResult>.
  • LoudnessMeterOptions — blockSize, overlap, targetLufs, signal.
  • LoudnessResult extends AnalysisResult with lufs, loudnessRange, truePeak, rms, peak, dcOffset[].

Implementation Notes

analyze and analyzeStream share one GatingEngine. The streaming path yields the engine's running state after every gating block; the batch path runs the same engine to completion. The invariant is that the last analyzeStream yield equals the analyze result for the same document. analyzeBlock keeps a per-instance scratch K-weighting filter so live-meter callers do not allocate on every audio callback. The scratch filter is re-built lazily when (sampleRate, channelCount) changes.