pub const STRESS_LENGTHS_EN: [u32; 8] = [182, 140, 220, 220, 220, 240, 260, 280];
pub const STRESS_AMPS_EN: [u8; 8] = [18, 18, 20, 20, 20, 22, 22, 20];
pub const LENGTHEN_TONIC_EN: u32 = 20;
pub const MAXAMP_EOC_EN: u8 = 19;
pub const LENGTH_MODS_EN: [u8; 100] = [
100, 120, 100, 105, 100, 110, 110, 100, 95, 100, 105, 120, 105, 110, 125, 130, 135, 115, 125, 100, 105, 120, 75, 100, 75, 105, 120, 85, 75, 100, 105, 120, 85, 105, 95, 115, 120, 100, 95, 100, 110, 120, 95, 105, 100, 115, 120, 100, 100, 100, 105, 120, 100, 105, 95, 115, 120, 110, 95, 100, 105, 120, 100, 105, 105, 122, 125, 110, 105, 100, 105, 120, 100, 105, 105, 122, 125, 110, 105, 100, 105, 120, 95, 105, 100, 115, 120, 110, 100, 100, 100, 120, 100, 100, 100, 100, 100, 100, 100, 100, ];
pub const LENGTH_MODS_EN0: [u8; 100] = [
100, 150, 100, 105, 110, 115, 110, 110, 110, 100, 105, 150, 105, 110, 125, 135, 140, 115, 135, 100, 105, 150, 90, 105, 90, 122, 135, 100, 90, 100, 105, 150, 100, 105, 100, 122, 135, 100, 100, 100, 105, 150, 100, 105, 105, 115, 135, 110, 105, 100, 105, 150, 100, 105, 105, 122, 130, 120, 125, 100, 105, 150, 100, 105, 110, 122, 125, 115, 110, 100, 105, 150, 100, 105, 105, 122, 135, 120, 105, 100, 105, 150, 100, 105, 105, 115, 135, 110, 105, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, ];
pub fn calc_vowel_length_mod(
stress: u8,
next_lm: u8,
next2_lm: u8,
more_syllables: bool,
end_of_clause: bool,
std_length: u8,
) -> u32 {
let stress = (stress as usize).min(7);
let table = if more_syllables { &LENGTH_MODS_EN } else { &LENGTH_MODS_EN0 };
let n2 = (next2_lm as usize).min(9);
let n1 = (next_lm as usize).min(9);
let base_mod = table[n2 * 10 + n1] as u32;
let stress_len = STRESS_LENGTHS_EN[stress];
let mut length_mod = base_mod * stress_len / 128;
if stress >= 7 {
length_mod += LENGTHEN_TONIC_EN;
}
if end_of_clause {
let len = (std_length as u32) * 2; let eoc_num = 280u32.saturating_sub(len);
length_mod = length_mod * (256 + eoc_num / 3) / 256;
}
length_mod.clamp(8, 500)
}
pub fn length_mod_to_samples(length_mod: u32, samplerate: u32, speed_factor: f64) -> usize {
if length_mod <= 45 {
return 0;
}
let length_std = length_mod - 45;
let total = (length_std as f64 / 1000.0)
* samplerate as f64
* (length_mod as f64 / 256.0)
* speed_factor;
total.round() as usize
}
pub fn stress_code_to_level(code: u8) -> u8 {
let raw = match code {
0 => 0, 2 => 0, 3 => 2, 4 => 3, 5 => 4, 6 => 6, 7 => 7, _ => 0,
};
if raw <= 1 { raw ^ 1 } else { raw }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn primary_stress_eoc_gives_long_vowel() {
let length_mod = calc_vowel_length_mod(7, 1, 1, false, true, 110);
assert!(length_mod >= 300, "tonic EOC vowel should be long: {length_mod}");
let samp = length_mod_to_samples(length_mod, 22050, 1.0);
assert!(samp > 5000, "tonic EOC vowel > 200ms: {samp}");
}
#[test]
fn unstressed_vowel_is_short() {
let length_mod = calc_vowel_length_mod(1, 7, 0, true, false, 60);
let samp = length_mod_to_samples(length_mod, 22050, 1.0);
assert!(samp < 2205, "unstressed schwa should be short: {samp}");
}
#[test]
fn stress_code_mapping() {
assert_eq!(stress_code_to_level(6), 6); assert_eq!(stress_code_to_level(7), 7); assert_eq!(stress_code_to_level(0), 1); assert_eq!(stress_code_to_level(2), 1); }
}