diff --git a/Cargo.lock b/Cargo.lock index 3f7c815ab..c7888acf8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,7 +135,7 @@ dependencies = [ "ticktimer-server", "trng", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -168,7 +168,7 @@ dependencies = [ "ticktimer-server", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -183,7 +183,7 @@ dependencies = [ "rkyv", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -312,7 +312,7 @@ dependencies = [ "ticktimer-server", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -329,7 +329,7 @@ dependencies = [ "ticktimer-server", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -346,7 +346,7 @@ dependencies = [ "ticktimer-server", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -449,7 +449,7 @@ dependencies = [ "trng", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -470,7 +470,7 @@ dependencies = [ "typenum", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -713,7 +713,7 @@ dependencies = [ "trng", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -769,7 +769,7 @@ dependencies = [ "ticktimer-server", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -924,7 +924,7 @@ dependencies = [ "trng", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -1010,7 +1010,7 @@ dependencies = [ "trng", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -1073,7 +1073,7 @@ dependencies = [ "ticktimer-server", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -1087,7 +1087,7 @@ dependencies = [ "num-traits", "rkyv", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -1104,7 +1104,7 @@ dependencies = [ "ticktimer-server", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -1142,7 +1142,7 @@ dependencies = [ "ticktimer-server", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -1191,7 +1191,7 @@ dependencies = [ "ticktimer-server", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -1274,7 +1274,7 @@ dependencies = [ "ticktimer-server", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -1308,7 +1308,7 @@ dependencies = [ "num-traits", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", ] [[package]] @@ -1384,7 +1384,7 @@ dependencies = [ "trng", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -1410,7 +1410,7 @@ dependencies = [ "trng", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -1573,7 +1573,7 @@ dependencies = [ "trng", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -1836,7 +1836,7 @@ dependencies = [ "ticktimer-server", "trng", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -1900,7 +1900,7 @@ dependencies = [ "rkyv", "ticktimer-server", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -1948,7 +1948,7 @@ dependencies = [ "trng", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", "zeroize", ] @@ -2132,7 +2132,7 @@ dependencies = [ "trng", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -2176,10 +2176,11 @@ dependencies = [ "susres", "ticktimer-server", "trng", + "tts-frontend", "utralib", "x25519-dalek", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -2259,7 +2260,7 @@ dependencies = [ "trng", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -2301,7 +2302,7 @@ dependencies = [ "trng", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -2334,7 +2335,7 @@ dependencies = [ "rkyv", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -2442,7 +2443,7 @@ dependencies = [ "ticktimer-server", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", ] [[package]] @@ -2495,7 +2496,7 @@ dependencies = [ "susres", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] @@ -2541,10 +2542,27 @@ dependencies = [ "ticktimer-server", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", "xous-names", ] +[[package]] +name = "tts-frontend" +version = "0.1.0" +dependencies = [ + "log", + "log-server", + "num-derive", + "num-traits", + "rkyv", + "ticktimer-server", + "utralib", + "xous", + "xous-ipc 0.9.7", + "xous-names", + "xous-tts-backend", +] + [[package]] name = "typenum" version = "1.12.0" @@ -2927,6 +2945,17 @@ dependencies = [ "xous", ] +[[package]] +name = "xous-ipc" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9874c4b4dcce5c8fe73351e0bb27e08cc1bf5b58ce621a4c42d1a5cb8869bd87" +dependencies = [ + "bitflags", + "rkyv", + "xous", +] + [[package]] name = "xous-macros" version = "0.1.0" @@ -2948,7 +2977,20 @@ dependencies = [ "rkyv", "utralib", "xous", - "xous-ipc", + "xous-ipc 0.9.7", +] + +[[package]] +name = "xous-tts-backend" +version = "0.1.0" +dependencies = [ + "log", + "num-derive", + "num-traits", + "rkyv", + "xous", + "xous-ipc 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-names", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 03e3afce2..71a7758b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,6 +90,7 @@ members = [ "apps/repl", "services/libstd-test", "services/ffi-test", + "services/tts", "kernel", ] resolver = "2" @@ -121,6 +122,8 @@ xous-macros = {path = "macros"} path = "services/engine-sha512" [patch.crates-io.aes] path = "services/aes" +[patch.crates-io.xous-names] +path = "services/xous-names" [patch.crates-io.curve25519-dalek] git="https://github.com/betrusted-io/curve25519-dalek.git" diff --git a/buildpush.ps1 b/buildpush.ps1 index 741a9df30..178c5beb2 100644 --- a/buildpush.ps1 +++ b/buildpush.ps1 @@ -3,7 +3,7 @@ # set RUSTFLAGS=--remap-path-prefix=F:\largework\rust-win\code\xous-core\=build # $env:RUSTFLAGS="--remap-path-prefix=$(Get-Location)=build" -cargo xtask app-image ball repl +cargo xtask tts # cargo xtask ffi-test # cargo xtask minimal precursors/soc.svd @@ -12,7 +12,8 @@ CertUtil -hashfile precursors/soc_csr.bin MD5 CertUtil -hashfile target/riscv32imac-unknown-xous-elf/release/loader.bin MD5 CertUtil -hashfile target/riscv32imac-unknown-xous-elf/release/xous.img MD5 -scp -i c:/users/bunnie/.ssh/id_pi target/riscv32imac-unknown-xous-elf/release/xous.img target/riscv32imac-unknown-xous-elf/release/loader.bin precursors/soc_csr.bin pi@10.0.245.181:code/precursors/ +# scp -i c:/users/bunnie/.ssh/id_pi target/riscv32imac-unknown-xous-elf/release/xous.img target/riscv32imac-unknown-xous-elf/release/loader.bin precursors/soc_csr.bin pi@10.0.245.181:code/precursors/ +scp -i c:/users/bunnie/.ssh/id_ed25519 target/riscv32imac-unknown-xous-elf/release/xous.img target/riscv32imac-unknown-xous-elf/release/loader.bin precursors/soc_csr.bin bunnie@10.0.245.9:/mnt/c/Users/bunnie/ # CertUtil -hashfile target/riscv32imac-unknown-none-elf/release/loader.bin MD5 # CertUtil -hashfile target/riscv32imac-unknown-none-elf/release/xous.img MD5 diff --git a/services/shellchat/Cargo.toml b/services/shellchat/Cargo.toml index fbc59d1a1..0e354b69d 100644 --- a/services/shellchat/Cargo.toml +++ b/services/shellchat/Cargo.toml @@ -36,6 +36,7 @@ net = {path="../net"} dns = {path="../dns"} pddb = {path="../pddb"} modals = {path="../modals"} +tts-frontend = {path="../tts"} trng = {path = "../trng"} @@ -76,6 +77,7 @@ utralib = {path = "../../utralib"} debugprint = [] spinortest = [] # for spinor testing. contra-indicated with PDDB, as it steals memory from the PDDB. benchmarks = [] # adds the benchmark routines. Left off normally to free up code and memory space. +tts = [] # adds text to speech plugin default = [] # "debugprint" [target.'cfg(any(windows,unix))'.dependencies] diff --git a/services/shellchat/src/cmds.rs b/services/shellchat/src/cmds.rs index dd3ea59b9..c9c5a99cc 100644 --- a/services/shellchat/src/cmds.rs +++ b/services/shellchat/src/cmds.rs @@ -102,6 +102,11 @@ mod jtag_cmd; use jtag_cmd::*; mod net_cmd; use net_cmd::*; mod pddb_cmd; use pddb_cmd::*; +#[cfg(feature="tts")] +mod tts; +#[cfg(feature="tts")] +use tts::*; + #[cfg(feature="benchmarks")] mod engine; #[cfg(feature="benchmarks")] @@ -138,6 +143,9 @@ pub struct CmdEnv { pddb_cmd: PddbCmd, wlan_cmd: Wlan, + #[cfg(feature="tts")] + tts_cmd: Tts, + #[cfg(feature="benchmarks")] sha_cmd: Sha, #[cfg(feature="benchmarks")] @@ -205,6 +213,9 @@ impl CmdEnv { pddb_cmd: PddbCmd::new(&xns), wlan_cmd: Wlan::new(), + #[cfg(feature="tts")] + tts_cmd: Tts::new(&xns), + #[cfg(feature="benchmarks")] sha_cmd: sha, #[cfg(feature="benchmarks")] @@ -247,6 +258,9 @@ impl CmdEnv { &mut self.net_cmd, &mut self.pddb_cmd, + #[cfg(feature="tts")] + &mut self.tts_cmd, + #[cfg(feature="benchmarks")] &mut self.sha_cmd, #[cfg(feature="benchmarks")] diff --git a/services/shellchat/src/cmds/tts.rs b/services/shellchat/src/cmds/tts.rs new file mode 100644 index 000000000..b4830e7c9 --- /dev/null +++ b/services/shellchat/src/cmds/tts.rs @@ -0,0 +1,55 @@ +use crate::{ShellCmdApi, CommonEnv}; +use xous_ipc::String; +use tts_frontend::*; +use core::fmt::Write; + +#[derive(Debug)] +pub struct Tts { + pub fe: TtsFrontend, +} +impl Tts { + pub fn new(xns: &xous_names::XousNames) -> Tts { + Tts { + fe: TtsFrontend::new(xns).unwrap(), + } + } +} + +impl<'a> ShellCmdApi<'a> for Tts { + cmd_api!(tts); // inserts boilerplate for command API + + fn process(&mut self, args: String::<1024>, env: &mut CommonEnv) -> Result>, xous::Error> { + let mut ret = String::<1024>::new(); + let helpstring = "tts options: speak"; + + let mut tokens = args.as_str().unwrap().split(' '); + + if let Some(sub_cmd) = tokens.next() { + match sub_cmd { + "speak" => { + let mut text = String::<1024>::new(); + join_tokens(&mut text, &mut tokens); + self.fe.tts_simple(text.as_str().expect("not valid utf-8")).unwrap(); + } + _ => { + write!(ret, "{}", helpstring).unwrap(); + } + } + + } else { + write!(ret, "{}", helpstring).unwrap(); + } + Ok(Some(ret)) + } +} + + +fn join_tokens<'a>(buf: &mut String<1024>, tokens: impl Iterator) { + for (i, tok) in tokens.enumerate() { + if i == 0 { + write!(buf, "{}", tok).unwrap(); + } else { + write!(buf, " {}", tok).unwrap(); + } + } +} \ No newline at end of file diff --git a/services/tts/Cargo.toml b/services/tts/Cargo.toml new file mode 100644 index 000000000..ddef6cdd4 --- /dev/null +++ b/services/tts/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "tts-frontend" +version = "0.1.0" +authors = ["bunnie "] +edition = "2018" +description = "Text to speech integration server" + +# Dependency policy: fully specify dependencies to the minor version number +[dependencies] +xous = { path = "../../xous-rs" } +xous-ipc = {path="../../xous-ipc"} +log-server = { path = "../log-server" } +ticktimer-server = { path = "../ticktimer-server" } +xous-names = { path = "../xous-names" } +log = "0.4.14" +num-derive = {version = "0.3.3", default-features = false} +num-traits = {version = "0.2.14", default-features = false} +rkyv = {version = "0.4.3", default-features = false, features = ["const_generics"]} +xous-tts-backend = {path="../../../tts-backend"} + +[target.'cfg(not(any(windows,unix)))'.dependencies] +utralib = { path = "../../utralib"} + +[features] +tts = [] +default = [] diff --git a/services/tts/src/api.rs b/services/tts/src/api.rs new file mode 100644 index 000000000..202f7301f --- /dev/null +++ b/services/tts/src/api.rs @@ -0,0 +1,16 @@ +pub(crate) const SERVER_NAME_TTS: &str = "_Text to speech front end_"; + +#[derive(num_derive::FromPrimitive, num_derive::ToPrimitive, Debug)] +pub(crate) enum Opcode { + /// Basic, synchronous, non-abortable conversion of a string to audible speech + TextToSpeech, + /// Callback for the codec crate + CodecCb, + /// Exits the server + Quit, +} + +#[derive(Debug, Copy, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +pub struct TtsFrontendMsg { + pub text: xous_ipc::String::<2048>, +} \ No newline at end of file diff --git a/services/tts/src/lib.rs b/services/tts/src/lib.rs new file mode 100644 index 000000000..9ebdcf62f --- /dev/null +++ b/services/tts/src/lib.rs @@ -0,0 +1,45 @@ +#![cfg_attr(target_os = "none", no_std)] + +pub mod api; +pub use api::*; +use xous::{CID, send_message}; +use xous_ipc::Buffer; +use num_traits::ToPrimitive; + +#[derive(Debug)] +pub struct TtsFrontend { + conn: CID, +} +impl TtsFrontend { + pub fn new(xns: &xous_names::XousNames) -> Result { + REFCOUNT.fetch_add(1, Ordering::Relaxed); + let conn = xns.request_connection_blocking(api::SERVER_NAME_TTS).expect("Can't connect to TtsFrontend server"); + Ok(TtsFrontend { + conn + }) + } + /// A fully synchronous text to speech call. The text is turned into speech and played immediately, + /// with no further synchronization or ability to abort the speech playback. + pub fn tts_simple(&self, text: &str) -> Result<(), xous::Error> { + let msg = TtsFrontendMsg { + text: xous_ipc::String::from_str(text), + }; + let buf = Buffer::into_buf(msg).or(Err(xous::Error::InternalError))?; + buf.lend(self.conn, Opcode::TextToSpeech.to_u32().unwrap()).map(|_| ()) + } +} + +use core::sync::atomic::{AtomicU32, Ordering}; +static REFCOUNT: AtomicU32 = AtomicU32::new(0); +impl Drop for TtsFrontend { + fn drop(&mut self) { + // the connection to the server side must be reference counted, so that multiple instances of this object within + // a single process do not end up de-allocating the CID on other threads before they go out of scope. + // Note to future me: you want this. Don't get rid of it because you think, "nah, nobody will ever make more than one copy of this object". + if REFCOUNT.fetch_sub(1, Ordering::Relaxed) == 1 { + unsafe{xous::disconnect(self.conn).unwrap();} + } + // if there was object-specific state (such as a one-time use server for async callbacks, specific to the object instance), + // de-allocate those items here. They don't need a reference count because they are object-specific + } +} \ No newline at end of file diff --git a/services/tts/src/main.rs b/services/tts/src/main.rs new file mode 100644 index 000000000..d7f1ad2ac --- /dev/null +++ b/services/tts/src/main.rs @@ -0,0 +1,51 @@ +#![cfg_attr(target_os = "none", no_std)] +#![cfg_attr(target_os = "none", no_main)] + +mod api; +use api::*; + +use xous_ipc::Buffer; + +use num_traits::*; +use xous_tts_backend::*; + +#[xous::xous_main] +fn xmain() -> ! { + log_server::init_wait().unwrap(); + log::set_max_level(log::LevelFilter::Trace); + log::info!("my PID is {}", xous::process::id()); + + let xns = xous_names::XousNames::new().unwrap(); + let tts_sid = xns.register_name(api::SERVER_NAME_TTS, None).expect("can't register server"); + log::trace!("registered with NS -- {:?}", tts_sid); + + let tts_be = TtsBackend::new(&xns).unwrap(); + + loop { + let msg = xous::receive_message(tts_sid).unwrap(); + match FromPrimitive::from_usize(msg.body.id()) { + Some(Opcode::TextToSpeech) => { + let buffer = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) }; + let msg = buffer.to_original::().unwrap(); + log::info!("tts front end got string {}", msg.text.as_str().unwrap()); + tts_be.tts_simple(msg.text.as_str().unwrap()).unwrap(); + }, + Some(Opcode::CodecCb) => { + // tbd + }, + Some(Opcode::Quit) => { + log::warn!("Quit received, goodbye world!"); + break; + }, + None => { + log::error!("couldn't convert opcode: {:?}", msg); + } + } + } + // clean up our program + log::trace!("main loop exit, destroying servers"); + xns.unregister_server(tts_sid).unwrap(); + xous::destroy_server(tts_sid).unwrap(); + log::trace!("quitting"); + xous::terminate_process(0) +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index a53d1680d..722bee7ee 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -201,20 +201,20 @@ fn main() -> Result<(), Box> { Some("tts") => { let mut args = env::args(); args.nth(1); - //let mut pkgs = hw_pkgs.to_vec(); - let mut pkgs = gfx_dev_pkgs.to_vec(); + let mut pkgs = hw_pkgs.to_vec(); + //let mut pkgs = gfx_dev_pkgs.to_vec(); let apps: Vec = args.collect(); for app in &apps { pkgs.push(app); } - pkgs.push("tts"); + pkgs.push("tts-frontend"); generate_app_menus(&apps); build_hw_image(false, Some("./precursors/soc.svd".to_string()), &pkgs, - lkey, kkey, + None, None, Some(&["--features", "tts"]), // one thing at a time... "--features", "braille", - &[])? + &["bins/espeak-embedded"], None)? } Some("libstd-test") => { let mut args = env::args(); @@ -469,7 +469,7 @@ Various debug configurations: pddb-hosted PDDB testing in a hosted environment pddb-ci PDDB config for CI testing (eg: TRNG->deterministic for reproducible errors) ffi-test builds an image for testing C-FFI bindings and integration - + tts builds an image with text to speech support via externally linked C executable " ) }