#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(all(not(feature = "std"), feature = "alloc"))]
extern crate alloc;
#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::{string::String, vec::Vec};
#[cfg(feature = "std")]
use std::{string::String, vec::Vec};
pub const SELF: &str = "🙋";
pub const YOU: &str = "👤";
pub const LOVE: &str = "❤️";
pub const THINK: &str = "🧠";
pub const REMEMBER: &str = "💭";
pub const BREAK: &str = "⧖";
pub const PAST: &str = "⏮";
pub const PRESENT: &str = "⏺";
pub const FUTURE: &str = "⏭";
pub const HAPPY: &str = "😊";
pub const SAD: &str = "😢";
pub const ANGRY: &str = "😡";
pub const FEAR: &str = "😨";
pub const NEUTRAL: &str = "😐";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Prosody {
pub semitone_offset: i8, pub grit: u8, pub bright: u8, }
impl Default for Prosody {
fn default() -> Self {
Self {
semitone_offset: 0,
grit: 0,
bright: 1,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Phone {
pub ph: &'static str,
pub boundary: bool,
pub prosody: Prosody,
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PhId {
Mm = 0, Yu = 1, Luv = 2, Nnn = 3, Mah = 4, Tsk = 5, Wah = 6, Oh = 7, Wee = 8, Hee = 9, Aww = 10, Grr = 11, Eee = 12, Uhh = 13, Nn = 14, Uh = 15, }
impl PhId {
pub fn from_symbol(sym: &str) -> Self {
match sym {
"🙋" => PhId::Mm,
"👤" => PhId::Yu,
"❤️" => PhId::Luv,
"🧠" => PhId::Nnn,
"💭" => PhId::Mah,
"⧖" => PhId::Tsk,
"⏮" => PhId::Wah,
"⏺" => PhId::Oh,
"⏭" => PhId::Wee,
"😊" => PhId::Hee,
"😢" => PhId::Aww,
"😡" => PhId::Grr,
"😨" => PhId::Eee,
"😐" => PhId::Uhh,
"∧" => PhId::Nn,
_ => PhId::Uh,
}
}
pub fn to_ascii(&self) -> &'static str {
match self {
PhId::Mm => "mm",
PhId::Yu => "yu",
PhId::Luv => "luv",
PhId::Nnn => "nnn",
PhId::Mah => "mah",
PhId::Tsk => "[!]",
PhId::Wah => "wah",
PhId::Oh => "oh",
PhId::Wee => "wee",
PhId::Hee => "hee",
PhId::Aww => "aww",
PhId::Grr => "grr",
PhId::Eee => "eee",
PhId::Uhh => "uhh",
PhId::Nn => "nn",
PhId::Uh => "uh",
}
}
}
const PH_MASK: u16 = 0b0000_0000_0000_1111;
const SEM_SHIFT: u16 = 4;
const SEM_MASK: u16 = 0b0000_0001_1111_0000;
const BR_SHIFT: u16 = 9;
const BR_MASK: u16 = 0b0000_0110_0000_0000;
const GR_SHIFT: u16 = 11;
const GR_MASK: u16 = 0b0001_1000_0000_0000;
const BD_SHIFT: u16 = 13;
const BD_MASK: u16 = 0b0010_0000_0000_0000;
#[inline]
fn semitone_to_5bit_tc(v: i8) -> u16 {
let cl = if v < -16 {
-16
} else if v > 15 {
15
} else {
v
};
let raw = if cl < 0 {
(32 + cl as i16) as u8
} else {
cl as u8
};
(raw & 0b1_1111) as u16
}
#[inline]
fn semitone_from_5bit_tc(bits: u16) -> i8 {
let v = (bits & 0b1_1111) as i8;
if v & 0b1_0000 != 0 {
(v as i16 - 32) as i8
} else {
v
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Packet(pub u16);
impl Packet {
pub fn pack(ph_id: PhId, semitone: i8, bright: u8, grit: u8, boundary: bool) -> Self {
let mut w: u16 = (ph_id as u16) & 0xF;
w |= semitone_to_5bit_tc(semitone) << SEM_SHIFT;
w |= ((bright & 0x3) as u16) << BR_SHIFT;
w |= ((grit & 0x3) as u16) << GR_SHIFT;
if boundary {
w |= 1 << BD_SHIFT;
}
Packet(w)
}
pub fn unpack(self) -> (PhId, i8, u8, u8, bool) {
let ph_raw = (self.0 & PH_MASK) as u8;
let ph = match ph_raw {
0 => PhId::Mm,
1 => PhId::Yu,
2 => PhId::Luv,
3 => PhId::Nnn,
4 => PhId::Mah,
5 => PhId::Tsk,
6 => PhId::Wah,
7 => PhId::Oh,
8 => PhId::Wee,
9 => PhId::Hee,
10 => PhId::Aww,
11 => PhId::Grr,
12 => PhId::Eee,
13 => PhId::Uhh,
14 => PhId::Nn,
_ => PhId::Uh,
};
let semi = semitone_from_5bit_tc((self.0 & SEM_MASK) >> SEM_SHIFT);
let bright = ((self.0 & BR_MASK) >> BR_SHIFT) as u8;
let grit = ((self.0 & GR_MASK) >> GR_SHIFT) as u8;
let boundary = ((self.0 & BD_MASK) >> BD_SHIFT) != 0;
(ph, semi, bright, grit, boundary)
}
pub fn raw(self) -> u16 {
self.0
}
}
pub fn encode(tokens: &[&str]) -> Vec<Phone> {
let mut phones = Vec::new();
let mut current_prosody = Prosody::default();
for &tok in tokens {
match tok {
"😊" => current_prosody.bright = 3,
"😢" => {
current_prosody.bright = 1;
current_prosody.semitone_offset = -5;
}
"😡" => current_prosody.grit = 3,
"😨" => {
current_prosody.bright = 2;
current_prosody.semitone_offset = 8;
}
"😐" => {
current_prosody.bright = 0;
current_prosody.grit = 0;
current_prosody.semitone_offset = 0;
} "⏮" => current_prosody.semitone_offset = -8, "⏺" => current_prosody.semitone_offset = 0, "⏭" => current_prosody.semitone_offset = 8, _ => {}
}
let ph_id = PhId::from_symbol(tok);
let boundary = tok == "⧖";
phones.push(Phone {
ph: ph_id.to_ascii(),
boundary,
prosody: current_prosody,
});
if boundary {
current_prosody = Prosody::default();
}
}
phones
}
pub fn encode_compact(tokens: &[&str]) -> Vec<Packet> {
let phones = encode(tokens);
phones
.into_iter()
.map(|ph| {
let id = PhId::from_symbol(match ph.ph {
"mm" => "🙋",
"yu" => "👤",
"luv" => "❤️",
"nnn" => "🧠",
"mah" => "💭",
"[!]" => "⧖",
"wah" => "⏮",
"oh" => "⏺",
"wee" => "⏭",
"hee" => "😊",
"aww" => "😢",
"grr" => "😡",
"eee" => "😨",
"uhh" => "😐",
"nn" => "∧",
_ => "?",
});
Packet::pack(
id,
ph.prosody.semitone_offset,
ph.prosody.bright,
ph.prosody.grit,
ph.boundary,
)
})
.collect()
}
pub fn decode_compact(packets: &[Packet]) -> Vec<Phone> {
packets
.iter()
.map(|&pk| {
let (id, semi, bright, grit, boundary) = pk.unpack();
Phone {
ph: id.to_ascii(),
boundary,
prosody: Prosody {
semitone_offset: semi,
grit,
bright,
},
}
})
.collect()
}
#[cfg(any(feature = "std", feature = "alloc"))]
pub fn to_ascii_line(phones: &[Phone]) -> String {
let mut out = String::new();
for ph in phones {
out.push_str(ph.ph);
if ph.prosody.semitone_offset > 0 {
out.push('↗');
} else if ph.prosody.semitone_offset < 0 {
out.push('↘');
}
if ph.prosody.bright > 2 {
out.push('˜'); }
if ph.prosody.grit > 2 {
out.push('!'); }
if ph.boundary {
out.push_str(" | ");
} else {
out.push(' ');
}
}
out
}
#[cfg(any(feature = "std", feature = "alloc"))]
pub fn compact_to_ascii_line(packets: &[Packet]) -> String {
let phones = decode_compact(packets);
to_ascii_line(&phones)
}
pub fn example_i_love_you() -> Vec<Packet> {
encode_compact(&["🙋", "❤️", "👤", "⧖"])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pack_roundtrip() {
let p = Packet::pack(PhId::Luv, -7, 3, 2, true);
let (id, s, b, g, bd) = p.unpack();
assert_eq!(id, PhId::Luv);
assert_eq!(s, -7);
assert_eq!(b, 3);
assert_eq!(g, 2);
assert!(bd);
}
#[test]
fn test_i_love_you() {
let packets = example_i_love_you();
assert_eq!(packets.len(), 4);
let total_bytes = packets.len() * 2;
assert_eq!(total_bytes, 8);
}
#[test]
fn test_emotional_coloring() {
let packets = encode_compact(&["😊", "🙋", "❤️", "👤", "⧖"]);
let phones = decode_compact(&packets);
assert!(phones[1].prosody.bright > 0); assert!(phones[2].prosody.bright > 0); }
#[test]
fn test_temporal_pitch() {
let past = encode_compact(&["⏮", "🙋", "😊", "⧖"]);
let future = encode_compact(&["⏭", "🙋", "😊", "⧖"]);
let past_phones = decode_compact(&past);
let future_phones = decode_compact(&future);
assert!(past_phones[1].prosody.semitone_offset < 0);
assert!(future_phones[1].prosody.semitone_offset > 0);
}
#[cfg(any(feature = "std", feature = "alloc"))]
#[test]
fn test_ascii_rendering() {
let packets = encode_compact(&["🙋", "❤️", "👤", "⧖"]);
let ascii = compact_to_ascii_line(&packets);
assert!(ascii.contains("mm"));
assert!(ascii.contains("luv"));
assert!(ascii.contains("yu"));
assert!(ascii.contains("|")); }
}