#![allow(dead_code)]
use ferraus_core::testing::noise;
use ferraus_core::AudioBuffer;
use std::f64::consts::PI;
pub const SR: u32 = 48_000;
pub struct Clean {
pub buf: AudioBuffer,
pub voiced: Vec<bool>,
}
fn formant_gain(f: f64, formants: &[(f64, f64)]) -> f64 {
let mut g = 0.02;
for (fc, bw) in formants {
let x = (f - fc) / (bw / 2.0);
g += 1.0 / (1.0 + x * x).sqrt();
}
g
}
pub fn speech_like(lead_s: f64, secs: f64, amp: f32) -> Clean {
let sr = SR as f64;
let n_lead = (lead_s * sr) as usize;
let n = (secs * sr) as usize;
let vowels: [[(f64, f64); 3]; 5] = [
[(730.0, 90.0), (1090.0, 110.0), (2440.0, 170.0)], [(270.0, 60.0), (2290.0, 100.0), (3010.0, 120.0)], [(300.0, 60.0), (870.0, 90.0), (2240.0, 120.0)], [(530.0, 70.0), (1840.0, 100.0), (2480.0, 140.0)], [(570.0, 80.0), (840.0, 90.0), (2410.0, 140.0)], ];
let pattern: [(f64, usize); 3] = [(0.16, 2), (0.13, 3), (0.19, 2)];
let mut sched = Vec::new();
let mut t = 0.05;
let mut done = false;
while !done {
for (dur, count) in pattern {
for _ in 0..count {
if t + dur > secs {
done = true;
break;
}
sched.push((t, dur));
t += dur + 0.04;
}
if done {
break;
}
t += 0.18;
}
}
let mut out = vec![0f32; n_lead + n];
let mut voiced = vec![false; n_lead + n];
let mut phase = 0.0f64;
let max_h = 60;
let mut si = 0;
for i in 0..n {
let t = i as f64 / sr;
while si < sched.len() && t >= sched[si].0 + sched[si].1 {
si += 1;
}
let f0_base = 125.0 + 25.0 * (2.0 * PI * 0.6 * t).sin();
if si >= sched.len() || t < sched[si].0 {
phase += 2.0 * PI * f0_base / sr;
continue;
}
let (s0, dur) = sched[si];
let ts = (t - s0) / dur;
let f0 = f0_base + 15.0 * (ts - 0.5);
phase += 2.0 * PI * f0 / sr;
let env = (PI * ts).sin().powf(0.6);
let v = &vowels[si % vowels.len()];
let mut s = 0.0;
for h in 1..=max_h {
let f = h as f64 * f0;
if f > 5500.0 {
break;
}
s += formant_gain(f, v) / h as f64 * (h as f64 * phase).sin();
}
out[n_lead + i] = (s * env) as f32;
voiced[n_lead + i] = env > 0.5;
}
normalize(&mut out, amp);
Clean { buf: AudioBuffer::from_mono(SR, out), voiced }
}
pub fn music_like(lead_s: f64, secs: f64, amp: f32) -> Clean {
let sr = SR as f64;
let n_lead = (lead_s * sr) as usize;
let n = (secs * sr) as usize;
let chords: [[f64; 4]; 4] = [
[261.63, 329.63, 392.00, 523.25],
[220.00, 261.63, 329.63, 440.00],
[174.61, 220.00, 261.63, 349.23],
[196.00, 246.94, 293.66, 392.00],
];
let dur = 0.5;
let mut out = vec![0f32; n_lead + n];
let mut voiced = vec![false; n_lead + n];
for i in 0..n {
let t = i as f64 / sr;
let ci = (t / dur).floor() as usize;
let tc = t - ci as f64 * dur;
let env = (tc / 0.02).min(1.0) * (-tc * 3.0).exp() * ((dur - tc) / 0.12).clamp(0.0, 1.0);
let mut s = 0.0;
for f in chords[ci % chords.len()] {
for h in 1..=8 {
s += (2.0 * PI * f * h as f64 * t).sin() / h as f64;
}
}
out[n_lead + i] = (s * env) as f32;
voiced[n_lead + i] = env > 0.5;
}
normalize(&mut out, amp);
Clean { buf: AudioBuffer::from_mono(SR, out), voiced }
}
fn normalize(x: &mut [f32], peak: f32) {
let m = x.iter().fold(0f32, |a, v| a.max(v.abs()));
if m > 0.0 {
for v in x.iter_mut() {
*v *= peak / m;
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NoiseKind {
Hiss,
Fan,
Hum,
Street,
}
impl NoiseKind {
pub const ALL: [NoiseKind; 4] = [NoiseKind::Hiss, NoiseKind::Fan, NoiseKind::Hum, NoiseKind::Street];
}
pub fn make_noise(kind: NoiseKind, secs: f64, seed: u64) -> Vec<f32> {
let w = noise(SR, secs, 1.0, seed).data.remove(0);
let n = w.len();
let mut x = match kind {
NoiseKind::Hiss => w,
NoiseKind::Fan => {
let (mut b0, mut b1, mut b2, mut b3, mut b4, mut b5, mut b6) = (0f32, 0f32, 0f32, 0f32, 0f32, 0f32, 0f32);
let mut out = Vec::with_capacity(n);
for v in w.iter() {
b0 = 0.99886 * b0 + v * 0.0555179;
b1 = 0.99332 * b1 + v * 0.0750759;
b2 = 0.96900 * b2 + v * 0.153_852;
b3 = 0.86650 * b3 + v * 0.3104856;
b4 = 0.55000 * b4 + v * 0.5329522;
b5 = -0.7616 * b5 - v * 0.0168980;
let pink = b0 + b1 + b2 + b3 + b4 + b5 + b6 + v * 0.5362;
b6 = v * 0.115926;
out.push(pink);
}
let (pr, wr) = (rms(&out) as f32, rms(&w) as f32);
for (i, (o, v)) in out.iter_mut().zip(&w).enumerate() {
let t = i as f64 / SR as f64;
let tone = 0.1 * (2.0 * PI * 180.0 * t).sin() as f32;
*o = *o / pr + 0.7 * v / wr + tone;
}
out
}
NoiseKind::Hum => (0..n)
.map(|i| {
let t = i as f64 / SR as f64;
let mut s = 0.0;
for (h, a) in [(1.0, 1.0), (2.0, 0.5), (3.0, 0.35), (4.0, 0.2), (5.0, 0.12), (6.0, 0.08)] {
s += a * (2.0 * PI * 60.0 * h * t + h).sin();
}
(s + 0.02 * w[i] as f64) as f32
})
.collect(),
NoiseKind::Street => {
let mut y = 0f32;
let mut out = Vec::with_capacity(n);
for v in &w {
y = 0.995 * y + 0.05 * v;
out.push(y);
}
out
}
};
let rms = (x.iter().map(|v| (*v as f64).powi(2)).sum::<f64>() / n as f64).sqrt() as f32;
for v in x.iter_mut() {
*v /= rms.max(1e-12);
}
x
}
pub fn rms(x: &[f32]) -> f64 {
(x.iter().map(|v| (*v as f64).powi(2)).sum::<f64>() / x.len().max(1) as f64).sqrt()
}
pub fn mix(clean: &AudioBuffer, noise: &[f32], snr_db: f64, lead: usize) -> (AudioBuffer, Vec<f32>) {
let c = &clean.data[0];
let sig_rms = rms(&c[lead..]);
let g = (sig_rms / 10f64.powf(snr_db / 20.0)) as f32;
let scaled: Vec<f32> = noise[..c.len()].iter().map(|v| v * g).collect();
let mixed: Vec<f32> = c.iter().zip(&scaled).map(|(a, b)| a + b).collect();
(AudioBuffer::from_mono(clean.sample_rate, mixed), scaled)
}
pub fn retained_db(clean: &[f32], out: &[f32], mask: &[bool]) -> f64 {
let (mut dot, mut cc) = (0f64, 0f64);
for i in 0..clean.len().min(out.len()) {
if mask[i] {
dot += clean[i] as f64 * out[i] as f64;
cc += (clean[i] as f64).powi(2);
}
}
20.0 * (dot / cc.max(1e-20)).log10()
}