SUPERB
Examples

measure_loudness

examples/primitives/measure_loudness.rs, the whole program

Run it with cargo run --example measure_loudness (see the examples for the features some need).

//! Two seconds of 1 kHz at −12 dBFS: peak and true peak −12.00 dB, RMS
//! −15.04 dBFS, integrated −15.01 LUFS. Normalising to −16 LUFS applies
//! −0.99 dB. A copy delayed 20 ms lines up at 960 samples.
//!
//! ```text
//! cargo run --example measure_loudness
//! ```

use superb::primitives::analysis::{meter, offset, silence};
use superb::primitives::buffers::gain::{db_to_linear, linear_to_db};
use superb::primitives::buffers::PlanarBuffer;
use superb::primitives::{Result, SampleRate};

fn main() -> Result<()> {
    let rate = SampleRate::HZ_48K;
    let hz = rate.hz() as f32;
    assert_eq!(rate.seconds_to_samples(2.0), 96_000);

    // 96000 samples of 1 kHz at −12 dBFS. The first and last 480 samples
    // (10 ms) fade. A 1 kHz cycle is 48 samples, so sample 492 is a crest
    // past the fade and the tone reaches −12.00 dBFS.
    let mut tone: Vec<f32> = (0..2 * rate.hz() as usize)
        .map(|i| db_to_linear(-12.0) * (core::f32::consts::TAU * 1_000.0 * i as f32 / hz).sin())
        .collect();
    assert_eq!(tone.len(), 96_000);
    let n = tone.len();
    let fade = 480;
    assert!((rate.samples_to_seconds(fade as i64) - 0.01).abs() < 1e-9);
    for i in 0..fade {
        let g = i as f32 / fade as f32;
        tone[i] *= g;
        tone[n - 1 - i] *= g;
    }
    let full = db_to_linear(-12.0);
    assert!((tone[492] - full).abs() < 1e-4);
    assert_eq!(format!("{:.2}", linear_to_db(full)), "-12.00");

    // The crest lands on a sample, so true peak equals the sample peak,
    // −12.00 dB. RMS of the faded tone is −15.04 dBFS.
    let peak_db = linear_to_db(meter::peak(&tone));
    let rms_db = linear_to_db(meter::rms(&tone));
    let true_peak_db = linear_to_db(meter::true_peak(&tone));
    assert_eq!(format!("{peak_db:.2}"), "-12.00");
    assert_eq!(format!("{rms_db:.2}"), "-15.04");
    assert_eq!(format!("{true_peak_db:.2}"), "-12.00");
    println!(
        "sample peak {:.2} dBFS, rms {:.2} dBFS, true peak {:.2} dBTP",
        peak_db, rms_db, true_peak_db
    );

    // Fed in blocks of 256. After 2.0 s, momentary (400 ms) is −15.08 LUFS.
    // Short-term needs 3 s, so it is absent. Integrated is −15.01 LUFS.
    let mut loudness = meter::Loudness::new(rate, 1)?;
    for block in tone.chunks(256) {
        loudness.push(&[block])?;
    }
    let momentary = loudness.momentary().unwrap_or(f32::NEG_INFINITY);
    let short_term = loudness.short_term().unwrap_or(f32::NEG_INFINITY);
    let integrated = loudness.integrated().unwrap_or(f32::NEG_INFINITY);
    assert_eq!(format!("{:.1}", loudness.seconds()), "2.0");
    assert!(loudness.short_term().is_none());
    assert_eq!(format!("{momentary:.2}"), "-15.08");
    assert_eq!(format!("{integrated:.2}"), "-15.01");
    println!(
        "after {:.1} s: momentary {:.2} LUFS, short-term {:.2} LUFS, integrated {:.2} LUFS",
        loudness.seconds(),
        momentary,
        short_term,
        integrated
    );

    // Target −16 LUFS under a −1 dBTP ceiling. The tone is −15.01 LUFS and
    // its true peak is −12 dBTP, so the gain is −0.99 dB and the ceiling
    // does not limit it. True peak lands at −12.99 dBTP.
    assert!((meter::PODCAST_LOUDNESS - -16.0).abs() < 1e-6);
    assert!((meter::TRUE_PEAK_CEILING - -1.0).abs() < 1e-6);
    let mut audio = PlanarBuffer::from_planes(&[&tone])?;
    let done = meter::normalize(
        &mut audio,
        rate,
        meter::PODCAST_LOUDNESS,
        meter::TRUE_PEAK_CEILING,
    )?
    .expect("the tone is long enough to normalise");
    assert_eq!(format!("{:+.2}", done.gain_db), "-0.99");
    assert_eq!(format!("{:.2}", done.loudness_before), "-15.01");
    assert_eq!(format!("{:.2}", done.loudness_after), "-16.00");
    assert_eq!(format!("{:.2}", done.true_peak_before), "-12.00");
    assert_eq!(format!("{:.2}", done.true_peak_after), "-12.99");
    println!(
        "normalised by {:+.2} dB: {:.2} -> {:.2} LUFS, true peak {:.2} -> {:.2} dBTP",
        done.gain_db,
        done.loudness_before,
        done.loudness_after,
        done.true_peak_before,
        done.true_peak_after
    );

    // The same tone arrives 20 ms later: 960 samples, inside a 100 ms
    // (4800-sample) search. The estimate returns that shift at confidence 0.96.
    let lag = (0.020 * hz) as usize;
    assert_eq!(lag, 960);
    let max_shift = rate.hz() as usize / 10;
    assert_eq!(max_shift, 4_800);
    assert!(lag < max_shift);
    let mut seed = 7u64;
    let mut noise = move || {
        seed = seed
            .wrapping_mul(6_364_136_223_846_793_005)
            .wrapping_add(1_442_695_040_888_963_407);
        ((seed >> 40) as f32 / (1u64 << 24) as f32) * 2.0 - 1.0
    };
    let hiss: Vec<f32> = (0..n).map(|_| 0.2 * noise()).collect();
    let reference: Vec<f32> = tone.iter().zip(&hiss).map(|(t, h)| t + h).collect();
    let other: Vec<f32> = (0..n)
        .map(|i| {
            let late = if i >= lag { reference[i - lag] } else { 0.0 };
            0.5 * late + 0.05 * noise()
        })
        .collect();
    let found = offset::estimate(&reference, &other, max_shift)?;
    assert_eq!(found.shift, 960);
    assert_eq!(format!("{:.1}", found.shift as f32 / hz * 1_000.0), "20.0");
    assert_eq!(format!("{:.2}", found.confidence), "0.96");
    println!(
        "the second microphone lags by {} samples ({:.1} ms), confidence {:.2}",
        found.shift,
        found.shift as f32 / hz * 1_000.0,
        found.confidence
    );

    // The middle second, samples 24000..72000, is scaled by 0.0001. Sample
    // 72000 is a zero of the 1 kHz tone, so the stretch is 24000..72001,
    // which prints as 0.500 s to 1.500 s. The minimum length is 0.25 s.
    let mut with_gap = tone.clone();
    let gap = rate.hz() as usize / 2..rate.hz() as usize * 3 / 2;
    assert_eq!(gap, 24_000..72_000);
    for s in &mut with_gap[gap] {
        *s *= 0.000_1;
    }
    let min_len = rate.hz() as usize / 4;
    assert_eq!(min_len, 12_000);
    let ranges = silence::stretches(&with_gap, db_to_linear(-60.0), min_len);
    assert_eq!(ranges, vec![24_000..72_001]);
    for range in &ranges {
        assert_eq!(format!("{:.3}", range.start as f32 / hz), "0.500");
        assert_eq!(format!("{:.3}", range.end as f32 / hz), "1.500");
        println!(
            "silence from {:.3} s to {:.3} s",
            range.start as f32 / hz,
            range.end as f32 / hz
        );
    }
    // Trimming only cuts the ends. The fades cross −60 dBFS at sample 4
    // and sample 95995, so the span prints as 0.000 s .. 2.000 s.
    let kept = silence::trimmed(&with_gap, db_to_linear(-60.0));
    assert_eq!(kept.start, 4);
    assert_eq!(kept.end, 95_996);
    assert_eq!(format!("{:.3}", kept.start as f32 / hz), "0.000");
    assert_eq!(format!("{:.3}", kept.end as f32 / hz), "2.000");
    println!(
        "trimmed of silence at the ends: {:.3} s .. {:.3} s",
        kept.start as f32 / hz,
        kept.end as f32 / hz
    );
    Ok(())
}