Skip to main content

speechd_module_rs/
types.rs

1use core::error;
2use core::fmt;
3use core::mem;
4use core::result;
5use core::str;
6
7use std::borrow;
8use std::collections;
9use std::io;
10use std::path;
11
12#[cfg(any(feature = "audio", doc))]
13use crate::audio;
14#[cfg(any(feature = "logger", doc))]
15use crate::logging;
16use crate::modapi;
17
18// Re-export safe types from sys crate
19pub mod enums {
20	pub use super::modapi::{
21		MessageType as Message,
22		DataMode,
23		Priority,
24		Notification as Notifications,
25		NotificationType as Notification,
26	};
27}
28
29
30/// High-level error type
31#[derive(thiserror::Error, Debug)]
32#[non_exhaustive]
33pub enum Error {
34	/// General I/O error occurred
35	#[error("General I/O error: {0}")]
36	IO(#[from] io::Error),
37
38	/// Error while setting up or configuring logging
39	#[cfg(any(feature = "logger", doc))]
40	#[error("Logger configuration failed: {0}")]
41	LoggerError(#[from] logging::flexi::Error),
42
43	/// [`Module::config`] function returned an error
44	#[error("Module configuration loading failed: {0}")]
45	ModuleConfigFailed(#[source] Box<dyn error::Error>),
46
47	/// [`Module::init`] function returned an error
48	#[error("Module initialization failed: {0}")]
49	ModuleInitFailed(#[source] Box<dyn error::Error>),
50
51	/// [`Module::list_voices`] function returned an error
52	#[error("Module LIST VOICES failed: {0}")]
53	ModuleListVoicesFailed(#[source] Box<dyn error::Error>),
54
55	/// [`Module::set_param`] function returned an error
56	#[error("Module SET failed: {0}")]
57	ModuleSetParamFailed(#[source] Box<dyn error::Error>),
58	
59	/// [`Module::speak`] or [`Module::speak_sync`] function returned an error
60	#[error("Module SPEAK failed: {0}")]
61	ModuleSpeakFailed(#[source] Box<dyn error::Error>),
62
63	/// [`Module::loglevel_set`] function returned an error
64	#[cfg(any(not(feature = "logger"), doc))]
65	#[error("Module LOGLEVEL value update failed: {0}")]
66	ModuleLoglevelSetFailed(#[source] Box<dyn error::Error>),
67
68	/// [`Module::debug`] function returned an error
69	#[cfg(any(not(feature = "logger"), doc))]
70	#[error("Module DEBUG update failed: {0}")]
71	ModuleDebugFailed(#[source] Box<dyn error::Error>),
72	
73	/// Server did not send INIT message
74	#[error("Server did not send INIT as first message")]
75	ProtoNoInit,
76
77	/// Error when parsing a malformed module property
78	///
79	/// Parameter will be [`None`] if a bad string on a string property was
80	/// encountered, otherwise the numeric parsing error will be provided.
81	#[error("Server sent malformed SET parameter {typ} value for “{0}”: {msg}",
82	        typ = if .1.is_some() { "numeric" } else { "string" },
83	        msg = if let Some(ref inner) = *.1 {
84	            borrow::Cow::from(inner.to_string())
85	        } else {
86	            borrow::Cow::from("Invalid value")
87	        })]
88	ProtoParamMalformed(String, #[source] Option<bounded_integer::ParseError>),
89
90	/// An unknown name was supplied to `loglevel_set`
91	///
92	/// (The only protocol allowed param is “`log_level`”.)
93	#[error("Received unknown LOGLEVEL parameter update: {0}")]
94	UnknownLoglevelParam(String),
95
96	/// An unknown name was supplied to `set_param`
97	#[error("Received unknown SET parameter update: {0}")]
98	UnknownParam(String),
99}
100
101impl From<io::ErrorKind> for Error {
102	fn from(kind: io::ErrorKind) -> Self {
103		Self::IO(kind.into())
104	}
105}
106
107
108/// Helper type for returning errors as strings
109///
110/// Provided as a service for modules wishing to avoid the need for structural
111/// error types from returning from their [`Module`] implementation functions.
112///
113/// # Example
114///
115/// ```
116/// # use result::Result;
117/// #
118/// # struct MyModule { is_ok: bool };
119/// #
120/// # trait Module {  // Mini-version of actual trait for demostration purposes
121/// #     type Error: error::Error + 'static;
122/// #     fn init(&mut self) -> Result<(), Self::Error>;
123/// # }
124/// #
125/// impl Module for MyModule {
126///     type Error = StringError;
127///
128///     fn init(&mut self) -> Result<(), Self::Error> {
129///         if self.is_ok {
130///             OK(())
131///         } else {
132///             Err(format!("It went really wrong").into())
133///         }
134///     }
135///
136///     // Other methods
137/// }
138#[derive(Debug, Clone, PartialEq, Eq)]
139pub struct StringError(String);
140
141impl fmt::Display for StringError {
142	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143		fmt::Display::fmt(&self.0, f)
144	}
145}
146
147impl error::Error for StringError {}
148
149
150
151pub type Result<T> = result::Result<T, Error>;
152
153
154#[repr(transparent)]
155#[derive(Debug, Default, PartialEq, Eq)]
156pub struct MessageSettings(modapi::MsgSettings);
157
158
159#[repr(transparent)]
160#[derive(Debug, Default, PartialEq, Eq)]
161pub struct Voice(modapi::Voice);
162
163#[derive(Debug, PartialEq, Eq)]
164pub struct Voices(mbox::MArray<Option<mbox::MBox<Voice>>>);
165
166impl Voices {
167	#[must_use]
168	pub fn take_slice(items: &mut [Voice]) -> Self {
169		// Create MBox’d slice of [Voice] pointers
170		let mut ptrs = mbox::MBox::<[Option<mbox::MBox<Voice>>]>::new_uninit_slice(items.len() + 1);
171
172		// Move each item from `items` to new pounter slice and required add
173		// end-of-list sentinel
174		for (idx, item) in items.iter_mut().enumerate() {
175			ptrs[idx] = mem::MaybeUninit::new(Some(mbox::MBox::new(mem::take(item))));
176		}
177		ptrs[items.len()] = mem::MaybeUninit::new(None);
178
179		// All initialized, can assume init and morph to sentinel-terminated array
180		Voices(
181			unsafe { mbox::MArray::from_raw(ptrs.assume_init().into_raw_parts().0) },
182		)
183	}
184
185	#[must_use]
186	pub fn as_ptr(&self) -> *const *const modapi::Voice {
187		self.0.as_ptr().cast::<*const modapi::Voice>()
188	}
189
190	#[must_use]
191	pub fn into_raw(self) -> *const *const modapi::Voice {
192		let Voices(inner) = self;
193		mbox::MBox::into_raw(inner.into_mbox()) as *const *const modapi::Voice
194	}
195}
196
197
198pub mod params {
199	bounded_integer::bounded_integer! {
200		/// Voice speaking rate
201		///
202		/// The value `-100` represents the slowest supported speaking rate,
203		/// `0` the default rate representing normal speech flow and `100`
204		/// the fastest supported speaking rate.
205		pub struct Rate(-100, 100);
206	}
207	bounded_integer::bounded_integer! {
208		/// Voice baseline pitch
209		///
210		/// The value `-100` represents the lowest supported voice pitch,
211		/// `0` the default voice pitch level  and `100` the highest supported
212		/// voice pitch.
213		pub struct Pitch(-100, 100);
214	}
215	bounded_integer::bounded_integer! {
216		/// Voice pitch variation range
217		///
218		/// The value `-100` represents an almost completely monotone voice,
219		/// `0` the default pitch variantion range and `100` the most dynamic
220		/// voicing pattern.
221		pub struct PitchRange(-100, 100);
222	}
223	bounded_integer::bounded_integer! {
224		/// Voice volume or intensity
225		///
226		/// The value `-100` represents an almost silent (perhaps whispering)
227		/// voice, `0` the default voice intensity and `100` the loudest
228		/// (perhaps screaming) voice.
229		pub struct Volume(-100, 100);
230	}
231
232	pub use super::modapi::{
233		CapitalLetters,
234		Punctuation,
235		Spelling,
236		VoiceType,
237	};
238}
239
240/// Enumeration of all standard speech dispatcher properties and an other field
241/// for custom properties
242///
243/// See the section 4.1.6 (“Parameter Settings Commands”) of the
244/// speech-dispatcher manual for a list of client-side functions corresponding
245/// alongside their meanings.
246#[derive(Debug, Clone, PartialEq, Eq)]
247pub enum Param {
248	/// Speaking rate
249	Rate(params::Rate),
250
251	/// Voice baseline pitch
252	Pitch(params::Pitch),
253
254	/// Voice pitch variation range
255	PitchRange(params::PitchRange),
256	
257	/// Voice volume or intensity
258	Volume(params::Volume),
259	
260	/// Punctuation verbosity mode
261	PunctuationMode(params::Punctuation),
262
263	/// Spelling mode enabled
264	SpellingMode(params::Spelling),
265
266	/// Capital letter recognition
267	CapLetRecogn(params::CapitalLetters),
268
269	/// Symbolic voice type
270	VoiceType(params::VoiceType),
271
272	/// Synthesis voice name
273	///
274	/// If received from the server this should generally match one of the
275	/// names returned by your [`Module::list_voices`] function. You are not
276	/// obliged to accept unknown voice names, but may fall back to any default
277	/// choice of your choosing in such case.
278	VoiceName(String),
279
280	/// Input text language
281	///
282	/// Semantics match those of [`Param::VoiceName`], each voice name and
283	/// language pair should be unique. Output modules should avoid
284	/// synthesizing in the wrong language though.
285	Language(String),
286
287	/// Other value, generally you can reject these
288	Other(String, String),
289}
290
291impl Param {
292	pub fn from_kv(name: &str, value: &str) -> Result<Self> {
293		fn int_from<T: str::FromStr<Err=bounded_integer::ParseError>>(name: &str, value: &str) -> Result<T> {
294			T::from_str(value).map_err(|err| Error::ProtoParamMalformed(name.to_owned(), Some(err)))
295		}
296		
297		fn itm_from<'a, T: TryFrom<&'a str>>(name: &str, value: &'a str) -> Result<T> {
298			T::try_from(value).or(Err(Error::ProtoParamMalformed(name.to_owned(), None)))
299		}
300	
301		Ok(match name {
302			"rate" => Self::Rate(int_from::<params::Rate>(name, value)?),
303			"pitch" => Self::Pitch(int_from::<params::Pitch>(name, value)?),
304			"pitch_range" => Self::PitchRange(int_from::<params::PitchRange>(name, value)?),
305			"volume" => Self::Volume(int_from::<params::Volume>(name, value)?),
306			
307			"punctuation_mode" => Self::PunctuationMode(itm_from::<params::Punctuation>(name, value)?),
308			"spelling_mode" => Self::SpellingMode(itm_from::<params::Spelling>(name, value)?),
309			"cap_let_recogn" => Self::CapLetRecogn(itm_from::<params::CapitalLetters>(name, value)?),
310			"voice" => Self::VoiceType(itm_from::<params::VoiceType>(name, value)?),
311
312			"synthesis_voice" => Self::VoiceName(value.to_owned()),
313			"language" => Self::Language(value.to_owned()),
314
315			_ => Self::Other(name.to_owned(), value.to_owned()),
316		})
317	}
318}
319
320
321/// Module parameter storage
322#[non_exhaustive]
323#[derive(Debug, Default, Clone, PartialEq, Eq)]
324pub struct Params {
325	/// Speaking rate
326	pub rate: params::Rate,
327
328	/// Voice baseline pitch
329	pub pitch: params::Pitch,
330
331	/// Voice pitch variation range
332	pub pitch_range: params::PitchRange,
333
334	/// Voice volume or intensity
335	pub volume: params::Volume,
336
337	/// Punctuation verbosity mode
338	pub punctuation_mode: params::Punctuation,
339
340	/// Spelling mode enabled
341	pub spelling_mode: params::Spelling,
342
343	/// Capital letter recognition
344	pub cap_let_recogn: params::CapitalLetters,
345
346	/// Symbolic voice type
347	pub voice_type: params::VoiceType,
348
349	/// Synthesis voice name
350	pub voice_name: String,
351
352	/// Input text language
353	pub language: String,
354
355	/// Accepted custom parameters
356	pub custom: collections::HashMap<String, String>,
357	
358}
359
360
361pub trait Module {
362	type Error: error::Error + 'static;
363	
364	/// Called on startup
365	fn config(&mut self, config_path: Option<&path::Path>) -> result::Result<(), Self::Error>;
366
367	/// Called after server sends INIT
368	fn init(&mut self) -> result::Result<String, Self::Error>;
369
370	/// List available voices
371	fn list_voices(&mut self) -> result::Result<Voices, Self::Error>;
372
373	/// Update speech parameter
374	///
375	/// See the supplied [`Param`] struct for a list of all recognized
376	/// parameters and their meaning.
377	///
378	/// If you need a storage container for received parameters consider using
379	/// the [`Params`] type in your code, otherwise you can also directly
380	/// update whatever internal state your code associates with the given
381	/// parameter without storing an extra copy. Parameters are only ever
382	/// written by the speech-dispatcher server, never read.
383	///
384	/// Return [`Result::Err`] to indicate an internal error (will be logged),
385	/// otherwise return an boolean indicating whether the given parameter is
386	/// supported by your output module or not.
387	fn set_param(&mut self, param: Param) -> result::Result<bool, Self::Error>;
388
389	#[cfg(any(feature = "async", doc))]
390	/// Queue asynchronous speech
391	///
392	/// Your module should only check that it can speak the given utterance and
393	/// pass queue it for asynchronous processing in this function, not
394	/// generate the full utterance in this function. Disable the `async`
395	/// feature if you prefer synchronous processing.
396	///
397	/// You do not have to call [`modapi::module_speak_ok`] or
398	/// [`modapi::module_speak_error`] yourself, the framework will do so if you
399	/// return an error response.
400	fn speak(&mut self, utterance: &str, msg_type: enums::Message) -> result::Result<(), Self::Error>;
401
402	#[cfg(any(not(feature = "async"), doc))]
403	/// Perform synchronous speech
404	///
405	/// Your module should call [`notify_speak_ok`](crate::notify_speak_ok) after
406	/// ensuring that it can speak the given utterance, but before doing any
407	/// resource-intensive work to actually generate the spoken word.
408	///
409	/// It is recommended that module try to implement asynchorous background
410	/// processing and enable the `async` feature if possible.
411	///
412	/// You do not have to call [`modapi::module_speak_error`] yourself, the
413	/// framework will do so if you return an error response.
414	///
415	/// TODO: Pass a callback struct as parameter to report speak_ok/stop/pause/index_mark
416	/// TODO: Also let users call `module_process` here and somehow return async events maybe?
417	fn speak_sync(&mut self, utterance: &str, msg_type: enums::Message) -> result::Result<(), Self::Error>;
418
419	/// Pause
420	///
421	/// TODO: Document semantics, really want this to not part of sync and instead return those from process
422	fn pause(&mut self) -> result::Result<(), Self::Error>;
423
424	/// Stop
425	///
426	/// TODO: Document semantics, really want this to not part of sync and instead return those from process
427	fn stop(&mut self) -> result::Result<(), Self::Error>;
428
429	/// Retrieve audio context object
430	///
431	/// When implementing this, simply make sure you create and store an
432	/// instance of [`audio::Context`] in your implementation and return it from
433	/// this callback.
434	#[cfg(any(feature = "audio", doc))]
435	fn audio_ctx(&mut self) -> &mut audio::Context;
436
437	/// Set the output verbosity level
438	///
439	/// A `level` or 0 means “no output”, while a level of 5 means
440	/// maximum/“trace” level verbosity, see section 2.4.7 (“Log Levels”) of
441	/// the speech-dispatcher manual for the list of standard logging levels.
442	///
443	/// **Note**: As an alternatice, there is an integrated logging framework
444	/// based on `flexi_logger` available when enabling the `logger` feature.
445	#[cfg(any(not(feature = "logger"), doc))]
446	fn loglevel_set(&mut self, level: bounded_integer::BoundedU8<0, 5>) -> result::Result<(), Self::Error>;
447
448	/// Enable or disable debug logging into the given file
449	///
450	/// If `path` is given, log to the given path, otherwise disable debug
451	/// logging.
452	///
453	/// **Note**: As an alternatice, there is an integrated logging framework
454	/// based on `flexi_logger` available when enabling the `logger` feature.
455	#[cfg(any(not(feature = "logger"), doc))]
456	fn debug(&mut self, path: Option<&path::Path>) -> result::Result<(), Self::Error>;
457}
458
459
460//let module: Module<E> where E: error::Error;
461//let result: result::Result<(), Box<dyn error::Error>> = module.init().map_error(Box::new);
462
463/// Wrapping type for `Box<dyn error::Error>`
464///
465/// For reasons bordering on insanity `Box<dyn error::Error>` does not actually
466/// implement [`error::Error`] since the compiler apparently gets confused by
467/// the [`error::Error::source`] method and hence considers `dyn error::Error`
468/// to *not* implement `error::Error` – no I don’t know what that even means…
469///
470/// In any case the compiler will also give the worst possible error message
471/// for this and state that `dyn Error` is not [`Sized`], which is true but
472/// also completely besides the point…
473#[derive(Debug)]
474pub(super) struct ErrorBoxWrapper(Box<dyn error::Error>);
475
476impl ErrorBoxWrapper {
477	fn new<E: error::Error + 'static>(err: E) -> Self {
478		Self(Box::new(err))
479	}
480
481	pub(super) fn into_inner(self) -> Box<dyn error::Error> {
482		self.0
483	}
484}
485
486impl fmt::Display for ErrorBoxWrapper {
487	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
488		fmt::Display::fmt(&self.0, f)
489	}
490}
491
492impl error::Error for ErrorBoxWrapper {
493	fn source(&self) -> Option<&(dyn error::Error + 'static)> { self.0.source() }
494}
495
496
497/// Wrapper type to transparently box returned [`error::Error`] instances
498pub(crate) struct ModuleWrapper<M: Module>(pub M);
499
500impl<M: Module> Module for ModuleWrapper<M> {
501	type Error = ErrorBoxWrapper;
502	
503	fn config(&mut self, config_path: Option<&path::Path>) -> result::Result<(), Self::Error> {
504		self.0.config(config_path).map_err(Self::Error::new)
505	}
506
507	fn init(&mut self) -> result::Result<String, Self::Error> {
508		self.0.init().map_err(Self::Error::new)
509	}
510
511	fn list_voices(&mut self) -> result::Result<Voices, Self::Error> {
512		self.0.list_voices().map_err(Self::Error::new)
513	}
514
515	fn set_param(&mut self, param: Param) -> result::Result<bool, Self::Error> {
516		self.0.set_param(param).map_err(Self::Error::new)
517	}
518
519	#[cfg(any(feature = "async", doc))]
520	fn speak(&mut self, utterance: &str, msg_type: enums::Message) -> result::Result<(), Self::Error> {
521		self.0.speak(utterance, msg_type).map_err(Self::Error::new)
522	}
523
524	#[cfg(any(not(feature = "async"), doc))]
525	fn speak_sync(&mut self, utterance: &str, msg_type: enums::Message) -> result::Result<(), Self::Error> {
526		self.0.speak_sync(utterance, msg_type).map_err(Self::Error::new)
527	}
528	fn pause(&mut self) -> result::Result<(), Self::Error> {
529		self.0.pause().map_err(Self::Error::new)
530	}
531
532	fn stop(&mut self) -> result::Result<(), Self::Error> {
533		self.0.stop().map_err(Self::Error::new)
534	}
535
536	#[cfg(any(feature = "audio", doc))]
537	fn audio_ctx(&mut self) -> &mut audio::Context {
538		self.0.audio_ctx()
539	}
540
541	#[cfg(any(not(feature = "logger"), doc))]
542	fn loglevel_set(&mut self, level: bounded_integer::BoundedU8<0, 5>) -> result::Result<(), Self::Error> {
543		self.0.loglevel_set(level).map_err(Self::Error::new)
544	}
545
546	#[cfg(any(not(feature = "logger"), doc))]
547	fn debug(&mut self, path: Option<&path::Path>) -> result::Result<(), Self::Error> {
548		self.0.debug(path).map_err(Self::Error::new)
549	}
550}