// Tests can be downloaded from https://tech.ebu.ch/publications/ebu_loudness_test_set // and should be put into tests/reference_files // // Expected results are in // - https://tech.ebu.ch/docs/tech/tech3341.pdf // - https://tech.ebu.ch/docs/tech/tech3342.pdf macro_rules! read_samples { ($reader:expr, 16) => { $reader .into_samples::() .map(|s| s.expect("Failed to read samples from reference file")) .collect::>() }; ($reader:expr, 24) => { $reader .into_samples::() .map(|s| s.expect("Failed to read samples from reference file") << 8) .collect::>() }; } macro_rules! prepare_file { ($file_name:expr, $rate:expr, $bpp:tt, $mode:expr, $channel_map:expr) => {{ let input_path = { let mut r = std::path::PathBuf::new(); r.push(env!("CARGO_MANIFEST_DIR")); r.push("tests"); r.push("reference_files"); r.push($file_name); r }; if !input_path.exists() { panic!("Reference file {} not found", input_path.display()); } let mut e = ebur128::EbuR128::new($channel_map.len() as u32, $rate, $mode) .expect("Can't create EbuR128 instance"); e.set_channel_map(&$channel_map).unwrap(); let reader = hound::WavReader::open(&input_path).expect("Failed to read reference file"); let samples = read_samples!(reader, $bpp); (e, samples) }}; } macro_rules! add_samples { ($e:expr, $samples:expr, 16) => { $e.add_frames_i16(&*$samples).expect("Failed to analyze samples"); }; ($e:expr, $samples:expr, 24) => { $e.add_frames_i32(&*$samples).expect("Failed to analyze samples"); }; } macro_rules! test_global_loudness( ($file_name:expr, $rate:expr, $bpp:tt, $channel_map:expr, $expected_loudness:expr) => { { let (mut e, samples) = prepare_file!($file_name, $rate, $bpp, ebur128::Mode::I, $channel_map); add_samples!(e, samples, $bpp); float_eq::assert_float_eq!(e.loudness_global().expect("Failed to get global loudness"), $expected_loudness, abs <= 0.1); } { let (mut e, samples) = prepare_file!($file_name, $rate, $bpp, ebur128::Mode::I | ebur128::Mode::HISTOGRAM, $channel_map); add_samples!(e, samples, $bpp); float_eq::assert_float_eq!(e.loudness_global().expect("Failed to get global loudness"), $expected_loudness, abs <= 0.1); } }; ); macro_rules! test_true_peak( ($file_name:expr, $rate:expr, $bpp:tt, $channel_map:expr, $expected_true_peak:expr) => { let (mut e, samples) = prepare_file!($file_name, $rate, $bpp, ebur128::Mode::TRUE_PEAK, $channel_map); add_samples!(e, samples, $bpp); let mut max_true_peak = f64::MIN; for peak in (0..$channel_map.len()) .map(|c| e.true_peak(c as u32).expect("Failed to get true peak")) { if peak > max_true_peak { max_true_peak = peak; } } let max_true_peak = 20.0 * f64::log10(max_true_peak); assert!( ($expected_true_peak - 0.4..=$expected_true_peak + 0.2).contains(&max_true_peak), "{} != {}", max_true_peak, $expected_true_peak, ); }; ); macro_rules! test_loudness_range( ($file_name:expr, $rate:expr, $bpp:tt, $channel_map:expr, $expected_loudness_range:expr) => { { let (mut e, samples) = prepare_file!($file_name, $rate, $bpp, ebur128::Mode::LRA, $channel_map); add_samples!(e, samples, $bpp); let loudness_range = e.loudness_range().expect("Failed to get loudness range"); float_eq::assert_float_eq!(loudness_range, $expected_loudness_range, abs <= 1.0, "queue mode"); } { let (mut e, samples) = prepare_file!($file_name, $rate, $bpp, ebur128::Mode::LRA | ebur128::Mode::HISTOGRAM, $channel_map); add_samples!(e, samples, $bpp); let loudness_range = e.loudness_range().expect("Failed to get loudness range in histogram mode"); float_eq::assert_float_eq!(loudness_range, $expected_loudness_range, abs <= 1.0, "histogram mode"); } }; ); #[test] fn seq_3341_1() { { test_global_loudness!( "seq-3341-1-16bit.wav", 48_000, 16, [ebur128::Channel::Left, ebur128::Channel::Right], -23.0 ); } { let (mut e, samples) = prepare_file!( "seq-3341-1-16bit.wav", 48_000, 16, ebur128::Mode::S, [ebur128::Channel::Left, ebur128::Channel::Right] ); // 100ms chunks / 10Hz for (i, chunk) in samples.chunks(2 * 48_000 / 10).enumerate() { e.add_frames_i16(chunk).expect("Failed to analyze samples"); // Constant after 3s if i >= 29 { float_eq::assert_float_eq!( e.loudness_shortterm() .expect("Failed to get shortterm loudness"), -23.0, abs <= 0.1 ); } } } { let (mut e, samples) = prepare_file!( "seq-3341-1-16bit.wav", 48_000, 16, ebur128::Mode::M, [ebur128::Channel::Left, ebur128::Channel::Right] ); // 100ms chunks / 10Hz for (i, chunk) in samples.chunks(2 * 48_000 / 10).enumerate() { e.add_frames_i16(chunk).expect("Failed to analyze samples"); // Constant after 1s if i >= 10 { float_eq::assert_float_eq!( e.loudness_momentary() .expect("Failed to get momentary loudness"), -23.0, abs <= 0.1 ); } } } } #[test] fn seq_3341_2() { test_global_loudness!( "seq-3341-2-16bit.wav", 48_000, 16, [ebur128::Channel::Left, ebur128::Channel::Right], -33.0 ); { let (mut e, samples) = prepare_file!( "seq-3341-2-16bit.wav", 48_000, 16, ebur128::Mode::S, [ebur128::Channel::Left, ebur128::Channel::Right] ); // 100ms chunks / 10Hz for (i, chunk) in samples.chunks(2 * 48_000 / 10).enumerate() { e.add_frames_i16(chunk).expect("Failed to analyze samples"); // Constant after 3s if i >= 29 { float_eq::assert_float_eq!( e.loudness_shortterm() .expect("Failed to get shortterm loudness"), -33.0, abs <= 0.1 ); } } } { let (mut e, samples) = prepare_file!( "seq-3341-2-16bit.wav", 48_000, 16, ebur128::Mode::M, [ebur128::Channel::Left, ebur128::Channel::Right] ); // 100ms chunks / 10Hz for (i, chunk) in samples.chunks(2 * 48_000 / 10).enumerate() { e.add_frames_i16(chunk).expect("Failed to analyze samples"); // Constant after 1s if i >= 10 { float_eq::assert_float_eq!( e.loudness_momentary() .expect("Failed to get momentary loudness"), -33.0, abs <= 0.1 ); } } } } #[test] fn seq_3341_3() { test_global_loudness!( "seq-3341-3-16bit-v02.wav", 48_000, 16, [ebur128::Channel::Left, ebur128::Channel::Right], -23.0 ); } #[test] fn seq_3341_4() { test_global_loudness!( "seq-3341-4-16bit-v02.wav", 48_000, 16, [ebur128::Channel::Left, ebur128::Channel::Right], -23.0 ); } #[test] fn seq_3341_5() { test_global_loudness!( "seq-3341-5-16bit-v02.wav", 48_000, 16, [ebur128::Channel::Left, ebur128::Channel::Right], -23.0 ); } #[test] fn seq_3341_6() { test_global_loudness!( "seq-3341-6-5channels-16bit.wav", 48_000, 16, [ ebur128::Channel::Left, ebur128::Channel::Right, ebur128::Channel::Center, ebur128::Channel::LeftSurround, ebur128::Channel::RightSurround ], -23.0 ); } #[test] fn seq_3341_6_1() { test_global_loudness!( "seq-3341-6-6channels-WAVEEX-16bit.wav", 48_000, 16, [ ebur128::Channel::Left, ebur128::Channel::Right, ebur128::Channel::Center, ebur128::Channel::Unused, ebur128::Channel::LeftSurround, ebur128::Channel::RightSurround ], -23.0 ); } #[test] fn seq_3341_7() { test_global_loudness!( "seq-3341-7_seq-3342-5-24bit.wav", 48_000, 24, [ebur128::Channel::Left, ebur128::Channel::Right], -23.0 ); } #[test] fn seq_3341_8() { test_global_loudness!( "seq-3341-2011-8_seq-3342-6-24bit-v02.wav", 48_000, 24, [ebur128::Channel::Left, ebur128::Channel::Right], -23.0 ); } #[test] fn seq_3341_9() { let (mut e, samples) = prepare_file!( "seq-3341-9-24bit.wav", 48_000, 24, ebur128::Mode::S, [ebur128::Channel::Left, ebur128::Channel::Right] ); // 100ms chunks / 10Hz for (i, chunk) in samples.chunks(2 * 48_000 / 10).enumerate() { e.add_frames_i32(chunk).expect("Failed to analyze samples"); // Constant after 3s if i >= 29 { float_eq::assert_float_eq!( e.loudness_shortterm() .expect("Failed to get shortterm loudness"), -23.0, abs <= 0.1 ); } } } #[test] fn seq_3341_10() { for i in 1..=20 { let (mut e, samples) = prepare_file!( format!("seq-3341-10-{i}-24bit.wav"), 48_000, 24, ebur128::Mode::S, [ebur128::Channel::Left, ebur128::Channel::Right] ); // 100ms chunks / 10Hz let mut max_loudness = f64::MIN; for chunk in samples.chunks(2 * 48_000 / 10) { e.add_frames_i32(chunk).expect("Failed to analyze samples"); let loudness = e .loudness_shortterm() .expect("Failed to get shortterm loudness"); if loudness > max_loudness { max_loudness = loudness; } } float_eq::assert_float_eq!(max_loudness, -23.0, abs <= 0.1, "file {}", i); } } #[test] fn seq_3341_11() { let (mut e, samples) = prepare_file!( "seq-3341-11-24bit.wav", 48_000, 24, ebur128::Mode::S, [ebur128::Channel::Left, ebur128::Channel::Right] ); // 100ms chunks / 10Hz let mut max_loudness = f64::MIN; for (i, chunk) in samples.chunks(2 * 48_000 / 10).enumerate() { e.add_frames_i32(chunk).expect("Failed to analyze samples"); let loudness = e .loudness_shortterm() .expect("Failed to get shortterm loudness"); if loudness > max_loudness { max_loudness = loudness; } if (i + 1) % 60 == 0 { let expected = -38.0 + ((i + 1) / 60 - 1) as f64; float_eq::assert_float_eq!( max_loudness, expected, abs <= 0.1, "chunk {}", (i + 1) / 60 - 1 ); } } } #[test] fn seq_3341_12() { let (mut e, samples) = prepare_file!( "seq-3341-12-24bit.wav", 48_000, 24, ebur128::Mode::M, [ebur128::Channel::Left, ebur128::Channel::Right] ); // 100ms chunks / 10Hz for (i, chunk) in samples.chunks(2 * 48_000 / 10).enumerate() { e.add_frames_i32(chunk).expect("Failed to analyze samples"); // Constant after 1s if i >= 10 { float_eq::assert_float_eq!( e.loudness_momentary() .expect("Failed to get momentary loudness"), -23.0, abs <= 0.1 ); } } } #[test] fn seq_3341_13() { for i in 1..=20 { let (mut e, samples) = prepare_file!( format!( "seq-3341-13-{}-24bit.wav{}", i, if i > 2 { ".wav" } else { "" } ), 48_000, 24, ebur128::Mode::M, [ebur128::Channel::Left, ebur128::Channel::Right] ); // 10ms chunks / 100Hz let mut max_loudness = f64::MIN; for chunk in samples.chunks(2 * 48_000 / 100) { e.add_frames_i32(chunk).expect("Failed to analyze samples"); let loudness = e .loudness_momentary() .expect("Failed to get momentary loudness"); if loudness > max_loudness { max_loudness = loudness; } } float_eq::assert_float_eq!(max_loudness, -23.0, abs <= 0.1, "file {}", i); } } #[test] fn seq_3341_14() { let (mut e, samples) = prepare_file!( "seq-3341-14-24bit.wav.wav", 48_000, 24, ebur128::Mode::M, [ebur128::Channel::Left, ebur128::Channel::Right] ); // 10ms chunks / 100Hz let mut max_loudness = f64::MIN; for (i, chunk) in samples.chunks(2 * 48_000 / 100).enumerate() { e.add_frames_i32(chunk).expect("Failed to analyze samples"); let loudness = e .loudness_momentary() .expect("Failed to get momentary loudness"); if loudness > max_loudness { max_loudness = loudness; } if (i + 1) % 80 == 0 { let expected = -38.0 + ((i + 1) / 80 - 1) as f64; float_eq::assert_float_eq!( max_loudness, expected, abs <= 0.1, "chunk {}", (i + 1) / 80 - 1 ); } } } #[test] fn seq_3341_15() { test_true_peak!( "seq-3341-15-24bit.wav.wav", 48_000, 24, [ebur128::Channel::Left, ebur128::Channel::Right], -6.0 ); } #[test] fn seq_3341_16() { test_true_peak!( "seq-3341-16-24bit.wav.wav", 48_000, 24, [ebur128::Channel::Left, ebur128::Channel::Right], -6.0 ); } #[test] fn seq_3341_17() { test_true_peak!( "seq-3341-17-24bit.wav.wav", 48_000, 24, [ebur128::Channel::Left, ebur128::Channel::Right], -6.0 ); } #[test] fn seq_3341_18() { test_true_peak!( "seq-3341-18-24bit.wav.wav", 48_000, 24, [ebur128::Channel::Left, ebur128::Channel::Right], -6.0 ); } #[test] fn seq_3341_19() { test_true_peak!( "seq-3341-19-24bit.wav.wav", 48_000, 24, [ebur128::Channel::Left, ebur128::Channel::Right], 3.0 ); } #[test] fn seq_3341_20() { test_true_peak!( "seq-3341-20-24bit.wav.wav", 48_000, 24, [ebur128::Channel::Left, ebur128::Channel::Right], 0.0 ); } #[test] fn seq_3341_21() { test_true_peak!( "seq-3341-21-24bit.wav.wav", 48_000, 24, [ebur128::Channel::Left, ebur128::Channel::Right], 0.0 ); } #[test] fn seq_3341_22() { test_true_peak!( "seq-3341-22-24bit.wav.wav", 48_000, 24, [ebur128::Channel::Left, ebur128::Channel::Right], 0.0 ); } #[test] fn seq_3341_23() { test_true_peak!( "seq-3341-23-24bit.wav.wav", 48_000, 24, [ebur128::Channel::Left, ebur128::Channel::Right], 0.0 ); } #[test] fn seq_3342_1() { test_loudness_range!( "seq-3342-1-16bit.wav", 48_000, 16, [ebur128::Channel::Left, ebur128::Channel::Right], 10.0 ); } #[test] fn seq_3342_2() { test_loudness_range!( "seq-3342-2-16bit.wav", 48_000, 16, [ebur128::Channel::Left, ebur128::Channel::Right], 5.0 ); } #[test] fn seq_3342_3() { test_loudness_range!( "seq-3342-3-16bit.wav", 48_000, 16, [ebur128::Channel::Left, ebur128::Channel::Right], 20.0 ); } #[test] fn seq_3342_4() { test_loudness_range!( "seq-3342-4-16bit.wav", 48_000, 16, [ebur128::Channel::Left, ebur128::Channel::Right], 15.0 ); } #[test] fn seq_3342_5() { test_loudness_range!( "seq-3341-7_seq-3342-5-24bit.wav", 48_000, 24, [ebur128::Channel::Left, ebur128::Channel::Right], 5.0 ); } #[test] fn seq_3342_6() { test_loudness_range!( "seq-3341-2011-8_seq-3342-6-24bit-v02.wav", 48_000, 24, [ebur128::Channel::Left, ebur128::Channel::Right], 15.0 ); }