Files
bass-foundry/plugins/weld/core/src/settings.rs
T
nvrl 9b3ddeaf56
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
feat(weld): phase alignment for layered bass, release 0.1.0
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.
2026-11-06 08:31:41 +01:00

81 lines
2.5 KiB
Rust

//! Host-independent controls. Gains in dB, frequencies in Hz, times in seconds.
use serde::{Deserialize, Serialize};
/// What the plugin outputs.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
pub enum Listen {
/// The aligned main layer only. The sidechain layer reaches the mix on its own track.
#[default]
Layer,
/// Aligned main layer plus the sidechain layer, to judge the sum in one place.
Sum,
/// Unprocessed main layer plus the sidechain layer, to compare with `Sum`.
SumDry,
}
/// How the alignment is found.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
pub enum Mode {
/// Keep measuring and refit when a clearly better alignment shows up.
#[default]
Follow,
/// Measure from the moment Learn starts, with equal weight; the result is applied
/// when Learn ends.
Learn,
/// Keep the current alignment. The display keeps measuring.
Hold,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct Settings {
pub mode: Mode,
/// Averaging time of the measurement in Follow mode.
pub response: f32,
/// Lowest frequency the fit listens to.
pub low: f32,
/// Highest frequency the fit listens to.
pub high: f32,
/// Largest number of allpass sections the fit may use.
pub sections: usize,
/// Frames quieter than this (dBFS RMS) on either layer are not measured.
pub gate: f32,
pub listen: Listen,
pub output: f32,
pub bypass: bool,
}
impl Default for Settings {
fn default() -> Self {
Self {
mode: Mode::Follow,
response: 1.5,
low: 60.,
high: 6000.,
sections: 4,
gate: -50.,
listen: Listen::Layer,
output: 0.,
bypass: false,
}
}
}
impl Settings {
pub fn validate(&self) -> Result<(), &'static str> {
let values = [self.response, self.low, self.high, self.gate, self.output];
if values.iter().any(|v| !v.is_finite()) {
return Err("non-finite setting");
}
if (0.3..=5.).contains(&self.response)
&& (30.0..=400.).contains(&self.low)
&& (1000.0..=12000.).contains(&self.high)
&& (1..=crate::chain::MAX_SECTIONS).contains(&self.sections)
&& (-70.0..=-30.).contains(&self.gate)
&& (-12.0..=12.).contains(&self.output)
{
Ok(())
} else {
Err("setting out of range")
}
}
}