ebur128_stream/lib.rs
1//! # ebur128-stream
2//!
3//! Streaming, zero-allocation EBU R128 loudness measurement in pure Rust.
4//!
5//! `ebur128-stream` implements the
6//! [ITU-R BS.1770-4](https://www.itu.int/rec/R-REC-BS.1770) loudness
7//! algorithm and the surrounding [EBU R128] practices (momentary,
8//! short-term, integrated, true peak, loudness range) with a **push-based
9//! streaming API** that accepts arbitrary chunk sizes deterministically
10//! and performs no allocations on the hot path.
11//!
12//! [EBU R128]: https://tech.ebu.ch/docs/r/r128.pdf
13//!
14//! ## Quick start
15//!
16//! ```
17//! use ebur128_stream::{AnalyzerBuilder, Channel, Mode};
18//!
19//! let mut analyzer = AnalyzerBuilder::new()
20//! .sample_rate(48_000)
21//! .channels(&[Channel::Left, Channel::Right])
22//! .modes(Mode::Integrated | Mode::TruePeak)
23//! .build()?;
24//!
25//! // Push interleaved stereo samples (any chunk size; the analyzer is
26//! // chunk-size-deterministic).
27//! let stereo = vec![0.0f32; 9_600]; // 100 ms of silence at 48 kHz
28//! analyzer.push_interleaved(&stereo)?;
29//!
30//! let report = analyzer.finalize();
31//! // For silent input, integrated loudness is None (nothing cleared the
32//! // -70 LUFS absolute gate).
33//! assert!(report.integrated_lufs().is_none());
34//! # Ok::<(), ebur128_stream::Error>(())
35//! ```
36//!
37//! ## Concepts
38//!
39//! ### What is loudness?
40//!
41//! Loudness is the *perceived* energy of an audio signal — what the ear
42//! hears, not what a meter sees. The classic peak / RMS meter measures
43//! signal *amplitude*; loudness meters apply a frequency-weighted
44//! filter (the **K-weighting** filter of BS.1770) that approximates the
45//! ear's sensitivity, then sum energy across channels with appropriate
46//! per-channel weights, and report a single calibrated value in
47//! **LUFS** (Loudness Units relative to Full Scale).
48//!
49//! Two signals at the same RMS can differ by 10 LU in measured
50//! loudness if one has more high-frequency content than the other.
51//! This is why broadcasters use LUFS rather than peak / RMS for
52//! loudness compliance.
53//!
54//! ### Momentary, short-term, integrated
55//!
56//! BS.1770 defines three measurement windows:
57//!
58//! ```text
59//! M (momentary) : sliding 400 ms window → quick reaction
60//! S (short-term) : sliding 3 s window → smoother envelope
61//! I (integrated) : full programme, gated → "the" loudness number
62//! ```
63//!
64//! Integrated loudness uses **gating** to ignore quiet sections that
65//! shouldn't drag the programme average down: an absolute gate at
66//! -70 LUFS, then a relative gate -10 LU below the absolute-gated mean.
67//!
68//! ### True peak vs. sample peak
69//!
70//! Sample peak — the largest absolute value in the PCM buffer — undercounts
71//! the actual signal level whenever a peak falls *between* samples. True
72//! peak (per BS.1770 Annex 2) approximates the analog reconstructed signal
73//! by 4× upsampling through a 12-tap polyphase FIR and reports the
74//! oversampled max in **dBTP** (decibels relative to full scale, true
75//! peak).
76//!
77//! ## Choosing a mode
78//!
79//! | Use case | Modes to request |
80//! |---|---|
81//! | Real-time monitor | [`Mode::Momentary`] + [`Mode::ShortTerm`] |
82//! | Programme delivery QC | [`Mode::Integrated`] + [`Mode::TruePeak`] |
83//! | Mastering / archival | [`Mode::All`] |
84//! | True-peak limiter feed | [`Mode::TruePeak`] only |
85//!
86//! ## Performance characteristics
87//!
88//! - **Allocations on `push_*`:** zero in steady state. The programme
89//! buffer ([`Mode::Integrated`], [`Mode::Lra`]) may grow when its
90//! `Vec` exceeds capacity; pre-call [`Vec::with_capacity`] semantics
91//! are out of scope for V0.1 but tracked for V0.2.
92//! - **Computational cost:** O(samples) per push, dominated by the
93//! K-weighting biquad and (when enabled) the 4× polyphase FIR.
94//! - **Snapshot cost:** O(1) for [`Snapshot::momentary_lufs`] /
95//! [`Snapshot::short_term_lufs`] (cached); O(programme blocks) for
96//! [`Snapshot::integrated_lufs`] (cached and invalidated on next
97//! push).
98//!
99//! ## Compliance
100//!
101//! Calibration is verified against synthetic stimuli matching the
102//! ITU-R BS.1770-4 reference signals. Independent verification against
103//! the official [EBU Tech 3341] test vectors is in progress; see
104//! `tests/calibration.rs` for the current cross-checks.
105//!
106//! [EBU Tech 3341]: https://tech.ebu.ch/docs/tech/tech3341.pdf
107//!
108//! ## See also
109//!
110//! - [`bs1770`](https://crates.io/crates/bs1770) — filter only,
111//! no gating, no streaming API.
112//! - [`ebur128`](https://crates.io/crates/ebur128) — Rust bindings to
113//! the C `libebur128`.
114
115#![cfg_attr(not(feature = "std"), no_std)]
116#![deny(missing_docs)]
117#![deny(rustdoc::broken_intra_doc_links)]
118#![forbid(unsafe_code)]
119#![cfg_attr(docsrs, feature(doc_cfg))]
120
121#[cfg(feature = "alloc")]
122extern crate alloc;
123
124mod analyzer;
125mod blocks;
126mod channel;
127mod error;
128mod filter;
129#[cfg(feature = "alloc")]
130mod gating;
131#[cfg(feature = "alloc")]
132mod lra;
133mod mode;
134#[cfg(feature = "normalize")]
135pub mod normalize;
136#[cfg(feature = "alloc")]
137mod peak;
138mod report;
139#[cfg(feature = "resampler")]
140pub mod resampler;
141mod sample;
142#[cfg(feature = "tokio")]
143mod sink;
144mod snapshot;
145#[cfg(feature = "svg")]
146pub mod svg;
147
148pub use analyzer::{Analyzer, AnalyzerBuilder};
149pub use channel::Channel;
150pub use error::Error;
151pub use mode::Mode;
152pub use report::Report;
153pub use sample::Sample;
154#[cfg(feature = "tokio")]
155pub use sink::AnalyzerSink;
156pub use snapshot::Snapshot;