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