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
//! Loudness preset factory — maps a delivery character to a tuned [`AcxConfig`].
//!
//! Each preset shifts the normalisation target within or adjacent to the
//! ACX window (−23…−18 dBFS) so that different loudness styles land at a
//! consistent perceived level without manual gain tweaking.
use crate::AcxConfig;
/// Delivery loudness character.
///
/// Select the preset that best matches the intended loudness of the source
/// material, then pass the resulting [`AcxConfig`] to [`crate::process_with_config`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoudnessPreset {
/// Very soft, close-mic delivery — intimate narration.
Whispered,
/// Calm, low-energy delivery.
Soft,
/// Balanced delivery — equivalent to [`AcxConfig::default`].
Standard,
/// Clear, forward-placed delivery — authoritative narration.
Projected,
/// High-energy, emphatic delivery.
Loud,
}
impl LoudnessPreset {
/// Return an [`AcxConfig`] tuned for this loudness character.
pub fn config(&self) -> AcxConfig {
let base = AcxConfig::default();
match self {
LoudnessPreset::Whispered => AcxConfig {
rms_target_db: -22.0,
rms_min_db: -23.0,
rms_max_db: -21.0,
..base
},
LoudnessPreset::Soft => AcxConfig {
rms_target_db: -21.0,
rms_min_db: -23.0,
rms_max_db: -19.0,
..base
},
LoudnessPreset::Standard => base,
LoudnessPreset::Projected => AcxConfig {
rms_target_db: -20.0,
rms_min_db: -22.0,
rms_max_db: -18.0,
..base
},
LoudnessPreset::Loud => AcxConfig {
rms_target_db: -19.0,
rms_min_db: -21.0,
rms_max_db: -17.0,
..base
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn standard_matches_default_config() {
let std_cfg = LoudnessPreset::Standard.config();
let default = AcxConfig::default();
assert_eq!(std_cfg.rms_target_db, default.rms_target_db);
assert_eq!(std_cfg.peak_ceiling_db, default.peak_ceiling_db);
}
#[test]
fn whispered_is_quieter_than_loud() {
let w = LoudnessPreset::Whispered.config();
let l = LoudnessPreset::Loud.config();
assert!(
w.rms_target_db < l.rms_target_db,
"Whispered target ({}) should be below Loud target ({})",
w.rms_target_db,
l.rms_target_db
);
}
#[test]
fn all_presets_share_same_peak_ceiling() {
let ceiling = AcxConfig::default().peak_ceiling_db;
for p in [
LoudnessPreset::Whispered,
LoudnessPreset::Soft,
LoudnessPreset::Standard,
LoudnessPreset::Projected,
LoudnessPreset::Loud,
] {
assert_eq!(
p.config().peak_ceiling_db,
ceiling,
"{:?} has wrong peak ceiling",
p
);
}
}
}