feat(weld): phase alignment for layered bass, release 0.1.0
Check and Windows preview / check (anvil) (push) Successful in 2m0s
Check and Windows preview / check (crucible) (push) Successful in 1m29s
Check and Windows preview / check (lathe) (push) Successful in 1m27s
Check and Windows preview / check (patina) (push) Successful in 1m53s
Check and Windows preview / check (rivet) (push) Successful in 1m29s
Check and Windows preview / check (temper) (push) Successful in 1m29s
Check and Windows preview / check (weld) (push) Successful in 2m38s
Check and Windows preview / previews (push) Successful in 1m28s
Independent plugin release / plan (push) Successful in 8s
Check and Windows preview / windows (push) Successful in 3m14s
Independent plugin release / build (push) Successful in 3m50s
Independent plugin release / publish (push) Successful in 9s
Check and Windows preview / check (anvil) (push) Successful in 2m0s
Check and Windows preview / check (crucible) (push) Successful in 1m29s
Check and Windows preview / check (lathe) (push) Successful in 1m27s
Check and Windows preview / check (patina) (push) Successful in 1m53s
Check and Windows preview / check (rivet) (push) Successful in 1m29s
Check and Windows preview / check (temper) (push) Successful in 1m29s
Check and Windows preview / check (weld) (push) Successful in 2m38s
Check and Windows preview / previews (push) Successful in 1m28s
Independent plugin release / plan (push) Successful in 8s
Check and Windows preview / windows (push) Successful in 3m14s
Independent plugin release / build (push) Successful in 3m50s
Independent plugin release / publish (push) Successful in 9s
Weld hears the other bass layer through the sidechain and fits polarity plus up to four RBJ allpass sections to the measured cross-spectrum, so the layers sum instead of cancelling. No latency; the layer on its own keeps its sound. - Worker thread: Welch cross-spectrum and coherence, Nelder-Mead fit with 1/f weighting, a per-band loss penalty and model selection by share of the achievable gain; GCC-PHAT lag detection flags a late layer. - Audio thread: allocation-free chain with a 40 ms warm-up and 40 ms crossfade per new fit; mono blocks go to the worker through a lock-free queue. - Follow / Learn / Hold; the fit is persisted and restored without a crossfade. - Editor: per-band loss and recovery against a perfect alignment, phase before and after, recovered dB, time offset and status. - Tests for flat magnitude, crossfade, fit targets, Follow hysteresis, Learn, silence, realtime allocation and the persisted fit in the host.
This commit is contained in:
1 parent
0a0917ac56
commit
9b3ddeaf56
26 files changed
+2898
-144
No files matched your search
@@ -111,6 +111,20 @@ fn sidechain_port_state_latency_and_audio_lifecycle() {
|
||||
envelope
|
||||
.params
|
||||
.insert("listen".into(), ParamValue::String("sum".into()));
|
||||
envelope
|
||||
.params
|
||||
.insert("mode".into(), ParamValue::String("hold".into()));
|
||||
envelope
|
||||
.params
|
||||
.insert("sections".into(), ParamValue::I32(2));
|
||||
// A saved alignment that flips polarity.
|
||||
let flipped = weld_core::Fit {
|
||||
flip: true,
|
||||
..weld_core::Fit::NONE
|
||||
};
|
||||
envelope
|
||||
.fields
|
||||
.insert("weld-fit".into(), serde_json::to_string(&flipped).unwrap());
|
||||
let encoded = serde_json::to_vec(&envelope).unwrap();
|
||||
let mut framed = (encoded.len() as u64).to_le_bytes().to_vec();
|
||||
framed.extend_from_slice(&encoded);
|
||||
@@ -126,13 +140,16 @@ fn sidechain_port_state_latency_and_audio_lifecycle() {
|
||||
serde_json::to_value(&restored.params).unwrap(),
|
||||
serde_json::to_value(&envelope.params).unwrap()
|
||||
);
|
||||
// Listen = Sum: a sidechain equal to the main input doubles the output (+6 dB).
|
||||
// A silent sidechain leaves the main input alone. CLAP hosts pass every port, so
|
||||
// the test always passes both (nih_plug reads one port past a short count).
|
||||
let saved: weld_core::Fit = serde_json::from_str(&restored.fields["weld-fit"]).unwrap();
|
||||
assert_eq!(saved, flipped);
|
||||
// Listen = Sum with the restored flip: a sidechain equal to the main input cancels
|
||||
// it from the first sample on. A silent sidechain leaves the level alone. CLAP hosts
|
||||
// pass every port, so the test always passes both (nih_plug reads one port past a
|
||||
// short count).
|
||||
for (rate, with_side, expected_db) in [
|
||||
(44100., true, 6.02),
|
||||
(48000., false, 0.),
|
||||
(96000., true, 6.02),
|
||||
(44100., true, None),
|
||||
(48000., false, Some(0.)),
|
||||
(96000., true, None),
|
||||
] {
|
||||
assert!(p.activate.unwrap()(plugin, rate, 1, 512));
|
||||
assert_eq!((*latency).get.unwrap()(plugin), 0);
|
||||
@@ -213,10 +230,10 @@ fn sidechain_port_state_latency_and_audio_lifecycle() {
|
||||
position += frames;
|
||||
}
|
||||
let ratio_db = 10. * (energy_out / energy_in).log10();
|
||||
assert!(
|
||||
(ratio_db - expected_db).abs() < 0.2,
|
||||
"{rate} {with_side} {ratio_db}"
|
||||
);
|
||||
match expected_db {
|
||||
Some(db) => assert!((ratio_db - db).abs() < 0.2, "{rate} {with_side} {ratio_db}"),
|
||||
None => assert!(ratio_db < -60., "{rate} {with_side} {ratio_db}"),
|
||||
}
|
||||
p.stop_processing.unwrap()(plugin);
|
||||
p.deactivate.unwrap()(plugin);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user