use super::Signal;
use crate::dsl::{Normalize, Stereo};
use crate::dsp::{
db_to_lin, loudness_lufs, loudness_lufs_gated, peak_limit, true_peak, true_peak_oversampled,
};
use std::f32::consts::FRAC_PI_2;
pub fn make_loop_buffer(
samples: &[f32],
sr: u32,
start_secs: f32,
end_secs: Option<f32>,
crossfade_secs: f32,
) -> Signal {
let len = samples.len();
let s = ((start_secs * sr as f32) as usize).min(len);
let e = end_secs
.map(|x| (x * sr as f32) as usize)
.unwrap_or(len)
.min(len);
if e <= s {
return samples.to_vec();
}
let region = &samples[s..e];
let l = region.len();
let x = ((crossfade_secs * sr as f32) as usize).min(l / 2);
if x == 0 {
return region.to_vec();
}
let out_len = l - x;
let mut out = region[..out_len].to_vec();
for (i, o) in out.iter_mut().take(x).enumerate() {
let t = (i as f32 + 0.5) / x as f32;
let fade_in = (FRAC_PI_2 * t).sin();
let fade_out = (FRAC_PI_2 * t).cos();
*o = region[i] * fade_in + region[out_len + i] * fade_out;
}
out
}
pub fn loop_seam_db(samples: &[f32]) -> f32 {
if samples.len() < 2 {
return -120.0;
}
let jump = (samples[0] - samples[samples.len() - 1]).abs();
20.0 * jump.max(1e-9).log10()
}
pub(super) fn normalize_output(samples: &mut [f32], nz: &Normalize) {
let ceil = db_to_lin(nz.ceiling_dbtp);
if let Some(target) = nz.target_lufs {
for _ in 0..2 {
let cur = loudness_lufs(samples);
if cur <= -120.0 {
break;
}
let g = db_to_lin(target - cur);
for x in samples.iter_mut() {
*x *= g;
}
soft_limit(samples, ceil);
}
}
true_peak_limit(samples, nz.ceiling_dbtp);
peak_limit(&mut [samples]);
}
pub(super) fn normalize_output_v4(channels: &mut [&mut [f32]], nz: &Normalize, sr: u32) {
let ceil = db_to_lin(nz.ceiling_dbtp);
if let Some(target) = nz.target_lufs {
for _ in 0..2 {
let cur = {
let views: Vec<&[f32]> = channels.iter().map(|c| &**c).collect();
loudness_lufs_gated(&views, sr)
};
if cur <= -120.0 {
break;
}
let g = db_to_lin(target - cur);
for c in channels.iter_mut() {
for x in c.iter_mut() {
*x *= g;
}
soft_limit(c, ceil);
}
}
}
let tp = channels
.iter()
.map(|c| true_peak_oversampled(c))
.fold(0.0f32, f32::max);
if tp > ceil && tp > 0.0 {
let g = ceil / tp;
for c in channels.iter_mut() {
for x in c.iter_mut() {
*x *= g;
}
}
}
peak_limit(channels);
}
fn soft_limit(samples: &mut [f32], ceil: f32) {
const KNEE: f32 = 0.7;
let ceil = ceil.max(1e-9);
for x in samples.iter_mut() {
let v = *x / ceil;
let a = v.abs();
if a > KNEE {
let compressed = KNEE + (1.0 - KNEE) * ((a - KNEE) / (1.0 - KNEE)).tanh();
*x = v.signum() * compressed * ceil;
}
}
}
fn true_peak_limit(samples: &mut [f32], ceiling_dbtp: f32) {
let ceil = db_to_lin(ceiling_dbtp);
let tp = true_peak(samples);
if tp > ceil && tp > 0.0 {
let g = ceil / tp;
for x in samples.iter_mut() {
*x *= g;
}
}
}
pub fn stereoize(mono: &[f32], stereo: Stereo, sr: u32) -> (Vec<f32>, Vec<f32>) {
let (mut l, mut r) = match stereo {
Stereo::Mono => (mono.to_vec(), mono.to_vec()),
Stereo::Haas { ms, pan } => {
let d = ((ms / 1000.0) * sr as f32) as usize;
let delayed: Vec<f32> = (0..mono.len())
.map(|i| if i >= d { mono[i - d] } else { 0.0 })
.collect();
if pan >= 0.0 {
(delayed, mono.to_vec())
} else {
(mono.to_vec(), delayed)
}
}
Stereo::Wide { amount } => {
let dec = allpass_decorrelate(mono, sr);
let a = amount.clamp(0.0, 1.0);
let mut l = Vec::with_capacity(mono.len());
let mut r = Vec::with_capacity(mono.len());
for i in 0..mono.len() {
let mid = mono[i];
let side = a * (mono[i] - dec[i]) * 0.5;
l.push(mid + side);
r.push(mid - side);
}
(l, r)
}
};
peak_limit(&mut [&mut l, &mut r]);
(l, r)
}
fn allpass_decorrelate(input: &[f32], sr: u32) -> Vec<f32> {
let scale = sr as f32 / 44_100.0;
let mut sig = input.to_vec();
for &tune in &[225usize, 556, 441] {
let len = ((tune as f32 * scale) as usize).max(1);
let mut buf = vec![0.0f32; len];
let mut idx = 0usize;
let g = 0.7;
for s in sig.iter_mut() {
let buffered = buf[idx];
let y = -*s * g + buffered;
buf[idx] = *s + buffered * g;
idx = (idx + 1) % len;
*s = y;
}
}
sig
}