Files
bass-foundry/plugins/weld/core/tests/dsp.rs
T
nvrl e3286f9094 feat(weld): add sidechain test build
Weld will phase-align a bass layer to another layer it hears through the
sidechain. This first build only checks that FL Studio delivers the
sidechain to a CLAP aux port: it passes the main layer through unchanged,
meters the sidechain and offers Layer/Sum/Sum dry listening. No latency.
2026-11-06 07:53:20 +01:00

98 lines
2.9 KiB
Rust

use weld_core::{Engine, Listen, Settings};
fn signal(n: usize, hz: f32) -> f32 {
(std::f32::consts::TAU * hz * n as f32 / 48000.).sin() * 0.5
}
#[test]
fn layer_output_is_the_main_input_with_zero_latency() {
let mut engine = Engine::new(48000., 2).unwrap();
engine.set_settings(Settings::default()).unwrap();
assert_eq!(engine.latency_samples(), 0);
for n in 0..4800 {
let x = [signal(n, 110.), -signal(n, 220.)];
let out = engine.process_sample(x, [signal(n, 330.), 0.25]);
assert_eq!(out, x);
}
}
#[test]
fn sum_modes_add_the_sidechain() {
for listen in [Listen::Sum, Listen::SumDry] {
let mut engine = Engine::new(48000., 2).unwrap();
engine
.set_settings(Settings {
listen,
..Settings::default()
})
.unwrap();
for n in 0..480 {
let x = [signal(n, 110.), signal(n, 120.)];
let s = [signal(n, 330.), -signal(n, 90.)];
let out = engine.process_sample(x, s);
for c in 0..2 {
assert!((out[c] - (x[c] + s[c])).abs() < 1e-6);
}
}
}
}
#[test]
fn mono_bypass_and_output_gain() {
let mut engine = Engine::new(44100., 1).unwrap();
engine
.set_settings(Settings {
output: -6.,
..Settings::default()
})
.unwrap();
let out = engine.process_sample([0.5, 0.], [0., 0.]);
assert!((out[0] - 0.5 * 10f32.powf(-6. / 20.)).abs() < 1e-6);
assert_eq!(out[1], 0.);
engine
.set_settings(Settings {
bypass: true,
..Settings::default()
})
.unwrap();
assert_eq!(engine.process_sample([0.3, 0.], [0.9, 0.]), [0.3, 0.]);
}
#[test]
fn analyzer_reports_sidechain_presence_and_level() {
let mut engine = Engine::new(48000., 2).unwrap();
engine.set_settings(Settings::default()).unwrap();
for n in 0..4800 {
engine.process_sample([signal(n, 100.); 2], [0.; 2]);
}
let frame = engine.take_analyzer();
assert!(!frame.side_seen);
assert!(frame.side_db < -100.);
assert!((frame.main_db - (-9.03)).abs() < 0.1, "{}", frame.main_db);
for n in 0..4800 {
engine.process_sample([0.; 2], [signal(n, 100.); 2]);
}
let frame = engine.take_analyzer();
assert!(frame.side_seen);
assert!((frame.side_db - (-9.03)).abs() < 0.1);
assert!((frame.side_peak_db - (-6.02)).abs() < 0.1);
}
#[test]
fn invalid_settings_and_rates_are_rejected() {
assert!(Engine::new(0., 2).is_err());
assert!(Engine::new(48000., 3).is_err());
let mut engine = Engine::new(48000., 2).unwrap();
assert!(engine
.set_settings(Settings {
output: f32::NAN,
..Settings::default()
})
.is_err());
let json = serde_json::to_string(&Settings::default()).unwrap();
assert_eq!(
serde_json::from_str::<Settings>(&json).unwrap(),
Settings::default()
);
}