use secs_types::calibration::CalibrationSettings;
use crate::calibration::{ProjectView, engine_inputs};
use super::cases;
#[test]
fn engine_inputs_match_constructor_goldens() {
for name in cases::names() {
let case = cases::load(&name);
let settings: CalibrationSettings = serde_json::from_value(case.settings.clone()).unwrap();
let groups = cases::pairs(&case.project["groups"])
.map(|(group, roles)| {
(
group.to_owned(),
roles
.as_array()
.unwrap()
.iter()
.map(|role| role.as_str().unwrap().to_owned())
.collect(),
)
})
.collect::<Vec<_>>();
let project_xo = cases::pairs(&case.project["xo"])
.map(|(group, value)| (group.to_owned(), secs_oracle::number(value)))
.collect::<Vec<_>>();
let target = case.config.target.clone();
let resolved = engine_inputs(
&ProjectView {
groups: &groups,
project_xo: &project_xo,
target: &target,
inverted: &[],
},
&case.measurements,
&settings,
)
.unwrap();
assert_config(&resolved.config, &case.config, &name);
assert!(resolved.notes.is_empty(), "{name}");
}
}
fn assert_config(actual: &crate::EngineConfig, expected: &crate::EngineConfig, case: &str) {
assert_eq!(actual.mains, expected.mains, "{case} mains");
assert_eq!(
actual.inverted_mains, expected.inverted_mains,
"{case} polarity"
);
assert_eq!(actual.subs, expected.subs, "{case} subs");
assert_eq!(actual.groups, expected.groups, "{case} groups");
assert_eq!(actual.target, expected.target, "{case} target");
assert_eq!(actual.xo, expected.xo, "{case} xo");
assert_eq!(actual.main_position, expected.main_position, "{case}");
assert_eq!(actual.taps, expected.taps, "{case}");
assert_eq!(actual.lift_ms, expected.lift_ms, "{case}");
assert_eq!(actual.max_boost_db, expected.max_boost_db, "{case}");
assert_eq!(actual.peq_bands, expected.peq_bands, "{case}");
assert_eq!(actual.max_latency_ms, expected.max_latency_ms, "{case}");
assert_eq!(actual.rc_range, expected.rc_range, "{case}");
assert_eq!(actual.room_volume_m3, expected.room_volume_m3, "{case}");
assert_eq!(actual.phase_frac, expected.phase_frac, "{case}");
assert_eq!(actual.sub_manual, expected.sub_manual, "{case}");
assert_eq!(
actual.sub_main_delay_ms, expected.sub_main_delay_ms,
"{case}"
);
assert_eq!(actual.bundle_layouts, expected.bundle_layouts, "{case}");
assert_eq!(
actual.infra_boost_max_db, expected.infra_boost_max_db,
"{case}"
);
assert_eq!(actual.xo_peq_on, expected.xo_peq_on, "{case}");
assert_eq!(actual.xo_peq_lo_hz, expected.xo_peq_lo_hz, "{case}");
assert_eq!(actual.xo_peq_hi_hz, expected.xo_peq_hi_hz, "{case}");
}
#[test]
fn inverted_main_inputs_preserve_precision_and_match_flipped_measurements() {
use crate::{Measurement, MeasurementSet, Samples};
let groups = vec![("front".into(), vec!["L".into(), "R".into()])];
let target = secs_types::target::TargetCurve::default();
let measurements = MeasurementSet {
positions: vec![
(
1,
vec![
(
"L".into(),
Measurement {
samples: Samples::F32(vec![1.0, -0.25].into()),
sample_rate: 48_000,
t0: 0.0,
},
),
(
"R".into(),
Measurement {
samples: Samples::F64(vec![0.5, -2.0].into()),
sample_rate: 48_000,
t0: 0.0,
},
),
],
),
(
2,
vec![(
"R".into(),
Measurement {
samples: Samples::F32(vec![0.75, -0.5].into()),
sample_rate: 48_000,
t0: 0.0,
},
)],
),
],
};
let inverted = vec!["R".into()];
let inputs = engine_inputs(
&ProjectView {
groups: &groups,
project_xo: &[],
target: &target,
inverted: &inverted,
},
&measurements,
&CalibrationSettings::default(),
)
.unwrap();
assert_eq!(inputs.config.inverted_mains, ["R"]);
assert!(matches!(
&inputs.measurements.get(1, "L").unwrap().samples,
Samples::F32(values) if values.as_ref() == [1.0, -0.25]
));
assert!(matches!(
&inputs.measurements.get(1, "R").unwrap().samples,
Samples::F64(values) if values.as_ref() == [-0.5, 2.0]
));
assert!(matches!(
&inputs.measurements.get(2, "R").unwrap().samples,
Samples::F32(values) if values.as_ref() == [-0.75, 0.5]
));
}
#[test]
fn inverted_main_designs_on_the_flipped_response() {
use crate::{DesignRequest, Samples, Tuning, design};
use secs_compute::Context;
let case = cases::load("room");
let settings: CalibrationSettings = serde_json::from_value(case.settings.clone()).unwrap();
let inverted = vec!["C".into()];
let configured = engine_inputs(
&ProjectView {
groups: &case.config.groups,
project_xo: &case.config.xo,
target: &case.config.target,
inverted: &inverted,
},
&case.measurements,
&settings,
)
.unwrap();
let mut flipped = case.measurements.clone();
for (_, roles) in &mut flipped.positions {
if let Some((_, measurement)) = roles.iter_mut().find(|(role, _)| role == "C") {
measurement.samples = match &measurement.samples {
Samples::F32(values) => Samples::F32(
values
.iter()
.map(|value| -*value)
.collect::<Vec<_>>()
.into(),
),
Samples::F64(values) => Samples::F64(
values
.iter()
.map(|value| -*value)
.collect::<Vec<_>>()
.into(),
),
};
}
}
let manual = engine_inputs(
&ProjectView {
groups: &case.config.groups,
project_xo: &case.config.xo,
target: &case.config.target,
inverted: &[],
},
&flipped,
&settings,
)
.unwrap();
for ((configured_position, configured_roles), (manual_position, manual_roles)) in configured
.measurements
.positions
.iter()
.zip(&manual.measurements.positions)
{
assert_eq!(configured_position, manual_position);
for ((configured_role, configured), (manual_role, manual)) in
configured_roles.iter().zip(manual_roles)
{
assert_eq!(configured_role, manual_role);
assert_eq!(configured.samples.len(), manual.samples.len());
assert!(
(0..configured.samples.len()).all(|index| configured.samples.at(index).to_bits()
== manual.samples.at(index).to_bits())
);
}
}
let context = Context::serial();
let configured_design = design(
&DesignRequest {
measurements: &configured.measurements,
config: &configured.config,
tuning: &Tuning::default(),
subs_only: false,
adapter_notes: &[],
},
&context,
&*crate::production_fitters(context.cancel()),
)
.unwrap();
let manual_design = design(
&DesignRequest {
measurements: &manual.measurements,
config: &manual.config,
tuning: &Tuning::default(),
subs_only: false,
adapter_notes: &[],
},
&context,
&*crate::production_fitters(context.cancel()),
)
.unwrap();
let mut configured_chain = configured_design.chain;
configured_chain
.mains
.iter_mut()
.for_each(|main| main.polarity = None);
assert_eq!(configured_chain, manual_design.chain);
let without_polarity = |notes: &[crate::Note]| {
notes
.iter()
.filter(|note| {
!matches!(
note,
crate::Note::PolarityReversed { .. } | crate::Note::PolaritySplit { .. }
)
})
.cloned()
.collect::<Vec<_>>()
};
assert_eq!(
without_polarity(&configured_design.notes),
without_polarity(&manual_design.notes)
);
assert_eq!(configured_design.mains.len(), manual_design.mains.len());
for (configured, manual) in configured_design.mains.iter().zip(&manual_design.mains) {
assert_eq!(configured.role, manual.role);
assert!(
configured
.after
.iter()
.zip(manual.after.iter())
.all(|(left, right)| {
(left.re.to_bits(), left.im.to_bits())
== (right.re.to_bits(), right.im.to_bits())
})
);
}
}
#[test]
fn calibration_replay_matches_assembled_design_bit_for_bit() {
use crate::{
CalibrationRequest, DesignRequest, Tuning, calibrate_with, design, normalize_measurements,
};
use secs_compute::Context;
let mut case = cases::load("dolby_linear");
let measurement = case.measurements.positions[0].1[0].1.clone();
case.measurements.positions[0]
.1
.push(("L:LF".into(), measurement));
let settings: CalibrationSettings = serde_json::from_value(case.settings).unwrap();
let view = ProjectView {
groups: &case.config.groups,
project_xo: &case.config.xo,
target: &case.config.target,
inverted: &[],
};
let ctx = Context::serial();
let inputs = engine_inputs(&view, &case.measurements, &settings).unwrap();
let measurements =
normalize_measurements(&inputs.measurements, settings.sample_rate_policy, &ctx).unwrap();
let replay = cases::Replay::for_case("dolby_linear");
let expected = design(
&DesignRequest {
measurements: &measurements,
config: &inputs.config,
tuning: &Tuning::default(),
subs_only: false,
adapter_notes: &inputs.notes,
},
&ctx,
&*replay.factory(),
)
.unwrap();
replay.finish();
let replay = cases::Replay::for_case("dolby_linear");
let actual = calibrate_with(
&CalibrationRequest {
view: &view,
irs: &case.measurements,
settings: &settings,
subs_only: false,
},
&ctx,
&*replay.factory(),
)
.unwrap();
replay.finish();
assert_eq!(actual.resampled, 0);
assert_eq!(actual.config.mains, inputs.config.mains);
assert_eq!(
actual.measurements.positions.len(),
measurements.positions.len()
);
let actual = actual.design;
assert_eq!(actual.chain, expected.chain);
assert_eq!(actual.notes, expected.notes);
assert_eq!(actual.adapter_notes, ["바이앰프 대역 측정 제외: L:LF"]);
assert_eq!(actual.adapter_notes, expected.adapter_notes);
assert_eq!(actual.ref_level.to_bits(), expected.ref_level.to_bits());
assert_eq!(actual.rc_hi.to_bits(), expected.rc_hi.to_bits());
assert_eq!(actual.mains.len(), expected.mains.len());
for (a, b) in actual.mains.iter().zip(&expected.mains) {
assert_eq!(a.role, b.role);
for (a, b) in [
(&a.before, &b.before),
(&a.combined, &b.combined),
(&a.after, &b.after),
] {
assert_eq!(a.len(), b.len());
assert!(
a.iter()
.zip(b.iter())
.all(|(a, b)| (a.re.to_bits(), a.im.to_bits())
== (b.re.to_bits(), b.im.to_bits()))
);
}
}
assert_eq!(actual.scores.len(), expected.scores.len());
for (a, b) in actual.scores.iter().zip(&expected.scores) {
assert_eq!(a.role, b.role);
assert_eq!(a.before, b.before);
assert_eq!(a.after, b.after);
for (a, b) in [
(&a.spl_before, &b.spl_before),
(&a.spl_after, &b.spl_after),
(&a.gd_before, &b.gd_before),
(&a.gd_after, &b.gd_after),
] {
assert_eq!(a.len(), b.len());
assert!(a.iter().zip(b).all(|(a, b)| a.to_bits() == b.to_bits()));
}
}
}