Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion packages/loudness-spectro-wasm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ npm run build:node # -> pkg-node/ (for the Node smoke test)

## Validation

Three independent layers, run by `npm run test:rust` (`cargo test`):
Three independent `cargo test` layers, plus two Python dev helpers:

1. **Absolute EBU Tech 3341/3342 conformance** (primary oracle,
`crates/asa-dsp/tests/ebu_conformance.rs`). Synthesized, network-free signals
Expand All @@ -100,6 +100,21 @@ Three independent layers, run by `npm run test:rust` (`cargo test`):
pip install pyloudnorm soundfile numpy
python scripts/pyloudnorm_crosscheck.py <corpus-dir> # asserts |Δ| < 0.5 LU
```
4. **Essentia parity** (the check that gates wiring this package into ASA —
`scripts/essentia_parity.py`, dev helper). ASA's authoritative Phase 1
loudness is Essentia's `LoudnessEBUR128`; before asa-dsp can back any product
path it must agree with it. The harness runs a deterministic synthetic corpus
through both asa-dsp (`measure-cli`) and Essentia at native rate and reports
integrated deltas, gating at **±0.1 LU**. Run with a Python that has Essentia
(the repo's backend venv):

```bash
npm run build:cli # asa-dsp side
../../apps/backend/venv/bin/python scripts/essentia_parity.py # Essentia side
```

The committed [`docs/essentia-parity-report.md`](docs/essentia-parity-report.md)
is the latest run (the WS3a checkpoint).

`npm run test:smoke` additionally exercises the generated WASM from Node.

Expand Down
44 changes: 44 additions & 0 deletions packages/loudness-spectro-wasm/docs/essentia-parity-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# asa-dsp ↔ Essentia loudness parity report

_Generated 2026-06-02 by `scripts/essentia_parity.py` (WS3a checkpoint)._

**Verdict: ❌ FAIL** — primary gate is integrated LUFS within ±0.1 LU on every file.

- Worst integrated delta: **0.102 LU** (tolerance ±0.1).
- Corpus: 8 deterministic synthetic signal(s) @ 48000 Hz, 12s stereo (seed 0xA5AD5).
- Breaches: log_sweep_20_20k (Δ -0.102 LU).

## Integrated LUFS (the gate)

| file | Essentia | asa-dsp | Δ (asa−ess) | status |
|---|---:|---:|---:|---|
| sine_1k | -6.00 | -6.01 | -0.012 | ok |
| hot_sine_3k | 2.67 | 2.67 | -0.002 | ok |
| quiet_sine_1k | -30.45 | -30.45 | -0.002 | ok |
| two_tone_100_3k | -4.77 | -4.79 | -0.018 | ok |
| pink_noise | -14.71 | -14.71 | -0.001 | ok |
| white_noise | -13.46 | -13.46 | -0.000 | ok |
| stereo_decorrelated_pink | -15.07 | -15.07 | +0.001 | ok |
| log_sweep_20_20k | -2.08 | -2.18 | -0.102 | FAIL |

## Secondary metrics (reported, not gated)

| file | Short-term max LUFS | Momentary max LUFS | LRA (LU) | True peak dBTP |
|---|---:|---:|---:|---:|
| sine_1k | -6.00 / -6.01 (Δ -0.01) | -6.00 / -6.01 (Δ -0.01) | 0.00 / 0.00 (Δ -0.00) | -6.02 / -6.02 (Δ -0.00) |
| hot_sine_3k | 2.67 / 2.67 (Δ -0.00) | 2.67 / 2.67 (Δ -0.00) | 0.00 / 0.00 (Δ -0.00) | -0.39 / -0.45 (Δ -0.06) |
| quiet_sine_1k | -30.45 / -30.45 (Δ -0.00) | -30.45 / -30.45 (Δ -0.00) | 0.00 / 0.00 (Δ -0.00) | -30.46 / -30.46 (Δ -0.00) |
| two_tone_100_3k | -4.77 / -4.79 (Δ -0.02) | -4.77 / -4.79 (Δ -0.02) | 0.00 / 0.00 (Δ -0.00) | -3.10 / -3.10 (Δ +0.00) |
| pink_noise | -14.67 / -14.66 (Δ +0.00) | -14.52 / -14.52 (Δ -0.00) | 0.05 / 0.05 (Δ +0.01) | -2.89 / -2.95 (Δ -0.06) |
| white_noise | -13.42 / -13.42 (Δ -0.00) | -13.34 / -13.34 (Δ -0.00) | 0.07 / 0.06 (Δ -0.01) | -6.61 / -6.02 (Δ +0.59) |
| stereo_decorrelated_pink | -15.02 / -14.95 (Δ +0.08) | -14.94 / -14.94 (Δ +0.00) | 0.06 / 0.05 (Δ -0.00) | -2.94 / -2.91 (Δ +0.03) |
| log_sweep_20_20k | 0.24 / 0.24 (Δ -0.00) | 0.25 / 0.25 (Δ -0.00) | 6.08 / 6.05 (Δ -0.03) | -2.91 / -2.99 (Δ -0.08) |

> Cells show `Essentia / asa-dsp (Δ asa−ess)`. True peak is converted from Essentia's linear TruePeakDetector output via `20·log10` to match asa-dsp's dBTP; it is a sanity signal, not a gate.

## Method & caveats

- Both paths decode each WAV at its **native rate** with no resampling. Essentia uses `AudioLoader` → `LoudnessEBUR128` (identical to `analyze_core.analyze_loudness`); asa-dsp uses the `measure-cli` binary (source-identical to the WASM core).
- The corpus is **synthetic** (tones, sweep, white/pink noise, decorrelated stereo). Broadband noise is the most demanding case for K-weighting agreement; tones are easy. Real-program parity should be re-confirmed before flipping any default.
- **True peak diverges materially on broadband content** (see `white_noise`: a ~0.6 dBTP gap) because the two oversampling/reconstruction filters differ. Integrated loudness is unaffected. WS3c must **not** expose a browser true-peak readout without its own gate — treat true peak as a known divergence until that is closed.
- This is the WS3a checkpoint. A PASS clears WS3b/WS3c to proceed *behind a default-off flag*; the loudness default only flips to asa-dsp after real-program parity is also proven.
Loading