1#![warn(clippy::all, clippy::cargo, clippy::pedantic, clippy::nursery)]
2use std::ffi::{CString, NulError};
3
4pub fn text_to_wave<'a>(
11 text: impl Into<String>,
12 voice: impl Into<String>,
13) -> Result<WaveformAudio<'a>, Error> {
14 let unsafe_wf = unsafe {
15 flite_sys::flite_init();
16 let c_text = CString::new(text.into())?.as_ptr();
17 let c_voice = flite_sys::flite_voice_select(CString::new(voice.into())?.as_ptr());
18 if c_voice.is_null() {
19 return Err(Error::NoVoice);
20 }
21 flite_sys::flite_text_to_wave(c_text, c_voice)
22 };
23 let wf = if unsafe_wf.is_null() {
24 return Err(Error::TextToWaveNull);
25 } else {
26 unsafe { *unsafe_wf }
27 };
28 let sample_count: usize = wf.num_samples.try_into().unwrap();
30 if wf.samples.is_null() {
31 return Err(Error::SamplesNull);
32 }
33 if wf.samples.is_null() {
34 return Err(Error::SamplesNull);
35 }
36 let samples = unsafe { std::slice::from_raw_parts(wf.samples, sample_count) };
37 Ok(WaveformAudio {
38 channels: wf.num_channels,
39 sample_rate: wf.sample_rate,
40 kind: unsafe { CString::from_raw(wf.type_ as *mut i8) },
41 samples,
42 })
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct WaveformAudio<'a> {
47 channels: i32,
48 sample_rate: i32,
49 kind: CString,
50 samples: &'a [i16],
51}
52
53impl WaveformAudio<'_> {
54 #[must_use]
55 pub const fn channels(&self) -> i32 {
56 self.channels
57 }
58 #[must_use]
59 pub const fn sample_rate(&self) -> i32 {
60 self.sample_rate
61 }
62 #[must_use]
63 pub const fn kind(&self) -> &CString {
64 &self.kind
65 }
66 #[must_use]
67 pub const fn samples(&self) -> &[i16] {
68 self.samples
69 }
70}
71
72#[derive(Debug)]
73pub enum Error {
74 CStringConversion(NulError),
75 NoVoice,
76 TextToWaveNull,
77 SamplesNull,
78 TypeNull,
79}
80
81impl From<NulError> for Error {
82 fn from(err: NulError) -> Self {
83 Self::CStringConversion(err)
84 }
85}
86
87impl std::fmt::Display for Error {
88 fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
89 match self {
90 Self::CStringConversion(e) => write!(f, "CString Conversion Error: {}", e),
91 Self::NoVoice => write!(f, "There was no voice with the selected name!"),
92 Self::TextToWaveNull => write!(f, "Text-to-Wave return value was NULL"),
93 Self::SamplesNull => write!(f, "Samples in waveform are NULL"),
94 Self::TypeNull => write!(f, "Type in waveform is NULL"),
95 }
96 }
97}
98
99impl std::error::Error for Error {}
100
101#[cfg(test)]
102mod test {
103 use super::*;
104 #[test]
105 #[should_panic]
106 fn invalid_voice() {
107 text_to_wave("Example text", "NAGIGADJG").unwrap();
108 }
109}