#[doc(alias = "IP1dB")]
#[doc(alias = "OP1dB")]
#[must_use]
pub fn input_to_output_db(input_p1db: f64, gain_db: f64) -> f64 {
input_p1db + (gain_db - 1.0)
}
#[doc(alias = "IP1dB")]
#[doc(alias = "OP1dB")]
#[must_use]
pub fn output_to_input_db(output_p1db: f64, gain_db: f64) -> f64 {
output_p1db - (gain_db - 1.0)
}
#[doc(alias = "OP1dB")]
#[must_use]
pub fn cascade_output_p1db_linear(
cumulative_output_p1db_linear: f64,
current_stage_output_p1db_linear: f64,
current_stage_gain_linear: f64,
) -> f64 {
1.0 / ((1.0 / cumulative_output_p1db_linear * current_stage_gain_linear)
+ (1.0 / current_stage_output_p1db_linear))
}
#[doc(alias = "OP1dB")]
#[doc(alias = "P1dB")]
#[must_use]
pub fn cascade_output_p1db(
cumulative_output_p1db: f64,
current_stage_output_p1db: f64,
current_stage_gain: f64,
) -> f64 {
let cumulative_output_p1db_linear = crate::power::db_to_linear(cumulative_output_p1db);
let current_stage_output_linear = crate::power::db_to_linear(current_stage_output_p1db);
let current_stage_gain_linear = crate::power::db_to_linear(current_stage_gain);
let cascade_output_p1db_linear = cascade_output_p1db_linear(
cumulative_output_p1db_linear,
current_stage_output_linear,
current_stage_gain_linear,
);
crate::power::linear_to_db(cascade_output_p1db_linear)
}
#[cfg(test)]
mod tests {
#[test]
fn input_to_output_p1db() {
let input_p1db: f64 = 5.0;
let gain_db: f64 = 30.0;
let output_p1db = crate::p1db::input_to_output_db(input_p1db, gain_db);
assert_eq!(output_p1db, 34.0);
}
#[test]
fn output_to_input_p1db() {
let output_p1db: f64 = 34.0;
let gain_db: f64 = 30.0;
let input_p1db = crate::p1db::output_to_input_db(output_p1db, gain_db);
assert_eq!(input_p1db, 5.0);
}
#[test]
fn cascade_output_p1db() {
let cumulative_output_p1db: f64 = 34.0;
let current_stage_output_p1db: f64 = 20.0;
let current_stage_gain: f64 = 30.0;
let cascade_output_p1db = crate::p1db::cascade_output_p1db(
cumulative_output_p1db,
current_stage_output_p1db,
current_stage_gain,
);
let rounded = (cascade_output_p1db * 1e5).round() / 1e5;
assert_eq!(rounded, 3.89226);
}
#[test]
fn roundtrip_input_to_output_to_input() {
let input_p1db: f64 = -10.0;
let gain_db: f64 = 25.0;
let output_p1db = crate::p1db::input_to_output_db(input_p1db, gain_db);
let result = crate::p1db::output_to_input_db(output_p1db, gain_db);
assert_eq!(input_p1db, result);
}
#[test]
fn input_to_output_negative_gain_attenuator() {
let result = crate::p1db::input_to_output_db(20.0, -10.0);
assert_eq!(result, 9.0);
}
#[test]
fn input_to_output_zero_gain() {
let result = crate::p1db::input_to_output_db(10.0, 0.0);
assert_eq!(result, 9.0);
}
#[test]
fn output_to_input_negative_gain() {
let result = crate::p1db::output_to_input_db(9.0, -10.0);
assert_eq!(result, 20.0);
}
#[test]
fn cascade_three_stage_amplifier_chain() {
let stages: Vec<(f64, f64)> = vec![(30.0, 20.0), (25.0, 15.0), (35.0, 10.0)];
let mut cumulative = stages[0].0; for &(op1db, gain) in &stages[1..] {
cumulative = crate::p1db::cascade_output_p1db(cumulative, op1db, gain);
}
assert!(
cumulative < 25.0,
"cascade should be limited by weakest stage"
);
assert!(cumulative > 0.0, "cascade should be positive");
}
#[test]
fn cascade_identical_stages() {
let result = crate::p1db::cascade_output_p1db(20.0, 20.0, 10.0);
assert!(result < 20.0);
}
#[test]
fn cascade_linear_known_value() {
let result = crate::p1db::cascade_output_p1db_linear(100.0, 100.0, 1.0);
assert_eq!(result, 50.0);
}
#[test]
fn cascade_high_gain_stage_dominates() {
let result = crate::p1db::cascade_output_p1db(20.0, 40.0, 40.0);
assert!(result < 20.0);
}
#[test]
fn roundtrip_multiple_gains() {
for gain in [-20.0, -5.0, 0.0, 10.0, 30.0, 50.0] {
let ip1db = 5.0;
let op1db = crate::p1db::input_to_output_db(ip1db, gain);
let back = crate::p1db::output_to_input_db(op1db, gain);
assert!(
(back - ip1db).abs() < 1e-10,
"roundtrip failed for gain={gain}"
);
}
}
}