#[derive(Clone, Debug)]
pub struct SoftClip {
mem: Vec<f32>,
}
impl SoftClip {
pub fn new(channels: usize) -> Self {
Self {
mem: vec![0.0; channels],
}
}
pub fn channels(&self) -> usize {
self.mem.len()
}
pub fn reset(&mut self) {
self.mem.fill(0.0);
}
pub fn apply(&mut self, pcm: &mut [f32]) {
let channels = self.mem.len();
if channels == 0 {
return;
}
let n = pcm.len() / channels;
if n == 0 {
return;
}
for s in &mut pcm[..n * channels] {
*s = if *s > -2.0 { *s } else { -2.0 };
*s = if *s < 2.0 { *s } else { 2.0 };
}
for c in 0..channels {
self.mem[c] = clip_channel(pcm, n, channels, c, self.mem[c]);
}
}
}
fn clip_channel(pcm: &mut [f32], n: usize, channels: usize, c: usize, carried: f32) -> f32 {
let at = |i: usize| i * channels + c;
let mut a = carried;
for i in 0..n {
let x = pcm[at(i)];
if x * a >= 0.0 {
break;
}
pcm[at(i)] = x + a * x * x;
}
let mut curr = 0usize;
let x0 = pcm[at(0)];
loop {
let mut peak = curr;
while peak < n {
let x = pcm[at(peak)];
#[allow(clippy::manual_range_contains)]
if x > 1.0 || x < -1.0 {
break;
}
peak += 1;
}
if peak == n {
a = 0.0;
break;
}
let x_peak = pcm[at(peak)];
let mut start = peak;
let mut end = peak;
let mut maxval = x_peak.abs();
let mut peak_pos = peak;
while start > 0 && x_peak * pcm[at(start - 1)] >= 0.0 {
start -= 1;
}
while end < n && x_peak * pcm[at(end)] >= 0.0 {
let mag = pcm[at(end)].abs();
if mag > maxval {
maxval = mag;
peak_pos = end;
}
end += 1;
}
let clipped_at_start = start == 0 && x_peak * pcm[at(0)] >= 0.0;
a = (maxval - 1.0) / (maxval * maxval);
a += a * 2.4e-7;
if x_peak > 0.0 {
a = -a;
}
for i in start..end {
let x = pcm[at(i)];
pcm[at(i)] = x + a * x * x;
}
if clipped_at_start && peak_pos >= 2 {
let mut offset = x0 - pcm[at(0)];
let delta = offset / peak_pos as f32;
for i in curr..peak_pos {
offset -= delta;
pcm[at(i)] += offset;
pcm[at(i)] = pcm[at(i)].clamp(-1.0, 1.0);
}
}
curr = end;
if curr == n {
break;
}
}
a
}
#[inline]
pub(crate) fn float_to_i16(x: f32) -> i16 {
let x = x * 32768.0;
let x = if x > -32768.0 { x } else { -32768.0 };
let x = if x < 32767.0 { x } else { 32767.0 };
x.round_ties_even() as i16
}
#[inline]
pub(crate) fn i16_to_float(x: i16) -> f32 {
x as f32 * (1.0 / 32768.0)
}