first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
# loudness-range
## Purpose
`loudness-range` computes the EBU Tech 3342 loudness range (LRA, in LU) — a
statistical measure of dynamic spread. It takes short-term loudness samples
(K-weighted, channel-summed loudness over 3 s windows stepped by 1 s) and
returns the 95th-minus-10th percentile spread of the gated distribution.
LRA complements integrated loudness: two programmes can share an integrated
LUFS but have wildly different listener experience if one is uniformly
compressed and the other has dynamic swings. Broadcasters use LRA thresholds
(e.g. EBU R128 recommends ≤ 7 LU for spoken-word programming) to flag
material that needs dynamics processing before transmission.
## Features
- Two-gate algorithm: absolute gate at −70 LUFS, relative gate 20 LU below
the gated mean (wider than the BS.1770-4 integrated-loudness 10 LU gate).
- Linear-interpolated percentiles per Tech 3342.
- Returns `lra`, `low`, `high`, and `gatedCount` for downstream UIs.
- Sub-window documents safely report `lra: 0` instead of throwing.
## Usage: Basic
```ts
import { loudnessRange } from "@suaveplan/audio-analysis-loudness/algorithms/loudness-range";
const { lra } = loudnessRange(shortTermLufsValues);
```
## Usage: Advanced
```ts
import {
loudnessRange,
percentile,
} from "@suaveplan/audio-analysis-loudness/algorithms/loudness-range";
const result = loudnessRange(shortTermLufsValues);
console.log({
lra: result.lra,
p10: result.low,
p95: result.high,
surviving: result.gatedCount,
});
// Inspect arbitrary percentiles for diagnostic overlays.
const sorted = [...shortTermLufsValues].sort((a, b) => a - b);
const p50 = percentile(sorted, 0.5);
```
## API Reference
- `LRA_RELATIVE_GATE_OFFSET_LU` — `-20`, the Tech 3342 relative-gate offset.
- `loudnessRange(shortTermLufs: readonly number[]) => LoudnessRangeResult`.
- `percentile(sortedAsc, q)` — linear-interpolated q ∈ [0,1].
## Implementation Notes
The mean used for the relative gate is computed in linear power and then
converted back to LUFS via `energyToLufs`, not as an arithmetic mean of LUFS
values — averaging in dB would over-weight quiet blocks. Edge cases (no
input, no survivors after the absolute gate, no survivors after the relative
gate) all map cleanly to `lra: 0` with the best-available `low`/`high` so
downstream consumers never see NaN.

View File

@@ -0,0 +1,57 @@
# lufs
## Purpose
`lufs` contains the kernel of BS.1770-4 loudness measurement: the per-block
mean-square energy calculation, the BS.1770 channel-weighted summation, the
energy-to-LUFS conversion, and the streaming `IntegratedLoudness` accumulator
that implements the two-gate algorithm (absolute gate at −70 LUFS, then a
relative gate 10 LU below the ungated mean of survivors). The kernels are
document-agnostic — they consume `Float32Array[]` blocks and numbers — so a
true-peak limiter can pull `momentaryLufs` without instantiating a meter.
## Features
- Pure functions: `blockMeanSquare`, `combineChannelEnergies`,
`energyToLufs`, `momentaryLufs`, `shortTermLufs`.
- Streaming `IntegratedLoudness` class with two-gate integration.
- BS.1770 channel weights — L/R/C at 1.0, surround at 1.41, LFE excluded.
- Finite silence floor (`LUFS_SILENCE_FLOOR = -100`) instead of `-Infinity`.
## Usage: Basic
```ts
import { momentaryLufs } from "@suaveplan/audio-analysis-loudness/algorithms/lufs";
const lufs = momentaryLufs(kWeightedBlock);
```
## Usage: Advanced
```ts
import { IntegratedLoudness } from "@suaveplan/audio-analysis-loudness/algorithms/lufs";
const integrator = new IntegratedLoudness(2);
for (const block of kWeightedGatingBlocks) integrator.pushBlock(block);
const integrated = integrator.integrated();
console.log({ integrated, surviving: integrator.gatedBlockCount });
```
## API Reference
- `LUFS_SILENCE_FLOOR`, `ABSOLUTE_GATE_LUFS`, `RELATIVE_GATE_OFFSET_LU`,
`CHANNEL_WEIGHTS_BS1770` — frozen constants.
- `channelWeight(index)`, `blockMeanSquare(block)`,
`combineChannelEnergies(perChannel)`, `energyToLufs(weightedMS)`,
`momentaryLufs(block)`, `shortTermLufs(block)`.
- `class IntegratedLoudness` — `pushBlock`, `integrated`, `reset`,
`gatedBlockCount`.
## Implementation Notes
The integrator stores the linear weighted mean-square values that survived
the absolute gate rather than their LUFS counterparts, because the relative
gate is itself derived from the linear mean. This avoids a second
`Math.pow(10, …)` pass per block. The silence floor is the BS.1770-4 minimum
reported value (−70 LUFS for "no programme") rounded down to −100 to give
downstream UI meters a stable bottom.

View File

@@ -0,0 +1,56 @@
# true-peak
## Purpose
`true-peak` implements true-peak (dBTP) detection via 4× polyphase
oversampling, per ITU-R BS.1770-4 Annex 2. Sample peak — `max |x[n]|` — under-
reports the real analog peak because the true waveform crests *between*
samples; near-Nyquist signals routinely show inter-sample peaks 1–3 dB above
the sample peak. Broadcast normalisation pipelines rely on dBTP to keep
material clear of the digital ceiling after DAC reconstruction.
## Features
- 4-phase polyphase FIR with 12-tap kernel per phase.
- `truePeakDbtp(block)` — true-peak across all channels in dBTP.
- `samplePeakLinear(block)` — sample peak in linear amplitude.
- Finite floor at −200 dBTP for digital silence (no `-Infinity`).
- Edge-aware: taps falling outside the buffer are zero-padded.
## Usage: Basic
```ts
import { truePeakDbtp } from "@suaveplan/audio-analysis-loudness/algorithms/true-peak";
const dbtp = truePeakDbtp([leftChannel, rightChannel]);
```
## Usage: Advanced
```ts
import {
truePeakDbtp,
samplePeakLinear,
} from "@suaveplan/audio-analysis-loudness/algorithms/true-peak";
const tp = truePeakDbtp(channels);
const sp = 20 * Math.log10(samplePeakLinear(channels));
console.log({ samplePeakDb: sp, truePeakDbtp: tp, intersampleMargin: tp - sp });
```
## API Reference
- `truePeakDbtp(block: readonly Float32Array[]) => number` — dBTP across all
channels.
- `samplePeakLinear(block: readonly Float32Array[]) => number` — linear-
amplitude sample peak.
## Implementation Notes
The polyphase kernel is a fixed 12-tap windowed-sinc low-pass (Kaiser β=8,
cutoff 0.45·Nyquist), normalised per phase to unity DC gain. Phase 0 is the
identity phase so the original samples are always included in the search and
`truePeakDbtp ≥ samplePeakDb` always holds. BS.1770-4 permits higher
oversampling ratios; the meter sticks to the specified minimum of 4× because
it is sufficient up to 48 kHz material — a configurable ratio would add
surface area for negligible accuracy gain in the target range.