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.
98 lines
2.9 KiB
Rust
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()
|
|
);
|
|
}
|