#[cfg(test)]
mod tests {
use autoeq::MeasurementSource;
use autoeq::roomeq::{
CrossoverConfig, OptimizerConfig, RoomConfig, SpeakerConfig, SubwooferStrategy,
SubwooferSystemConfig, SystemConfig, SystemModel,
};
use std::collections::HashMap;
fn make_test_curve(base_level: f64) -> autoeq::Curve {
let n = 100;
let freq: Vec<f64> = (0..n)
.map(|i| 20.0 * (1000.0f64).powf(i as f64 / n as f64))
.collect();
let spl: Vec<f64> = vec![base_level; n];
autoeq::Curve {
freq: ndarray::Array1::from_vec(freq),
spl: ndarray::Array1::from_vec(spl),
phase: None,
..Default::default()
}
}
#[test]
fn test_stereo_2_0_level_alignment() {
let mut speakers = HashMap::new();
speakers.insert(
"left_meas".to_string(),
SpeakerConfig::Single(MeasurementSource::InMemory(make_test_curve(80.0))),
);
speakers.insert(
"right_meas".to_string(),
SpeakerConfig::Single(MeasurementSource::InMemory(make_test_curve(85.0))),
);
let mut system_speakers = HashMap::new();
system_speakers.insert("L".to_string(), "left_meas".to_string());
system_speakers.insert("R".to_string(), "right_meas".to_string());
let config = RoomConfig {
version: "1.2.0".to_string(),
system: Some(SystemConfig {
model: SystemModel::Stereo,
speakers: system_speakers,
subwoofers: None,
}),
speakers,
crossovers: None,
target_curve: None,
optimizer: OptimizerConfig {
max_iter: 100, ..OptimizerConfig::default()
},
recording_config: None,
cea2034_cache: None,
};
let result = autoeq::roomeq::optimize_room(&config, 48000.0, None, None)
.expect("Optimization failed");
let l_chain = &result.channels["L"];
let r_chain = &result.channels["R"];
let get_gain = |plugins: &[autoeq::roomeq::PluginConfigWrapper]| -> f64 {
for p in plugins {
if p.plugin_type == "gain" {
return p.parameters["gain_db"].as_f64().unwrap_or(0.0);
}
}
0.0
};
let l_gain = get_gain(&l_chain.plugins);
let r_gain = get_gain(&r_chain.plugins);
println!("L Gain: {}, R Gain: {}", l_gain, r_gain);
assert!(
l_gain.abs() < 0.1,
"Left should not be gained (it is lowest)"
);
assert!(
(r_gain - -5.0).abs() < 0.1,
"Right should be attenuated by -5dB"
);
}
#[test]
fn test_stereo_2_1_workflow() {
let mut speakers = HashMap::new();
speakers.insert(
"l".to_string(),
SpeakerConfig::Single(MeasurementSource::InMemory(make_test_curve(80.0))),
);
speakers.insert(
"r".to_string(),
SpeakerConfig::Single(MeasurementSource::InMemory(make_test_curve(80.0))),
);
speakers.insert(
"sub".to_string(),
SpeakerConfig::Single(MeasurementSource::InMemory(make_test_curve(90.0))),
);
let mut sys_spk = HashMap::new();
sys_spk.insert("L".to_string(), "l".to_string());
sys_spk.insert("R".to_string(), "r".to_string());
sys_spk.insert("LFE".to_string(), "sub".to_string());
let mut sub_map = HashMap::new();
sub_map.insert("sub".to_string(), "L".to_string());
let mut crossovers = HashMap::new();
crossovers.insert(
"sub_xover".to_string(),
CrossoverConfig {
crossover_type: "LR24".to_string(),
frequency: Some(80.0),
frequencies: None,
frequency_range: None,
},
);
let config = RoomConfig {
version: "1.2.0".to_string(),
system: Some(SystemConfig {
model: SystemModel::Stereo,
speakers: sys_spk,
subwoofers: Some(SubwooferSystemConfig {
config: SubwooferStrategy::Single,
crossover: Some("sub_xover".to_string()),
mapping: sub_map,
}),
}),
speakers,
crossovers: Some(crossovers),
target_curve: None,
optimizer: OptimizerConfig {
max_iter: 100,
..OptimizerConfig::default()
},
recording_config: None,
cea2034_cache: None,
};
let result = autoeq::roomeq::optimize_room(&config, 48000.0, None, None)
.expect("Optimization failed");
assert!(result.channels.contains_key("L"));
assert!(result.channels.contains_key("R"));
assert!(result.channels.contains_key("LFE"));
let lfe_chain = &result.channels["LFE"];
let mut total_gain = 0.0;
for p in &lfe_chain.plugins {
if p.plugin_type == "gain" {
total_gain += p.parameters["gain_db"].as_f64().unwrap_or(0.0);
}
}
println!("LFE Total Gain: {}", total_gain);
assert!(total_gain < -8.0, "LFE should be significantly attenuated");
}
#[test]
fn test_generic_loop_processes_all_speakers_when_system_is_none() {
let mut speakers = HashMap::new();
speakers.insert(
"left".to_string(),
SpeakerConfig::Single(MeasurementSource::InMemory(make_test_curve(80.0))),
);
speakers.insert(
"right".to_string(),
SpeakerConfig::Single(MeasurementSource::InMemory(make_test_curve(82.0))),
);
let config = RoomConfig {
version: "1.2.0".to_string(),
system: None, speakers,
crossovers: None,
target_curve: None,
optimizer: OptimizerConfig {
max_iter: 100,
num_filters: 3,
..OptimizerConfig::default()
},
recording_config: None,
cea2034_cache: None,
};
let result = autoeq::roomeq::optimize_room(&config, 48000.0, None, None)
.expect("Optimization of 2-speaker system:None config failed");
assert_eq!(
result.channel_results.len(),
2,
"both speakers must appear in channel_results; got keys: {:?}",
result.channel_results.keys().collect::<Vec<_>>(),
);
assert!(
result.channel_results.contains_key("left"),
"'left' missing from channel_results: {:?}",
result.channel_results.keys().collect::<Vec<_>>(),
);
assert!(
result.channel_results.contains_key("right"),
"'right' missing from channel_results: {:?}",
result.channel_results.keys().collect::<Vec<_>>(),
);
}
#[test]
fn test_generic_loop_gpui_simple_wizard_style_two_speakers() {
use autoeq::roomeq::{
TargetResponseConfig, TargetShape,
};
fn make_curve_with_bump(base: f64, bump_db: f64) -> autoeq::Curve {
let n = 200;
let freq: Vec<f64> = (0..n)
.map(|i| 20.0 * (1000.0f64).powf(i as f64 / n as f64))
.collect();
let spl: Vec<f64> = freq
.iter()
.map(|f| {
let bump = if *f < 150.0 {
let log_dist = (f.log10() - 60.0_f64.log10()).abs();
bump_db * (-log_dist * 5.0).exp()
} else {
0.0
};
base + bump
})
.collect();
autoeq::Curve {
freq: ndarray::Array1::from_vec(freq),
spl: ndarray::Array1::from_vec(spl),
phase: None,
..Default::default()
}
}
let mut speakers = HashMap::new();
speakers.insert(
"left".to_string(),
SpeakerConfig::Single(MeasurementSource::InMemory(make_curve_with_bump(80.0, 5.0))),
);
speakers.insert(
"right".to_string(),
SpeakerConfig::Single(MeasurementSource::InMemory(make_curve_with_bump(82.0, 7.0))),
);
let optimizer = OptimizerConfig {
loss_type: "flat".to_string(),
algorithm: "autoeq:de".to_string(),
num_filters: 5,
min_q: 0.5,
max_q: 6.0,
min_db: -12.0,
max_db: 4.0,
min_freq: 20.0,
max_freq: 1600.0,
max_iter: 500,
population: 80,
peq_model: "pk".to_string(),
refine: true,
local_algo: "cobyla".to_string(),
psychoacoustic: true,
asymmetric_loss: true,
tolerance: 1e-5,
atolerance: 1e-5,
target_response: Some(TargetResponseConfig {
shape: TargetShape::FromMeasurement,
slope_db_per_octave: 0.0,
reference_freq: 1000.0,
curve_path: None,
preference: Default::default(),
broadband_precorrection: false,
}),
..OptimizerConfig::default()
};
let config = RoomConfig {
version: "1.2.0".to_string(),
system: None, speakers,
crossovers: None,
target_curve: None,
optimizer,
recording_config: None,
cea2034_cache: None,
};
let result = autoeq::roomeq::optimize_room(&config, 48000.0, None, None)
.expect("GPUI-style optimize_room should succeed for 2 speakers");
assert_eq!(
result.channel_results.len(),
2,
"GPUI Simple Wizard style: both speakers must appear; got keys: {:?}",
result.channel_results.keys().collect::<Vec<_>>(),
);
for name in ["left", "right"] {
let ch = result
.channel_results
.get(name)
.unwrap_or_else(|| panic!("missing channel_result for '{}'", name));
assert!(
!ch.biquads.is_empty(),
"channel '{}' produced no biquad filters",
name
);
}
}
#[test]
fn test_generic_loop_processes_three_speakers_when_system_is_none() {
let mut speakers = HashMap::new();
speakers.insert(
"left".to_string(),
SpeakerConfig::Single(MeasurementSource::InMemory(make_test_curve(80.0))),
);
speakers.insert(
"right".to_string(),
SpeakerConfig::Single(MeasurementSource::InMemory(make_test_curve(81.0))),
);
speakers.insert(
"center".to_string(),
SpeakerConfig::Single(MeasurementSource::InMemory(make_test_curve(82.0))),
);
let config = RoomConfig {
version: "1.2.0".to_string(),
system: None,
speakers,
crossovers: None,
target_curve: None,
optimizer: OptimizerConfig {
max_iter: 100,
num_filters: 3,
..OptimizerConfig::default()
},
recording_config: None,
cea2034_cache: None,
};
let result = autoeq::roomeq::optimize_room(&config, 48000.0, None, None)
.expect("Optimization of 3-speaker system:None config failed");
assert_eq!(
result.channel_results.len(),
3,
"all three speakers must appear in channel_results; got keys: {:?}",
result.channel_results.keys().collect::<Vec<_>>(),
);
}
}