#[cfg(feature = "analysis")]
pub mod mlp;
#[cfg(feature = "analysis")]
pub mod mlp_data;
#[cfg(all(feature = "analysis", target_arch = "aarch64"))]
mod neon;
#[cfg(feature = "analysis")]
mod tables;
#[cfg(feature = "analysis")]
mod tonality;
#[cfg(all(feature = "analysis", target_arch = "x86_64", target_feature = "sse2"))]
mod x86;
#[cfg(feature = "analysis")]
pub use tonality::{
ANALYSIS_BUF_SIZE, ANALYSIS_COUNT_MAX, DETECT_SIZE, NB_FRAMES, NB_TBANDS,
TonalityAnalysisState, fast_atan2f,
};
pub const LEAK_BANDS: usize = 19;
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct AnalysisInfo {
pub valid: bool,
pub tonality: f32,
pub tonality_slope: f32,
pub noisiness: f32,
pub activity: f32,
pub music_prob: f32,
pub music_prob_min: f32,
pub music_prob_max: f32,
pub bandwidth: i32,
pub activity_probability: f32,
pub max_pitch_ratio: f32,
pub leak_boost: [u8; LEAK_BANDS],
}
#[derive(Clone, Copy, Debug)]
pub enum PcmRef<'a> {
F32(&'a [f32]),
I16(&'a [i16]),
I32(&'a [i32]),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SecondChannel {
None,
Channel(usize),
AllOthers,
}
#[derive(Clone, Copy, Debug)]
pub struct AnalysisPcm<'a> {
pub pcm: PcmRef<'a>,
pub channels: usize,
pub c1: usize,
pub c2: SecondChannel,
pub frame_size: usize,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum AnalysisGate {
#[default]
FloatBuild,
FixedPointBuild,
Off,
}
impl AnalysisGate {
#[cfg_attr(not(feature = "analysis"), allow(dead_code))]
pub(crate) fn min_complexity(self) -> Option<i32> {
match self {
AnalysisGate::FloatBuild => Some(7),
AnalysisGate::FixedPointBuild => Some(10),
AnalysisGate::Off => None,
}
}
}
pub fn is_digital_silence(pcm: &[f32], lsb_depth: i32) -> bool {
let threshold = 1.0 / (1i32 << lsb_depth) as f32;
#[cfg(any(target_arch = "aarch64", all(target_arch = "x86_64", target_feature = "sse2")))]
if let Some(max_abs) = max_abs_without_nan(pcm) {
return max_abs <= threshold;
}
is_digital_silence_scalar(pcm, threshold)
}
fn is_digital_silence_scalar(pcm: &[f32], threshold: f32) -> bool {
let mut max = 0.0f32;
let mut min = 0.0f32;
for &x in pcm {
max = if max > x { max } else { x };
min = if min < x { min } else { x };
}
let neg_min = -min;
let max_abs = if max > neg_min { max } else { neg_min };
max_abs <= threshold
}
#[cfg(target_arch = "aarch64")]
fn max_abs_without_nan(pcm: &[f32]) -> Option<f32> {
use crate::simd_mem::{array_at, neon::load_f32x4};
use core::arch::aarch64::*;
let (blocks, tail) = pcm.as_chunks::<8>();
let (mut max, mut min) = unsafe {
let zero = vdupq_n_f32(0.0);
let (mut max0, mut max1, mut min0, mut min1) = (zero, zero, zero, zero);
for b in blocks {
let a = load_f32x4(array_at(b, 0));
let c = load_f32x4(array_at(b, 4));
max0 = vmaxq_f32(max0, a);
max1 = vmaxq_f32(max1, c);
min0 = vminq_f32(min0, a);
min1 = vminq_f32(min1, c);
}
(vmaxvq_f32(vmaxq_f32(max0, max1)), vminvq_f32(vminq_f32(min0, min1)))
};
for &x in tail {
max = if x > max || x.is_nan() { x } else { max };
min = if x < min { x } else { min };
}
if max.is_nan() || min.is_nan() {
return None;
}
Some(if max > -min { max } else { -min })
}
#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
fn max_abs_without_nan(pcm: &[f32]) -> Option<f32> {
use crate::simd_mem::array_at;
use crate::simd_mem::x86::{load_f32x4, to_array_f32x4};
use core::arch::x86_64::*;
let (blocks, tail) = pcm.as_chunks::<8>();
let (max, nan) = unsafe {
let sign = _mm_set1_ps(-0.0);
let zero = _mm_setzero_ps();
let (mut max0, mut max1, mut nan0, mut nan1) = (zero, zero, zero, zero);
for b in blocks {
let a = load_f32x4(array_at(b, 0));
let c = load_f32x4(array_at(b, 4));
max0 = _mm_max_ps(_mm_andnot_ps(sign, a), max0);
max1 = _mm_max_ps(_mm_andnot_ps(sign, c), max1);
nan0 = _mm_or_ps(nan0, _mm_cmpunord_ps(a, a));
nan1 = _mm_or_ps(nan1, _mm_cmpunord_ps(c, c));
}
let max = to_array_f32x4(_mm_max_ps(max0, max1));
(max, _mm_movemask_ps(_mm_or_ps(nan0, nan1)) != 0)
};
if nan || tail.iter().any(|x| x.is_nan()) {
return None;
}
let max = max.into_iter().chain(tail.iter().map(|x| x.abs())).fold(0.0f32, f32::max);
Some(max)
}
#[cfg(all(
test,
feature = "std",
any(target_arch = "aarch64", all(target_arch = "x86_64", target_feature = "sse2"))
))]
mod tests {
use super::*;
#[test]
fn vector_silence_test_matches_the_scalar_loop() {
let mut seed = 0x1234_5678_9abc_def0u64;
let mut next = || {
seed ^= seed << 13;
seed ^= seed >> 7;
seed ^= seed << 17;
seed
};
let specials = [0.0, -0.0, f32::INFINITY, f32::NEG_INFINITY, f32::NAN, 1e-40, -1e-40, f32::MAX];
for len in 0..80 {
for _ in 0..200 {
let lsb = 8 + (next() % 17) as i32;
let threshold = 1.0 / (1i32 << lsb) as f32;
let pcm: Vec<f32> = (0..len)
.map(|_| match next() % 16 {
0 => specials[(next() % specials.len() as u64) as usize],
1 => threshold,
2 => -threshold,
3 => f32::from_bits(threshold.to_bits() + 1),
_ => {
let scale = threshold * 4.0;
((next() >> 40) as f32 / (1u64 << 24) as f32 - 0.5) * scale
}
})
.collect();
let scalar = is_digital_silence_scalar(&pcm, threshold);
match max_abs_without_nan(&pcm) {
Some(m) => {
assert!(!pcm.iter().any(|x| x.is_nan()));
assert_eq!(m <= threshold, scalar, "{pcm:?} lsb {lsb}");
}
None => assert!(pcm.iter().any(|x| x.is_nan())),
}
assert_eq!(is_digital_silence(&pcm, lsb), scalar, "{pcm:?} lsb {lsb}");
}
}
}
}