#![allow(missing_docs)]
pub const INSTN_RETURN: u16 = 0x0001;
pub const INSTN_CONTINUE: u16 = 0x0002;
pub const I_IPA_NAME: u16 = 0x0d; pub const I_CHANGE_PHONEME: u16 = 0x01; pub const I_CALLPH: u16 = 0x9100;
pub const I_PITCHENV: u16 = 0x9200;
pub const I_AMPENV: u16 = 0x9300;
pub const I_VOWELIN: u16 = 0xa100;
pub const I_VOWELOUT: u16 = 0xa200;
pub const I_FMT: u16 = 0xb000;
pub const I_WAV: u16 = 0xc000;
pub const I_VWLSTART: u16 = 0xd000;
pub const I_VWLENDING: u16 = 0xe000;
pub const I_WAVADD: u16 = 0xf000;
pub fn num_instn_words(instn: u16) -> usize {
const N_WORDS: [u8; 16] = [0, 1, 0, 0, 1, 1, 0, 1, 1, 2, 4, 0, 0, 0, 0, 0];
let hi4 = (instn >> 12) as usize;
let n = N_WORDS[hi4];
if n > 0 {
return n as usize;
}
match hi4 {
0 => {
let opcode = (instn >> 8) as u8;
if opcode == I_IPA_NAME as u8 {
let data = (instn & 0xff) as usize; 1 + (data + 1) / 2 } else {
1
}
}
2 | 3 => {
let n = instn & 0x0f00;
if n == 0x0600 || n == 0x0d00 { 2 } else { 1 }
}
6 => {
let type2 = (instn & 0x0f00) >> 9;
if type2 == 5 || type2 == 6 { 12 } else { 1 }
}
0xb | 0xc | 0xd | 0xe | 0xf => 2,
_ => 1,
}
}
#[derive(Debug, Clone, Default)]
pub struct PhonemeExtract {
pub fmt_addr: Option<u32>,
pub fmt_param: i8,
pub wav_addr: Option<u32>,
pub wav_param: i8,
pub vwlstart_addr: Option<u32>,
pub vwlending_addr: Option<u32>,
pub change_phoneme_code: Option<u8>,
}
pub fn scan_phoneme(program: u16, phonindex: &[u8]) -> PhonemeExtract {
let mut result = PhonemeExtract::default();
if program == 0 {
return result;
}
let mut pc = program as usize; let max_words = phonindex.len() / 2;
let scan_limit = pc + 128;
loop {
if pc >= max_words || pc >= scan_limit {
break;
}
let byte_off = pc * 2;
let instn = u16::from_le_bytes([phonindex[byte_off], phonindex[byte_off + 1]]);
if instn == INSTN_RETURN {
break;
}
let hi4 = instn >> 12;
match hi4 {
0xb => {
if result.fmt_addr.is_none() && pc + 1 < max_words {
let next = u16::from_le_bytes([
phonindex[(pc+1)*2],
phonindex[(pc+1)*2 + 1],
]);
let addr = ((instn & 0xf) as u32) << 18 | ((next as u32) << 2);
result.fmt_addr = Some(addr);
result.fmt_param = ((instn >> 4) & 0xff) as i8;
}
if pc + 2 < max_words {
let next2 = u16::from_le_bytes([
phonindex[(pc+2)*2],
phonindex[(pc+2)*2 + 1],
]);
if next2 >> 12 == 0xf {
pc += 2;
continue;
}
}
break; }
0xc => {
if result.wav_addr.is_none() && pc + 1 < max_words {
let next = u16::from_le_bytes([
phonindex[(pc+1)*2],
phonindex[(pc+1)*2 + 1],
]);
let addr = ((instn & 0xf) as u32) << 18 | ((next as u32) << 2);
result.wav_addr = Some(addr);
result.wav_param = ((instn >> 4) & 0xff) as i8;
}
break; }
0xd => {
if result.vwlstart_addr.is_none() && pc + 1 < max_words {
let next = u16::from_le_bytes([
phonindex[(pc+1)*2],
phonindex[(pc+1)*2 + 1],
]);
result.vwlstart_addr = Some(
((instn & 0xf) as u32) << 18 | ((next as u32) << 2)
);
}
pc += 2;
continue;
}
0xe => {
if result.vwlending_addr.is_none() && pc + 1 < max_words {
let next = u16::from_le_bytes([
phonindex[(pc+1)*2],
phonindex[(pc+1)*2 + 1],
]);
result.vwlending_addr = Some(
((instn & 0xf) as u32) << 18 | ((next as u32) << 2)
);
}
pc += 2;
continue;
}
0xf => {
if result.wav_addr.is_none() && pc + 1 < max_words {
let next = u16::from_le_bytes([
phonindex[(pc+1)*2],
phonindex[(pc+1)*2 + 1],
]);
result.wav_addr = Some(
((instn & 0xf) as u32) << 18 | ((next as u32) << 2)
);
}
break;
}
9 => {
let instn2 = ((instn >> 8) & 0xf) as u8;
if instn2 == 1 && pc + 1 < max_words {
let next = u16::from_le_bytes([phonindex[(pc+1)*2], phonindex[(pc+1)*2+1]]);
let called_prog = ((((instn & 0xf) as u32) << 16) | next as u32) as usize;
if called_prog > 0 && called_prog < max_words {
let sub = scan_phoneme(called_prog as u16, phonindex);
if result.fmt_addr.is_none() { result.fmt_addr = sub.fmt_addr; result.fmt_param = sub.fmt_param; }
if result.wav_addr.is_none() { result.wav_addr = sub.wav_addr; result.wav_param = sub.wav_param; }
if result.vwlstart_addr.is_none() { result.vwlstart_addr = sub.vwlstart_addr; }
if result.vwlending_addr.is_none() { result.vwlending_addr = sub.vwlending_addr; }
}
}
pc += 2;
continue;
}
0 => {
let opcode = (instn >> 8) as u8;
if opcode == I_CHANGE_PHONEME as u8 {
result.change_phoneme_code = Some((instn & 0xff) as u8);
}
pc += num_instn_words(instn);
continue;
}
_ => {
pc += num_instn_words(instn);
continue;
}
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
const PROG: u16 = 1;
fn pad(insns: &[u8]) -> Vec<u8> {
let mut v = vec![0u8, 0u8]; v.extend_from_slice(insns);
v
}
fn make_fmt_phonindex(fmt_addr: u32) -> Vec<u8> {
let instn: u16 = 0xb000 | (((fmt_addr >> 18) & 0xf) as u16);
let next: u16 = ((fmt_addr >> 2) & 0xffff) as u16;
let mut insns = vec![0u8; 4];
insns[0..2].copy_from_slice(&instn.to_le_bytes());
insns[2..4].copy_from_slice(&next.to_le_bytes());
pad(&insns)
}
fn make_ipa_then_fmt(fmt_addr: u32) -> Vec<u8> {
let ipa_instn: u16 = ((I_IPA_NAME as u16) << 8) | 2; let ipa_data: u16 = u16::from_be_bytes([b'e', b':']);
let fmt_instn: u16 = 0xb000 | (((fmt_addr >> 18) & 0xf) as u16);
let fmt_next: u16 = ((fmt_addr >> 2) & 0xffff) as u16;
let mut insns = vec![0u8; 8];
insns[0..2].copy_from_slice(&ipa_instn.to_le_bytes());
insns[2..4].copy_from_slice(&ipa_data.to_le_bytes());
insns[4..6].copy_from_slice(&fmt_instn.to_le_bytes());
insns[6..8].copy_from_slice(&fmt_next.to_le_bytes());
pad(&insns)
}
#[test]
fn scan_simple_fmt() {
let addr = 0x1234u32 * 4;
let phonindex = make_fmt_phonindex(addr);
let result = scan_phoneme(PROG, &phonindex);
assert_eq!(result.fmt_addr, Some(addr));
assert!(result.wav_addr.is_none());
}
#[test]
fn scan_ipa_then_fmt() {
let addr = 0x5678u32 * 4;
let phonindex = make_ipa_then_fmt(addr);
let result = scan_phoneme(PROG, &phonindex);
assert_eq!(result.fmt_addr, Some(addr));
}
#[test]
fn scan_zero_program_returns_empty() {
let phonindex = vec![0u8; 4];
let result = scan_phoneme(0, &phonindex); assert!(result.fmt_addr.is_none());
assert!(result.wav_addr.is_none());
}
#[test]
fn scan_return_stops_early() {
let addr = 0x1000u32 * 4;
let fmt_instn: u16 = 0xb000 | (((addr >> 18) & 0xf) as u16);
let fmt_next: u16 = ((addr >> 2) & 0xffff) as u16;
let mut insns = vec![0u8; 8];
insns[0..2].copy_from_slice(&INSTN_RETURN.to_le_bytes());
insns[2..4].copy_from_slice(&[0, 0]);
insns[4..6].copy_from_slice(&fmt_instn.to_le_bytes());
insns[6..8].copy_from_slice(&fmt_next.to_le_bytes());
let result = scan_phoneme(PROG, &pad(&insns));
assert!(result.fmt_addr.is_none(), "RETURN should stop scanner");
}
#[test]
fn num_instn_words_fmt() {
assert_eq!(num_instn_words(0xb000), 2);
assert_eq!(num_instn_words(0xb123), 2);
}
#[test]
fn num_instn_words_vowelin() {
assert_eq!(num_instn_words(0xa100), 4);
assert_eq!(num_instn_words(0xa200), 4);
}
#[test]
fn num_instn_words_ipa_name_4bytes() {
let instn: u16 = ((I_IPA_NAME as u16) << 8) | 4;
assert_eq!(num_instn_words(instn), 3);
}
#[test]
fn num_instn_words_callph() {
assert_eq!(num_instn_words(0x9100), 2);
assert_eq!(num_instn_words(0x9200), 2);
assert_eq!(num_instn_words(0x9300), 2);
}
#[test]
fn scan_wav_only() {
let addr = 0x2000u32 * 4;
let wav_instn: u16 = 0xc000 | (((addr >> 18) & 0xf) as u16);
let wav_next: u16 = ((addr >> 2) & 0xffff) as u16;
let mut insns = vec![0u8; 4];
insns[0..2].copy_from_slice(&wav_instn.to_le_bytes());
insns[2..4].copy_from_slice(&wav_next.to_le_bytes());
let result = scan_phoneme(PROG, &pad(&insns));
assert!(result.fmt_addr.is_none());
assert_eq!(result.wav_addr, Some(addr));
}
#[test]
fn scan_wavadd_after_fmt() {
let fmt_a = 0x1000u32 * 4;
let wav_a = 0x2000u32 * 4;
let fmt_instn: u16 = 0xb000 | (((fmt_a >> 18) & 0xf) as u16);
let fmt_next: u16 = ((fmt_a >> 2) & 0xffff) as u16;
let add_instn: u16 = 0xf000 | (((wav_a >> 18) & 0xf) as u16);
let add_next: u16 = ((wav_a >> 2) & 0xffff) as u16;
let mut insns = vec![0u8; 8];
insns[0..2].copy_from_slice(&fmt_instn.to_le_bytes());
insns[2..4].copy_from_slice(&fmt_next.to_le_bytes());
insns[4..6].copy_from_slice(&add_instn.to_le_bytes());
insns[6..8].copy_from_slice(&add_next.to_le_bytes());
let result = scan_phoneme(PROG, &pad(&insns));
assert_eq!(result.fmt_addr, Some(fmt_a));
assert_eq!(result.wav_addr, Some(wav_a));
}
}