use std::collections::HashMap;
pub fn normalize_loudness(samples: &[f32], target_rms: f32) -> Vec<f32> {
if samples.is_empty() {
return Vec::new();
}
let sum_sq: f32 = samples.iter().map(|s| s * s).sum();
let current_rms = (sum_sq / samples.len() as f32).sqrt();
if current_rms < 1e-10 {
return samples.to_vec();
}
let gain = target_rms / current_rms;
samples
.iter()
.map(|s| {
let amplified = s * gain;
if amplified.abs() > 0.95 {
amplified.signum() * (0.95 + 0.05 * (amplified.abs() - 0.95).tanh())
} else {
amplified
}
})
.collect()
}
pub fn trim_silence(samples: &[f32], threshold_db: f32, min_silence_samples: usize) -> Vec<f32> {
if samples.is_empty() {
return Vec::new();
}
let threshold = 10.0_f32.powf(threshold_db / 20.0);
let mut start = 0;
for (i, &sample) in samples.iter().enumerate() {
if sample.abs() > threshold {
start = i.saturating_sub(min_silence_samples / 4); break;
}
}
let mut end = samples.len();
for (i, &sample) in samples.iter().enumerate().rev() {
if sample.abs() > threshold {
end = (i + min_silence_samples / 4).min(samples.len()); break;
}
}
if start >= end {
return samples.to_vec();
}
samples[start..end].to_vec()
}
pub fn high_pass_filter(samples: &[f32], cutoff_hz: f32, sample_rate: f32) -> Vec<f32> {
if samples.is_empty() {
return Vec::new();
}
let rc = 1.0 / (2.0 * std::f32::consts::PI * cutoff_hz);
let dt = 1.0 / sample_rate;
let alpha = rc / (rc + dt);
let mut output = Vec::with_capacity(samples.len());
let mut prev_input = samples[0];
let mut prev_output = 0.0_f32;
for &sample in samples.iter() {
let filtered = alpha * (prev_output + sample - prev_input);
output.push(filtered);
prev_input = sample;
prev_output = filtered;
}
output
}
pub fn postprocess_tts_audio(samples: &[f32], sample_rate: u32) -> Vec<f32> {
let filtered = high_pass_filter(samples, 80.0, sample_rate as f32);
let min_silence = (sample_rate as f32 * 0.05) as usize; let trimmed = trim_silence(&filtered, -40.0, min_silence);
normalize_loudness(&trimmed, 0.1)
}
pub fn load_tokens_map(tokens_content: &str) -> HashMap<char, i64> {
let mut map = HashMap::new();
for line in tokens_content.lines() {
if line.is_empty() {
continue;
}
let mut chars = line.chars();
if let Some(token_char) = chars.next() {
let remaining: String = chars.collect();
let id_str = remaining.trim();
if let Ok(id) = id_str.parse::<i64>() {
map.insert(token_char, id);
}
}
}
map
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_load_tokens_map() {
let tokens_content = "$ 0\n; 1\na 43\nb 44\n";
let map = load_tokens_map(tokens_content);
assert_eq!(map.get(&'$'), Some(&0));
assert_eq!(map.get(&';'), Some(&1));
assert_eq!(map.get(&'a'), Some(&43));
assert_eq!(map.get(&'b'), Some(&44));
}
#[test]
fn test_load_tokens_map_handles_space_character() {
let tokens_content = "; 1\n 16\nA 24\n";
let map = load_tokens_map(tokens_content);
assert_eq!(map.get(&' '), Some(&16), "Space character should map to 16");
assert_eq!(map.get(&';'), Some(&1));
assert_eq!(map.get(&'A'), Some(&24));
}
#[test]
fn test_load_tokens_map_handles_empty_lines() {
let tokens_content = "a 1\n\nb 2\n";
let map = load_tokens_map(tokens_content);
assert_eq!(map.get(&'a'), Some(&1));
assert_eq!(map.get(&'b'), Some(&2));
}
}