//! Shared helpers for the SILK/hybrid round-trip tests: synthetic signals, encoding, decoding //! with FilmCraft's spec-derived decoder, delay-searched SNR and an Ogg Opus writer for ffmpeg. #![allow(dead_code)] use effectcraft_opusenc::OpusEncoder; pub const SR: f64 = 48_000.0; pub const FRAME: usize = OpusEncoder::FRAME_SIZE; /// Two-pole resonator state. struct Reson { y1: f64, y2: f64, } impl Reson { fn run(&mut self, x: f64, f: f64, bw: f64) -> f64 { let r = (-std::f64::consts::PI * bw / SR).exp(); let a1 = 2.0 * r * (2.0 * std::f64::consts::PI * f / SR).cos(); let a2 = -r * r; let g = 1.0 - r; let y = g * x + a1 * self.y1 + a2 * self.y2; self.y2 = self.y1; self.y1 = y; y } } /// Speech-like test signal: a glottal pulse train (100–200 Hz, gliding pitch) through three /// formant resonators whose frequencies change per syllable, with syllable envelopes, short /// pauses and a little aspiration noise. pub fn speech(len: usize, seed: u32) -> Vec { let vowels: [[f64; 3]; 5] = [[730.0, 1090.0, 2440.0], [270.0, 2290.0, 3010.0], [530.0, 1840.0, 2480.0], [570.0, 840.0, 2410.0], [300.0, 870.0, 2240.0]]; let syl = (0.24 * SR) as usize; let mut out = Vec::with_capacity(len); let mut res = [Reson { y1: 0.0, y2: 0.0 }, Reson { y1: 0.0, y2: 0.0 }, Reson { y1: 0.0, y2: 0.0 }]; let mut phase = 0.0f64; let mut s = seed; let mut prev_glottal = 0.0; for n in 0..len { let t = n as f64 / SR; let k = n / syl; let pos = (n % syl) as f64 / syl as f64; // Every fourth syllable is a pause. let pause = k % 4 == 3; let env = if pause { 0.0 } else { (std::f64::consts::PI * pos).sin().powf(0.7) }; let f0 = 100.0 + 50.0 * (1.0 + (2.0 * std::f64::consts::PI * 0.9 * t).sin()) + 15.0 * ((k * 7) % 5) as f64 - 30.0 * pos; phase += f0 / SR; if phase >= 1.0 { phase -= 1.0; } // Rosenberg-like glottal flow; its derivative excites the vocal tract. let g = if phase < 0.4 { 0.5 * (1.0 - (std::f64::consts::PI * phase / 0.4).cos()) } else if phase < 0.56 { (std::f64::consts::FRAC_PI_2 * (phase - 0.4) / 0.16).cos() } else { 0.0 }; let dg = (g - prev_glottal) * 40.0; prev_glottal = g; s = s.wrapping_mul(1_664_525).wrapping_add(1_013_904_223); let noise = ((s >> 8) as f64 / (1u32 << 24) as f64 - 0.5) * 0.02; let v = vowels[(k * 3 + 1) % 5]; let w = vowels[(k * 3 + 4) % 5]; let mix = pos; let mut x = dg + noise; let mut y = 0.0; for (i, r) in res.iter_mut().enumerate() { let f = v[i] * (1.0 - mix) + w[i] * mix; y += r.run(x, f, 60.0 + 40.0 * i as f64) * [1.0, 0.6, 0.3][i]; } x = y * env * 1.2; out.push(x as f32); } let peak = out.iter().fold(0f32, |m, v| m.max(v.abs())); let g = 0.5 / peak.max(1e-9); out.iter().map(|v| v * g).collect() } /// Music-like test signal: three-note chords of harmonic tones (six partials each), changing /// every half second with soft attacks and releases. pub fn music(len: usize) -> Vec { let chords: [[f64; 3]; 4] = [[261.63, 329.63, 392.0], [220.0, 277.18, 329.63], [174.61, 220.0, 261.63], [196.0, 246.94, 293.66]]; let seg = (0.5 * SR) as usize; (0..len) .map(|n| { let t = n as f64 / SR; let k = n / seg; let pos = (n % seg) as f64 / seg as f64; let env = (pos * 20.0).min(1.0) * ((1.0 - pos) * 10.0).min(1.0); let mut x = 0.0; for (j, &f) in chords[k % 4].iter().enumerate() { for h in 1..=6 { x += (2.0 * std::f64::consts::PI * f * h as f64 * t + j as f64).sin() / h as f64; } } (0.12 * env * x) as f32 }) .collect() } pub fn interleave(planar: &[Vec]) -> Vec { let c = planar.len(); let len = planar[0].len(); let mut pcm = vec![0f32; len * c]; for (ch, p) in planar.iter().enumerate() { for (i, &v) in p.iter().enumerate() { pcm[i * c + ch] = v; } } pcm } /// Encodes planar input (padding the tail so the encoder delay is flushed). pub fn encode(mut enc: OpusEncoder, planar: &[Vec]) -> (OpusEncoder, Vec>) { let c = planar.len(); let len = planar[0].len(); let total = len + enc.pre_skip() as usize + 16; let frames = total.div_ceil(FRAME); let mut pcm = interleave(planar); pcm.resize(frames * FRAME * c, 0.0); let packets = pcm.chunks(FRAME * c).map(|f| enc.encode_float(f)).collect(); (enc, packets) } /// Decodes with filmcraft-opus, drops the pre-skip and returns planar channels (full length). pub fn decode(head: &[u8], packets: &[Vec]) -> Vec> { let mut dec = filmcraft_opus::Decoder::new(head).expect("OpusHead"); dec.set_trim_pre_skip(true); let mut out: Vec> = vec![Vec::new(); dec.channels()]; for p in packets { let planes = dec.decode(Some(p)).expect("decode"); for (o, p) in out.iter_mut().zip(planes) { o.extend_from_slice(&p); } } out } /// Waveform SNR (dB) of `dec[i + shift]` against `orig[i]`, excluding the first and last 20 ms. pub fn snr_at(orig: &[f32], dec: &[f32], shift: isize) -> f64 { let (mut s, mut e) = (0f64, 0f64); for i in FRAME..orig.len() - FRAME { let j = i as isize + shift; let d = if j >= 0 && (j as usize) < dec.len() { dec[j as usize] as f64 } else { 0.0 }; s += (orig[i] as f64).powi(2); e += (orig[i] as f64 - d).powi(2); } 10.0 * (s / e.max(1e-30)).log10() } /// Best SNR over residual delays of ±`range` samples: (SNR dB, delay). pub fn snr_search(orig: &[f32], dec: &[f32], range: isize) -> (f64, isize) { (-range..=range).map(|d| (snr_at(orig, dec, d), d)).fold((f64::MIN, 0), |a, b| if b.0 > a.0 { b } else { a }) } /// Segmental SNR (dB): mean over 20 ms segments holding signal (each clamped to [-10, 40]). pub fn seg_snr(orig: &[f32], dec: &[f32], shift: isize) -> f64 { let mut acc = 0f64; let mut n = 0; let mut i = FRAME; while i + FRAME <= orig.len() - FRAME { let (mut s, mut e) = (0f64, 0f64); for k in i..i + FRAME { let j = k as isize + shift; let d = if j >= 0 && (j as usize) < dec.len() { dec[j as usize] as f64 } else { 0.0 }; s += (orig[k] as f64).powi(2); e += (orig[k] as f64 - d).powi(2); } if s / FRAME as f64 > 1e-5 { acc += (10.0 * (s / e.max(1e-30)).log10()).clamp(-10.0, 40.0); n += 1; } i += FRAME; } acc / n.max(1) as f64 } /// Average bitrate (bits per second) of a packet sequence of 20 ms packets. pub fn bitrate(packets: &[Vec]) -> f64 { packets.iter().map(|p| p.len() * 8).sum::() as f64 / (packets.len() as f64 * 0.02) } /// Low-pass filtered copy (FFT brick wall) used to compare band-limited modes fairly. pub fn lowpass(x: &[f32], fc: f64) -> Vec { let n = x.len().next_power_of_two(); let mut re: Vec = x.iter().map(|&v| v as f64).collect(); re.resize(n, 0.0); let mut im = vec![0f64; n]; fft(&mut re, &mut im, false); for k in 0..n { let f = (k.min(n - k)) as f64 * SR / n as f64; if f > fc { re[k] = 0.0; im[k] = 0.0; } } fft(&mut re, &mut im, true); re[..x.len()].iter().map(|&v| (v / n as f64) as f32).collect() } pub fn fft(re: &mut [f64], im: &mut [f64], inverse: bool) { let n = re.len(); let mut j = 0; for i in 1..n { let mut bit = n >> 1; while j & bit != 0 { j ^= bit; bit >>= 1; } j |= bit; if i < j { re.swap(i, j); im.swap(i, j); } } let mut len = 2; while len <= n { let ang = if inverse { 2.0 } else { -2.0 } * std::f64::consts::PI / len as f64; for s in (0..n).step_by(len) { for k in 0..len / 2 { let (wr, wi) = ((ang * k as f64).cos(), (ang * k as f64).sin()); let (a, b) = (s + k, s + k + len / 2); let tr = re[b] * wr - im[b] * wi; let ti = re[b] * wi + im[b] * wr; re[b] = re[a] - tr; im[b] = im[a] - ti; re[a] += tr; im[a] += ti; } } len <<= 1; } } // ---- Ogg Opus (RFC 3533 + RFC 7845) for the ffmpeg oracle ---- fn ogg_crc(data: &[u8]) -> u32 { let mut crc = 0u32; for &b in data { crc ^= (b as u32) << 24; for _ in 0..8 { crc = if crc & 0x8000_0000 != 0 { (crc << 1) ^ 0x04C1_1DB7 } else { crc << 1 }; } } crc } fn ogg_page(out: &mut Vec, packet: &[u8], header_type: u8, granule: u64, seq: u32) { let mut lacing = vec![255u8; packet.len() / 255]; lacing.push((packet.len() % 255) as u8); let start = out.len(); out.extend_from_slice(b"OggS"); out.push(0); out.push(header_type); out.extend_from_slice(&granule.to_le_bytes()); out.extend_from_slice(&0x4543_4F50u32.to_le_bytes()); out.extend_from_slice(&seq.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.push(lacing.len() as u8); out.extend_from_slice(&lacing); out.extend_from_slice(packet); let crc = ogg_crc(&out[start..]); out[start + 22..start + 26].copy_from_slice(&crc.to_le_bytes()); } /// An Ogg Opus file holding `packets` for `len` samples of audio. pub fn ogg_opus(enc: &OpusEncoder, packets: &[Vec], len: usize) -> Vec { let mut out = Vec::new(); ogg_page(&mut out, &enc.opus_head(), 0x02, 0, 0); let vendor = b"effectcraft-opusenc"; let mut tags = b"OpusTags".to_vec(); tags.extend_from_slice(&(vendor.len() as u32).to_le_bytes()); tags.extend_from_slice(vendor); tags.extend_from_slice(&0u32.to_le_bytes()); ogg_page(&mut out, &tags, 0, 0, 1); let pre_skip = enc.pre_skip() as u64; for (i, p) in packets.iter().enumerate() { let last = i + 1 == packets.len(); let granule = if last { pre_skip + len as u64 } else { ((i + 1) * FRAME) as u64 }; ogg_page(&mut out, p, if last { 0x04 } else { 0 }, granule, i as u32 + 2); } out }