use crate::bands::BANDS;
use crate::fixed::{acc, hi, low, shift};
const SCORED_BANDS: usize = 18;
const LOUD_TRACKED: i64 = 15360 << 16;
const LOUD_SUMMARY: i64 = 7680 << 16;
const VOICING_HIGH: i16 = 26214;
const VOICING_MID: i16 = 19661;
const VOICING_LOW: i16 = 13107;
const SCORE_LIMIT: i16 = 4;
#[derive(Clone, Copy)]
struct Thresholds {
limit: [i16; 6],
delay: i16,
length: i16,
}
fn thresholds(tracked: i64, summary: i64) -> Thresholds {
if acc(tracked - LOUD_TRACKED) > 0 {
Thresholds {
limit: [50, 15, 768, 128, 40, 6],
delay: 4,
length: 4,
}
} else if acc(summary - LOUD_SUMMARY) > 0 {
Thresholds {
limit: [50, 15, 768, 128, 40, 6],
delay: 6,
length: 2,
}
} else {
Thresholds {
limit: [80, 15, 1280, 128, 40, 6],
delay: 8,
length: 1,
}
}
}
#[derive(Clone, Copy, Default)]
pub struct Features {
pub tracked: i64,
pub excess_sum: i16,
pub difference: i64,
pub variance: i64,
}
#[derive(Clone, Copy, Default)]
pub struct Frame {
pub summary: i64,
pub reference: [Features; 2],
pub voicing: i16,
}
fn score(t: &Thresholds, f: &Features) -> i16 {
let mut total = 0i16;
let mut vote = |value: i64, upper: i64, lower: i64| {
if acc(value - upper) > 0 {
total += 1;
} else if acc(value - lower) < 0 {
total -= 1;
}
};
vote(f.excess_sum as i64, t.limit[0] as i64, t.limit[1] as i64);
vote(
f.difference,
(t.limit[2] as i64) << 16,
(t.limit[3] as i64) << 16,
);
vote(
f.variance,
(t.limit[4] as i64) << 16,
(t.limit[5] as i64) << 16,
);
total
}
#[derive(Clone, Copy)]
struct ReferenceScore {
thresholds: Thresholds,
value: i16,
}
impl ReferenceScore {
fn new(summary: i64, features: &Features) -> Self {
let thresholds = thresholds(features.tracked, summary);
Self {
value: score(&thresholds, features),
thresholds,
}
}
fn with_voicing(mut self, voicing: i16) -> Self {
self.value = (self.value + voicing_bias(voicing)).clamp(-SCORE_LIMIT, SCORE_LIMIT);
self
}
fn hold(self, hangover: &mut Hangover, loud: bool) -> Held {
hangover.step(
loud,
self.value,
self.thresholds.delay,
self.thresholds.length,
)
}
}
fn voicing_bias(voicing: i16) -> i16 {
if voicing > VOICING_HIGH {
2
} else if voicing > VOICING_MID {
1
} else if voicing > VOICING_LOW {
0
} else {
-2
}
}
#[derive(Clone, Copy, Default)]
struct Hangover {
active: i16,
remaining: i16,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Held {
Active,
Coasting,
Idle,
}
fn coasting_score(previous: i16) -> i16 {
if previous > 0 { previous - 1 } else { 0 }
}
impl Hangover {
fn step(&mut self, loud: bool, score: i16, delay: i16, length: i16) -> Held {
if loud && score >= 0 {
self.active = self.active.wrapping_add(1);
if self.active > delay {
self.remaining = length;
}
Held::Active
} else {
self.active = 0;
self.remaining = self.remaining.wrapping_sub(1);
if self.remaining > 0 {
Held::Coasting
} else {
self.remaining = 0;
Held::Idle
}
}
}
}
#[derive(Clone, Copy, Default)]
pub struct Vad {
hangover: [Hangover; 2],
previous: i16,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Decision {
pub active: bool,
pub fast: bool,
pub score: i16,
}
impl Vad {
pub fn run(&mut self, frame: &Frame) -> Decision {
let loud = acc(frame.summary - LOUD_SUMMARY) > 0;
let fast_score = ReferenceScore::new(frame.summary, &frame.reference[0]);
let active_score =
ReferenceScore::new(frame.summary, &frame.reference[1]).with_voicing(frame.voicing);
let fast = fast_score.hold(&mut self.hangover[0], loud) != Held::Idle;
let held = active_score.hold(&mut self.hangover[1], loud);
let mut reported = active_score.value;
if held == Held::Coasting {
reported = coasting_score(self.previous);
}
self.previous = reported;
Decision {
active: held != Held::Idle,
fast,
score: reported,
}
}
}
pub fn excess_sum(excess: &[i64; BANDS]) -> i16 {
let mut total = 0i64;
for &e in excess[1..=SCORED_BANDS].iter() {
total = acc(total + shift(hi(e) as i64, -8));
}
low(total)
}