|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#![cfg_attr(not(feature = "std"), no_std)] |
|
|
|
|
|
use crate::config::MarineConfig; |
|
|
use crate::ema::Ema; |
|
|
use crate::packet::{SalienceMarker, SaliencePacket}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub struct MarineProcessor { |
|
|
|
|
|
cfg: MarineConfig, |
|
|
|
|
|
|
|
|
prev2: f32, |
|
|
|
|
|
prev1: f32, |
|
|
|
|
|
idx: u64, |
|
|
|
|
|
|
|
|
last_peak_idx: u64, |
|
|
|
|
|
last_peak_amp: f32, |
|
|
|
|
|
|
|
|
ema_period: Ema, |
|
|
|
|
|
ema_amp: Ema, |
|
|
|
|
|
|
|
|
peak_count: u64, |
|
|
} |
|
|
|
|
|
impl MarineProcessor { |
|
|
|
|
|
pub fn new(cfg: MarineConfig) -> Self { |
|
|
Self { |
|
|
cfg, |
|
|
prev2: 0.0, |
|
|
prev1: 0.0, |
|
|
idx: 0, |
|
|
last_peak_idx: 0, |
|
|
last_peak_amp: 0.0, |
|
|
ema_period: Ema::new(cfg.ema_period_alpha), |
|
|
ema_amp: Ema::new(cfg.ema_amp_alpha), |
|
|
peak_count: 0, |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn process_sample(&mut self, sample: f32) -> Option<SalienceMarker> { |
|
|
let i = self.idx; |
|
|
self.idx += 1; |
|
|
|
|
|
|
|
|
if sample.abs() < self.cfg.clip_threshold { |
|
|
self.prev2 = self.prev1; |
|
|
self.prev1 = sample; |
|
|
return None; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
let is_peak = i >= 2 |
|
|
&& self.prev1.abs() >= self.cfg.clip_threshold |
|
|
&& self.prev1.abs() > self.prev2.abs() |
|
|
&& self.prev1.abs() > sample.abs(); |
|
|
|
|
|
let mut result = None; |
|
|
|
|
|
if is_peak { |
|
|
let peak_idx = i - 1; |
|
|
let amp = self.prev1.abs(); |
|
|
let energy = amp * amp; |
|
|
|
|
|
|
|
|
let period = if self.last_peak_idx == 0 { |
|
|
0.0 |
|
|
} else { |
|
|
(peak_idx - self.last_peak_idx) as f32 |
|
|
}; |
|
|
|
|
|
|
|
|
if period > self.cfg.min_period as f32 && period < self.cfg.max_period as f32 { |
|
|
if self.ema_period.is_ready() { |
|
|
|
|
|
let jp = (period - self.ema_period.get()).abs() / self.ema_period.get(); |
|
|
let ja = (amp - self.ema_amp.get()).abs() / self.ema_amp.get(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let h = 1.0; |
|
|
|
|
|
|
|
|
|
|
|
let s = 1.0 / (1.0 + jp + ja); |
|
|
|
|
|
result = Some(SalienceMarker::Peak(SaliencePacket::new( |
|
|
jp, ja, h, s, energy, peak_idx, |
|
|
))); |
|
|
} |
|
|
|
|
|
|
|
|
self.ema_period.update(period); |
|
|
self.ema_amp.update(amp); |
|
|
} |
|
|
|
|
|
self.last_peak_idx = peak_idx; |
|
|
self.last_peak_amp = amp; |
|
|
self.peak_count += 1; |
|
|
} |
|
|
|
|
|
|
|
|
self.prev2 = self.prev1; |
|
|
self.prev1 = sample; |
|
|
|
|
|
result |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "std")] |
|
|
pub fn process_buffer(&mut self, samples: &[f32]) -> Vec<SaliencePacket> { |
|
|
let mut packets = Vec::new(); |
|
|
|
|
|
for &sample in samples { |
|
|
if let Some(SalienceMarker::Peak(packet)) = self.process_sample(sample) { |
|
|
packets.push(packet); |
|
|
} |
|
|
} |
|
|
|
|
|
packets |
|
|
} |
|
|
|
|
|
|
|
|
pub fn reset(&mut self) { |
|
|
self.prev2 = 0.0; |
|
|
self.prev1 = 0.0; |
|
|
self.idx = 0; |
|
|
self.last_peak_idx = 0; |
|
|
self.last_peak_amp = 0.0; |
|
|
self.ema_period.reset(); |
|
|
self.ema_amp.reset(); |
|
|
self.peak_count = 0; |
|
|
} |
|
|
|
|
|
|
|
|
pub fn peak_count(&self) -> u64 { |
|
|
self.peak_count |
|
|
} |
|
|
|
|
|
|
|
|
pub fn current_index(&self) -> u64 { |
|
|
self.idx |
|
|
} |
|
|
|
|
|
|
|
|
pub fn is_warmed_up(&self) -> bool { |
|
|
self.peak_count >= 3 && self.ema_period.is_ready() |
|
|
} |
|
|
|
|
|
|
|
|
pub fn expected_period(&self) -> Option<f32> { |
|
|
if self.ema_period.is_ready() { |
|
|
Some(self.ema_period.get()) |
|
|
} else { |
|
|
None |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
pub fn expected_amplitude(&self) -> Option<f32> { |
|
|
if self.ema_amp.is_ready() { |
|
|
Some(self.ema_amp.get()) |
|
|
} else { |
|
|
None |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
#[cfg(test)] |
|
|
mod tests { |
|
|
use super::*; |
|
|
|
|
|
#[test] |
|
|
fn test_peak_detection() { |
|
|
let config = MarineConfig::speech_default(22050); |
|
|
let mut processor = MarineProcessor::new(config); |
|
|
|
|
|
|
|
|
|
|
|
let mut samples = vec![0.0; 100]; |
|
|
for i in (10..100).step_by(10) { |
|
|
samples[i] = 0.5; |
|
|
if i > 0 { |
|
|
samples[i - 1] = 0.3; |
|
|
} |
|
|
if i < 99 { |
|
|
samples[i + 1] = 0.3; |
|
|
} |
|
|
} |
|
|
|
|
|
let mut peak_count = 0; |
|
|
for sample in &samples { |
|
|
if let Some(SalienceMarker::Peak(_)) = processor.process_sample(*sample) { |
|
|
peak_count += 1; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
assert!(peak_count > 0); |
|
|
} |
|
|
|
|
|
#[test] |
|
|
fn test_jitter_calculation() { |
|
|
let mut config = MarineConfig::speech_default(22050); |
|
|
config.min_period = 5; |
|
|
config.max_period = 20; |
|
|
let mut processor = MarineProcessor::new(config); |
|
|
|
|
|
|
|
|
let mut detected_packets = vec![]; |
|
|
for cycle in 0..10 { |
|
|
for i in 0..10 { |
|
|
let sample = if i == 5 { |
|
|
0.8 |
|
|
} else if i == 4 || i == 6 { |
|
|
0.5 |
|
|
} else { |
|
|
0.01 |
|
|
}; |
|
|
|
|
|
if let Some(SalienceMarker::Peak(packet)) = processor.process_sample(sample) { |
|
|
detected_packets.push(packet); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if detected_packets.len() > 3 { |
|
|
let last = detected_packets.last().unwrap(); |
|
|
|
|
|
assert!(last.j_p < 0.5, "Period jitter too high: {}", last.j_p); |
|
|
} |
|
|
} |
|
|
|
|
|
#[test] |
|
|
fn test_reset() { |
|
|
let config = MarineConfig::speech_default(22050); |
|
|
let mut processor = MarineProcessor::new(config); |
|
|
|
|
|
|
|
|
for _ in 0..100 { |
|
|
processor.process_sample(0.5); |
|
|
} |
|
|
assert!(processor.current_index() > 0); |
|
|
|
|
|
|
|
|
processor.reset(); |
|
|
assert_eq!(processor.current_index(), 0); |
|
|
assert_eq!(processor.peak_count(), 0); |
|
|
assert!(!processor.is_warmed_up()); |
|
|
} |
|
|
|
|
|
#[cfg(feature = "std")] |
|
|
#[test] |
|
|
fn test_process_buffer() { |
|
|
let mut config = MarineConfig::speech_default(22050); |
|
|
config.min_period = 5; |
|
|
config.max_period = 50; |
|
|
let mut processor = MarineProcessor::new(config); |
|
|
|
|
|
|
|
|
let mut samples = Vec::new(); |
|
|
for _ in 0..20 { |
|
|
samples.extend_from_slice(&[0.01, 0.3, 0.8, 0.3, 0.01]); |
|
|
} |
|
|
|
|
|
let packets = processor.process_buffer(&samples); |
|
|
|
|
|
assert!(packets.len() > 0); |
|
|
} |
|
|
} |
|
|
|