use super::N_PHONEME_TAB_NAME;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct PhonemeTab {
pub mnemonic: u32,
pub phflags: u32,
pub program: u16,
pub code: u8,
pub typ: u8,
pub start_type: u8,
pub end_type: u8,
pub std_length: u8,
pub length_mod: u8,
}
impl PhonemeTab {
pub fn mnemonic_str(&self) -> String {
let mut s = String::with_capacity(4);
for shift in 0..4u32 {
let c = ((self.mnemonic >> (shift * 8)) & 0xff) as u8;
if c == 0 { break; }
s.push(c as char);
}
s
}
pub fn pack_mnemonic(s: &str) -> u32 {
let b = s.as_bytes();
let mut v = 0u32;
for (i, &byte) in b.iter().take(4).enumerate() {
v |= (byte as u32) << (i * 8);
}
v
}
pub fn from_bytes(bytes: &[u8; 16]) -> Self {
let mnemonic = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
let phflags = u32::from_le_bytes(bytes[4..8].try_into().unwrap());
let program = u16::from_le_bytes(bytes[8..10].try_into().unwrap());
let code = bytes[10];
let typ = bytes[11];
let start_type = bytes[12];
let end_type = bytes[13];
let std_length = bytes[14];
let length_mod = bytes[15];
Self { mnemonic, phflags, program, code, typ, start_type, end_type, std_length, length_mod }
}
pub fn to_bytes(&self) -> [u8; 16] {
let mut out = [0u8; 16];
out[0..4].copy_from_slice(&self.mnemonic.to_le_bytes());
out[4..8].copy_from_slice(&self.phflags.to_le_bytes());
out[8..10].copy_from_slice(&self.program.to_le_bytes());
out[10] = self.code;
out[11] = self.typ;
out[12] = self.start_type;
out[13] = self.end_type;
out[14] = self.std_length;
out[15] = self.length_mod;
out
}
}
#[derive(Debug, Clone)]
pub struct PhonemeTabList {
pub name: String,
pub phonemes: Vec<PhonemeTab>,
pub n_phonemes: usize,
pub includes: u8,
}
impl PhonemeTabList {
pub fn parse_name(buf: &[u8; N_PHONEME_TAB_NAME]) -> String {
let end = buf.iter().position(|&b| b == 0).unwrap_or(N_PHONEME_TAB_NAME);
String::from_utf8_lossy(&buf[..end]).into_owned()
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ReplacePhoneme {
pub old_ph: u8,
pub new_ph: u8,
pub kind: i8,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mnemonic_roundtrip_ascii() {
for name in &["@", "@-", "nas", "vwl!", "_:"] {
let packed = PhonemeTab::pack_mnemonic(name);
let mut ph = PhonemeTab::default();
ph.mnemonic = packed;
assert_eq!(&ph.mnemonic_str(), name, "roundtrip failed for {:?}", name);
}
}
#[test]
fn from_bytes_roundtrip() {
let raw: [u8; 16] = [
0x5f, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x25, 0x00, ];
let ph = PhonemeTab::from_bytes(&raw);
assert_eq!(ph.mnemonic_str(), "_:");
assert_eq!(ph.code, 9);
assert_eq!(ph.std_length, 37);
assert_eq!(ph.to_bytes(), raw, "round-trip failed");
}
#[test]
fn parse_name_null_padded() {
let mut buf = [0u8; 32];
buf[..4].copy_from_slice(b"base");
assert_eq!(PhonemeTabList::parse_name(&buf), "base");
}
#[test]
fn parse_name_full() {
let mut buf = [b'x'; 32];
assert_eq!(PhonemeTabList::parse_name(&buf).len(), 32);
buf[31] = 0;
assert_eq!(PhonemeTabList::parse_name(&buf).len(), 31);
}
}