libsam_rs/
lib.rs

1//! libSAM-rs
2//! sys/safe bindings for Software Automatic Mouth
3//! this runs using my fork of SAM
4//! which provides utility functions for setting Tts values
5//! and a utility function for rendering messages (they have to be formatted)
6//! this does build SAM for your target arch also and gets statically linked
7//! 
8//! If you have not heard of SAM it was used by the game FAITH: The Unholy Trinity
9//! due to having 4 values it can be really easy to use if you just randomize the values by 32-64
10
11use std::ffi::NulError;
12
13use libc::c_void;
14
15pub mod sys;
16
17/// unsigned 8-bit mono pcm @ 22050 hz audio returned by SAM
18pub type SAMAudio = Vec<u8>;
19
20/// A enum containg all errors that TTS can return
21pub enum TTSError {
22    /// the string *you* passed contains a null, dont do that
23    ContainsNull,
24    /// error id from the libSAM, will mabey split this into values l8r
25    Code(i32)
26}
27
28/// quick impl so i dont have to catch it and it can just be questioned
29impl From<NulError> for TTSError {
30    fn from(_value: NulError) -> Self {
31        TTSError::ContainsNull
32    }
33}
34
35/// set SAM tts values (0/None sets value to default)
36pub fn set_speech_values(
37    pitch: Option<u8>,
38    speed: Option<u8>,
39    throat: Option<u8>,
40    mouth: Option<u8>,
41) {
42    unsafe {
43        sys::setupSpeak(
44            pitch.unwrap_or(0),
45            speed.unwrap_or(0),
46            throat.unwrap_or(0),
47            mouth.unwrap_or(0),
48        )
49    }
50}
51
52/// internal function to render a string into PCM audio
53/// SAFTEY: chunk must be at most 255 bytes long
54unsafe fn render_chunk(chunk: &str) -> Result<Vec<u8>,TTSError> {
55    let mut bytes: Vec<i8> = chunk.bytes().map(|b|{std::mem::transmute(b)}).collect();
56    bytes.push(0);
57    let ptr = sys::speakText(bytes.as_mut_ptr());
58    let res = ptr.read();
59    if res.res != 1 {
60        libc::free(ptr as *mut c_void);
61        return Err(TTSError::Code(res.res))
62    }
63    let buf = std::slice::from_raw_parts(res.buf, res.buf_size as usize);
64    buf.into_iter().map(|b|std::mem::transmute(b)).collect()
65}
66
67/// Speaks the chosen text as a message
68pub fn speak_words(tospeak: &str) -> Result<SAMAudio, TTSError> {
69    let bytes: Vec<u8> = if tospeak.len()<=255 {
70        unsafe {render_chunk(tospeak)?}
71    } else {
72        let words = tospeak.split(' ');
73        let mut small = vec![];
74        let mut result: Vec<u8> = vec![];
75        for word in words {
76            if small.iter().map(|x:&&str| {x.len() }).fold(0,|acc, x| acc + x)+word.len() <= 255 {
77                small.push(word);
78            } else {
79                result.append(&mut unsafe {render_chunk(small.join(" ").as_str())?})
80            }
81        };
82        result.append(&mut unsafe {render_chunk(small.join(" ").as_str())?});
83        result
84    };
85    Ok(bytes)
86}