mod glue;
use glue::{make_cstring, PicoString};
use std::cell::RefCell;
use std::rc::Rc;
use std::{ffi, fmt};
use ttspico_sys as native;
#[derive(Debug, PartialEq, Eq)]
pub struct PicoError {
pub code: native::pico_Status,
pub descr: String,
}
impl fmt::Display for PicoError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} (error {})", self.descr, self.code)
}
}
#[derive(Debug)]
pub struct System {
c_sys: native::pico_System,
mem: *mut u8,
mem_layout: std::alloc::Layout,
}
impl System {
unsafe fn get_error(&self, code: native::pico_Status) -> Result<(), PicoError> {
if code == native::PICO_OK {
Ok(())
} else {
let mut c_str = PicoString::new(native::PICO_RETSTRINGSIZE);
native::pico_getSystemStatusMessage(self.c_sys, code, c_str.as_mut_ptr());
Err(PicoError {
code,
descr: match c_str.to_str() {
Ok(pico_msg) => pico_msg.to_string(),
Err(utf8_err) => format!("[invalid Pico message: {}]", utf8_err),
},
})
}
}
pub fn new(memsz: usize) -> Result<Rc<RefCell<System>>, PicoError> {
unsafe {
let mem_layout = std::alloc::Layout::from_size_align(memsz, 16).unwrap();
let mut ret = System {
c_sys: std::ptr::null_mut(),
mem: std::alloc::alloc(mem_layout),
mem_layout,
};
let c_code = native::pico_initialize(
ret.mem as *mut std::os::raw::c_void,
ret.mem_layout.size() as native::pico_Uint32,
&mut ret.c_sys,
);
match ret.get_error(c_code) {
Ok(_) => Ok(Rc::new(RefCell::new(ret))),
Err(err) => Err(err),
}
}
}
pub fn load_resource(
sys: Rc<RefCell<Self>>,
path: impl AsRef<str>,
) -> Result<Rc<RefCell<Resource>>, PicoError> {
let c_path = make_cstring(path, "Invalid resource name")?;
unsafe {
let mut c_res = std::ptr::null_mut::<native::pico_resource>();
sys.borrow().get_error(native::pico_loadResource(
sys.borrow().c_sys,
c_path.as_ptr() as *const native::pico_Char,
&mut c_res,
))?;
let mut c_name = PicoString::new(native::PICO_MAX_RESOURCE_NAME_SIZE);
sys.borrow().get_error(native::pico_getResourceName(
sys.borrow().c_sys,
c_res,
c_name.as_mut_ptr(),
))?;
Ok(Rc::new(RefCell::new(Resource { sys, c_res, c_name })))
}
}
pub fn create_voice<'a>(
sys: Rc<RefCell<Self>>,
name: impl AsRef<str>,
) -> Result<Rc<RefCell<Voice>>, PicoError> {
let c_name = make_cstring(name, "Invalid voice name")?;
unsafe {
sys.borrow().get_error(native::pico_createVoiceDefinition(
sys.borrow().c_sys,
c_name.as_ptr() as *const native::pico_Char,
))?;
}
Ok(Rc::new(RefCell::new(Voice {
sys,
c_name,
resources: Vec::new(),
})))
}
}
impl Drop for System {
fn drop(&mut self) {
unsafe {
if !self.c_sys.is_null() {
native::pico_terminate(&mut self.c_sys);
}
if !self.mem.is_null() {
std::alloc::dealloc(self.mem, self.mem_layout);
self.mem = std::ptr::null_mut();
}
}
}
}
impl PartialEq for System {
fn eq(&self, other: &Self) -> bool {
self.c_sys == other.c_sys
}
}
impl Eq for System {}
unsafe impl Send for System {}
#[derive(Debug)]
pub struct Resource {
sys: Rc<RefCell<System>>,
c_res: native::pico_Resource,
c_name: PicoString,
}
impl Resource {
pub fn sys(&self) -> Rc<RefCell<System>> {
self.sys.clone()
}
pub fn name(&self) -> Result<&str, std::str::Utf8Error> {
self.c_name.to_str()
}
}
impl Drop for Resource {
fn drop(&mut self) {
unsafe {
if !self.c_res.is_null() {
let _ = native::pico_unloadResource(self.sys.borrow().c_sys, &mut self.c_res);
}
}
}
}
impl PartialEq for Resource {
fn eq(&self, other: &Self) -> bool {
self.sys == other.sys && self.c_res == other.c_res
}
}
impl Eq for Resource {}
unsafe impl Send for Resource {}
#[derive(Debug)]
pub struct Voice {
sys: Rc<RefCell<System>>,
c_name: ffi::CString,
resources: Vec<Rc<RefCell<Resource>>>,
}
impl Voice {
pub fn sys(&self) -> Rc<RefCell<System>> {
self.sys.clone()
}
pub fn name(&self) -> Result<&str, std::str::Utf8Error> {
self.c_name.to_str()
}
pub fn add_resource(&mut self, resource: Rc<RefCell<Resource>>) -> Result<(), PicoError> {
let err_code = unsafe {
let c_code = native::pico_addResourceToVoiceDefinition(
self.sys.borrow().c_sys,
self.c_name.as_ptr() as *const native::pico_Char,
resource.borrow().c_name.as_ptr() as *const native::pico_Char,
);
self.sys.borrow().get_error(c_code)
};
match err_code {
Ok(_) => {
self.resources.push(resource);
err_code
}
_ => err_code,
}
}
pub unsafe fn create_engine(voice: Rc<RefCell<Voice>>) -> Result<Engine, PicoError> {
let mut c_engine = std::ptr::null_mut::<native::pico_engine>();
voice
.borrow()
.sys
.borrow()
.get_error(native::pico_newEngine(
voice.borrow().sys.borrow().c_sys,
voice.borrow().c_name.as_ptr() as *const native::pico_Char,
&mut c_engine,
))?;
Ok(Engine { voice, c_engine })
}
}
impl Drop for Voice {
fn drop(&mut self) {
unsafe {
let _ = native::pico_releaseVoiceDefinition(
self.sys.borrow().c_sys,
self.c_name.as_ptr() as *const native::pico_Char,
);
}
}
}
impl PartialEq for Voice {
fn eq(&self, other: &Self) -> bool {
self.sys == other.sys && self.c_name == other.c_name
}
}
impl Eq for Voice {}
unsafe impl Send for Voice {}
#[derive(Debug)]
pub struct Engine {
voice: Rc<RefCell<Voice>>,
c_engine: native::pico_Engine,
}
#[derive(Debug, PartialEq, Eq)]
#[repr(i32)]
pub enum EngineStatus {
Idle = native::PICO_STEP_IDLE,
Busy = native::PICO_STEP_BUSY,
}
#[derive(Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum EngineResetMode {
Full = native::PICO_RESET_FULL,
Soft = native::PICO_RESET_SOFT,
}
impl Engine {
unsafe fn get_error(&self, code: native::pico_Status) -> Result<(), PicoError> {
if code == native::PICO_OK {
Ok(())
} else {
let mut c_str = PicoString::new(native::PICO_RETSTRINGSIZE);
native::pico_getEngineStatusMessage(self.c_engine, code, c_str.as_mut_ptr());
Err(PicoError {
code,
descr: match c_str.to_str() {
Ok(pico_msg) => pico_msg.to_string(),
Err(utf8_err) => format!("[invalid Pico message: {}]", utf8_err),
},
})
}
}
pub fn put_text(&mut self, utf8_text: impl AsRef<[u8]>) -> Result<usize, PicoError> {
let buf_size = std::cmp::min(utf8_text.as_ref().len(), native::PICO_INT16_MAX as usize);
let mut bytes_put: i16 = 0;
unsafe {
self.get_error(native::pico_putTextUtf8(
self.c_engine,
utf8_text.as_ref().as_ptr() as *const native::pico_Char,
buf_size as i16,
&mut bytes_put,
))?;
}
Ok(bytes_put as usize)
}
pub fn flush(&mut self) -> Result<usize, PicoError> {
self.put_text(b"\0")
}
pub fn reset(&mut self, mode: EngineResetMode) -> Result<(), PicoError> {
unsafe {
self.get_error(native::pico_resetEngine(
self.c_engine,
mode as native::pico_Int32,
))
}
}
pub fn get_data(
&mut self,
mut buf: impl AsMut<[i16]>,
) -> Result<(usize, EngineStatus), PicoError> {
let c_buf = buf.as_mut().as_mut_ptr() as *mut std::os::raw::c_void;
let max_size = buf.as_mut().len() * std::mem::size_of::<i16>();
let max_size_i16 =
std::cmp::min(max_size, native::PICO_INT16_MAX as usize) as native::pico_Int16;
let mut written_size: native::pico_Int16 = 0;
let mut written_dtype: native::pico_Int16 = 0;
unsafe {
let c_code = native::pico_getData(
self.c_engine,
c_buf,
max_size_i16,
&mut written_size,
&mut written_dtype,
);
assert_eq!(written_dtype, native::PICO_DATA_PCM_16BIT);
let n_written = (written_size as usize) / std::mem::size_of::<i16>();
match c_code {
native::PICO_STEP_BUSY => Ok((n_written, EngineStatus::Busy)),
native::PICO_STEP_IDLE => Ok((n_written, EngineStatus::Idle)),
err_code => Err(self
.voice
.borrow()
.sys
.borrow()
.get_error(err_code)
.unwrap_err()),
}
}
}
}
impl PartialEq for Engine {
fn eq(&self, other: &Self) -> bool {
self.voice == other.voice && self.c_engine == other.c_engine
}
}
impl Eq for Engine {}
unsafe impl Send for Engine {}