#![no_std]
use core::fmt::Debug;
#[cfg(test)]
use core::mem::size_of;
use include_data::include_data;
use tracing::{error, instrument};
const BYTE_SIZE: usize = 20810;
type PcmSample = i16;
#[cfg(test)]
const SAMPLE_SIZE: usize = size_of::<PcmSample>();
const MAX_LETTERS: usize = 32;
#[cfg(test)]
const LETTER_SAMPLES: usize = BYTE_SIZE / SAMPLE_SIZE;
const MAX_BUFFER_SIZE: usize = BYTE_SIZE * MAX_LETTERS;
macro_rules! import_raw {
($var_name:ident, $file_name:literal) => {
const $var_name: [PcmSample; BYTE_SIZE / 2] = include_data!($file_name);
};
}
import_raw!(A, "../data/a.raw");
import_raw!(B, "../data/b.raw");
import_raw!(C, "../data/c.raw");
import_raw!(D, "../data/d.raw");
import_raw!(E, "../data/e.raw");
import_raw!(F, "../data/f.raw");
import_raw!(G, "../data/g.raw");
import_raw!(H, "../data/h.raw");
import_raw!(I, "../data/i.raw");
import_raw!(J, "../data/j.raw");
import_raw!(K, "../data/k.raw");
import_raw!(L, "../data/l.raw");
import_raw!(M, "../data/m.raw");
import_raw!(N, "../data/n.raw");
import_raw!(O, "../data/o.raw");
import_raw!(P, "../data/p.raw");
import_raw!(Q, "../data/q.raw");
import_raw!(R, "../data/r.raw");
import_raw!(S, "../data/s.raw");
import_raw!(T, "../data/t.raw");
import_raw!(U, "../data/u.raw");
import_raw!(V, "../data/v.raw");
import_raw!(W, "../data/w.raw");
import_raw!(X, "../data/x.raw");
import_raw!(Y, "../data/y.raw");
import_raw!(Z, "../data/z.raw");
import_raw!(SPACE, "../data/space.raw");
#[instrument]
fn letter_to_pcm(c: char) -> Option<[PcmSample; BYTE_SIZE / 2]> {
match c {
'a' => Some(A),
'b' => Some(B),
'c' => Some(C),
'd' => Some(D),
'e' => Some(E),
'f' => Some(F),
'g' => Some(G),
'h' => Some(H),
'i' => Some(I),
'j' => Some(J),
'k' => Some(K),
'l' => Some(L),
'm' => Some(M),
'n' => Some(N),
'o' => Some(O),
'p' => Some(P),
'q' => Some(Q),
'r' => Some(R),
's' => Some(S),
't' => Some(T),
'u' => Some(U),
'v' => Some(V),
'w' => Some(W),
'x' => Some(X),
'y' => Some(Y),
'z' => Some(Z),
' ' => Some(SPACE),
_ => {
error!(
"Character '{}' does not correspond to a pre-recorded sound",
c
);
None
}
}
}
#[instrument(ret, skip(buf))]
pub fn tts<S: AsRef<str> + Debug>(s: S, buf: &mut [PcmSample; MAX_BUFFER_SIZE]) -> Option<usize> {
if s.as_ref().len() > MAX_LETTERS {
error!("The length of the string {} ({} letters) is greater than the maximum amount of letters permitted: {}", s.as_ref(), s.as_ref().len(), MAX_LETTERS);
return None;
}
Some(s.as_ref().chars().fold(0, |offset: usize, ch| {
letter_to_pcm(ch)
.unwrap()
.iter()
.enumerate()
.for_each(|(i, pcm)| buf[(offset * BYTE_SIZE) + i] = *pcm);
offset + 1
}))
}
#[cfg(test)]
mod tests {
extern crate alloc;
use super::tts;
use super::PcmSample;
use super::A;
use super::LETTER_SAMPLES;
use super::MAX_BUFFER_SIZE;
use alloc::string::String;
use test_log::test;
#[test]
fn check_one_letter_str() {
let mut buf: [PcmSample; MAX_BUFFER_SIZE] = [0; MAX_BUFFER_SIZE];
let conv = String::from("a");
let bytes = tts(conv, &mut buf);
assert_eq!(bytes.unwrap(), 1);
let created_slice = &buf[0..LETTER_SAMPLES];
assert_eq!(created_slice.len(), A.len());
assert_eq!(created_slice, A);
}
#[test]
fn check_one_word_str() {
let mut buf: [PcmSample; MAX_BUFFER_SIZE] = [0; MAX_BUFFER_SIZE];
let conv = String::from("hello");
let bytes = tts(conv, &mut buf);
assert_eq!(bytes.unwrap(), 5);
}
#[test]
fn check_multi_word() {
let mut buf: [PcmSample; MAX_BUFFER_SIZE] = [0; MAX_BUFFER_SIZE];
let conv = String::from("hello world");
let bytes = tts(conv, &mut buf);
assert_eq!(bytes.unwrap(), 11);
}
}