espeak_ng/lib.rs
1//! # espeak-ng-rs
2//!
3//! A pure-Rust port of [eSpeak NG](https://github.com/espeak-ng/espeak-ng)
4//! text-to-speech.
5//!
6//! The crate can be used as a **drop-in replacement** for the C library
7//! (`libespeak-ng`) from Rust code. The mapping is:
8//!
9//! | C function | Rust equivalent |
10//! |---|---|
11//! | `espeak_ng_Initialize()` | [`EspeakNg::new()`] |
12//! | `espeak_ng_SetVoiceByName()` | [`EspeakNg::set_voice()`] |
13//! | `espeak_ng_SetParameter()` | [`EspeakNg::set_parameter()`] |
14//! | `espeak_ng_Synthesize()` | [`EspeakNg::synth()`] |
15//! | `espeak_TextToPhonemes()` | [`EspeakNg::text_to_phonemes()`] |
16//! | `espeak_ng_GetSampleRate()` | [`EspeakNg::sample_rate()`] |
17//! | `espeak_ng_Terminate()` | `drop(engine)` |
18//!
19//! ## Quick start
20//!
21//! ```rust,no_run
22//! use espeak_ng::EspeakNg;
23//!
24//! // Initialise (equivalent to espeak_ng_Initialize + espeak_ng_SetVoiceByName)
25//! let engine = EspeakNg::new("en")?;
26//!
27//! // Text → IPA phonemes
28//! let ipa = engine.text_to_phonemes("hello world")?;
29//! assert_eq!(ipa, "hɛlˈəʊ wˈɜːld");
30//!
31//! // Text → 22 050 Hz PCM
32//! let (samples, rate) = engine.synth("hello world")?;
33//! assert_eq!(rate, 22050);
34//! # Ok::<(), espeak_ng::Error>(())
35//! ```
36//!
37//! ## Convenience functions
38//!
39//! For one-shot calls that don't need a persistent engine:
40//!
41//! ```rust,no_run
42//! // Text → IPA string
43//! let ipa = espeak_ng::text_to_ipa("en", "hello world")?;
44//!
45//! // Text → PCM samples + sample rate
46//! let (samples, rate) = espeak_ng::text_to_pcm("en", "hello")?;
47//! # Ok::<(), espeak_ng::Error>(())
48//! ```
49//!
50//! ## Module overview
51//!
52//! | Module | Purpose |
53//! |---|---|
54//! | (crate root) | [`EspeakNg`] engine, convenience functions |
55//! | [`encoding`] | UTF-8 / ISO-8859-* / KOI8-R / ISCII decode |
56//! | [`phoneme`] | Binary phoneme table loader and IPA scanner |
57//! | [`dictionary`] | Dictionary lookup and rule-based translation |
58//! | [`translate`] | Full text → phoneme code → IPA pipeline |
59//! | [`synthesize`] | Harmonic formant synthesizer → PCM |
60//! | [`oracle`] | C library FFI (feature = `c-oracle`) |
61
62// ---------------------------------------------------------------------------
63// Modules
64// ---------------------------------------------------------------------------
65
66pub mod encoding;
67pub mod phoneme;
68pub mod dictionary;
69pub mod translate;
70pub mod synthesize;
71pub mod error;
72pub mod engine;
73
74pub use translate::ipa_table;
75
76/// C library oracle for comparison testing and benchmarking.
77///
78/// Only compiled when the `c-oracle` feature is active.
79/// Links `libespeak-ng` via FFI and exposes the original C functions
80/// alongside equivalent Rust implementations for A/B comparison.
81#[cfg(feature = "c-oracle")]
82pub mod oracle;
83
84// ---------------------------------------------------------------------------
85// Bundled data (feature = "bundled-data")
86// ---------------------------------------------------------------------------
87
88/// Install all bundled eSpeak NG data files into `dest_dir`.
89///
90/// Only available when the `bundled-data` Cargo feature is enabled. The
91/// three data sub-crates (`espeak-ng-data-phonemes`, `espeak-ng-data-dict-ru`,
92/// `espeak-ng-data-dicts`) embed every data file at compile time; this
93/// function extracts them to a user-supplied directory so the engine can
94/// read them.
95///
96/// The call is **idempotent** — running it multiple times on the same
97/// directory is safe (files are simply overwritten).
98///
99/// # Example
100///
101/// ```rust,no_run
102/// # #[cfg(feature = "bundled-data")]
103/// # {
104/// use std::path::PathBuf;
105///
106/// let data_dir = PathBuf::from("/tmp/my-espeak-data");
107/// std::fs::create_dir_all(&data_dir).unwrap();
108/// espeak_ng::install_bundled_data(&data_dir).unwrap();
109///
110/// let engine = espeak_ng::EspeakNg::with_data_dir(&data_dir, "en").unwrap();
111/// let ipa = engine.text_to_phonemes("hello world").unwrap();
112/// # }
113/// ```
114///
115/// # Errors
116/// Returns an [`std::io::Error`] if a directory cannot be created or a
117/// file cannot be written.
118#[cfg(feature = "bundled-data")]
119pub fn install_bundled_data(dest_dir: &std::path::Path) -> std::io::Result<()> {
120 espeak_ng_data_phonemes::install(dest_dir)?;
121 espeak_ng_data_dict_ru::install(dest_dir)?;
122 espeak_ng_data_dicts::install(dest_dir)?;
123 Ok(())
124}
125
126mod bundled_data_generated;
127
128pub use bundled_data_generated::{
129 bundled_languages,
130 has_bundled_language,
131 install_bundled_language,
132 install_bundled_languages,
133 BUNDLED_LANGUAGES,
134};
135
136// ---------------------------------------------------------------------------
137// Re-exports – top-level public API
138// ---------------------------------------------------------------------------
139
140pub use error::{Error, Result};
141
142/// Text encoding enum and decoder.
143///
144/// Re-exported from [`encoding`] for convenience.
145pub use encoding::{Encoding, TextDecoder, DecodeMode};
146
147/// Phoneme data types.
148///
149/// Re-exported from [`phoneme`] for convenience.
150pub use phoneme::{PhonemeData, PhonemeTab, PhonemeFeature};
151
152/// Text translator.
153///
154/// Re-exported from [`translate`] for convenience.
155pub use translate::{Translator, PhonemeCode};
156
157/// Synthesizer and voice parameters.
158///
159/// Re-exported from [`synthesize`] for convenience.
160pub use synthesize::{Synthesizer, VoiceParams, PcmBuffer};
161
162/// Main TTS engine — drop-in replacement for the C library session.
163///
164/// Re-exported from [`engine`] for convenience.
165pub use engine::{EspeakNg, Builder, Parameter, VoiceSpec, VoiceSpecBuilder, Gender,
166 SynthEvent, EventKind, OutputMode};
167
168// ---------------------------------------------------------------------------
169// Convenience functions
170// ---------------------------------------------------------------------------
171
172/// Convert text to an IPA phoneme string.
173///
174/// One-shot convenience wrapper around [`EspeakNg::text_to_phonemes`].
175/// Equivalent to running:
176///
177/// ```shell
178/// espeak-ng -v <lang> -q --ipa "<text>"
179/// ```
180///
181/// Uses the default espeak-ng data directory (`/usr/share/espeak-ng-data`
182/// or the `ESPEAK_DATA_PATH` environment variable).
183///
184/// # Errors
185/// - [`Error::VoiceNotFound`] — the language data files are missing.
186/// - [`Error::InvalidData`] — the dictionary or phoneme tables are corrupt.
187///
188/// # Example
189/// ```rust,no_run
190/// // English
191/// assert_eq!(espeak_ng::text_to_ipa("en", "hello world")?, "hɛlˈəʊ wˈɜːld");
192/// assert_eq!(espeak_ng::text_to_ipa("en", "42")?, "fˈɔːti tˈuː");
193/// assert_eq!(espeak_ng::text_to_ipa("en", "walked")?, "wˈɔːkt");
194///
195/// // German
196/// assert_eq!(espeak_ng::text_to_ipa("de", "schön")?, "ʃˈøːn");
197///
198/// // French
199/// assert_eq!(espeak_ng::text_to_ipa("fr", "bonjour")?, "bɔ̃ʒˈuːɹ");
200/// # Ok::<(), espeak_ng::Error>(())
201/// ```
202pub fn text_to_ipa(lang: &str, text: &str) -> Result<String> {
203 let translator = Translator::new_default(lang)?;
204 translator.text_to_ipa(text)
205}
206
207/// Synthesize text to raw 16-bit PCM audio at 22 050 Hz (mono).
208///
209/// One-shot convenience wrapper around [`EspeakNg::synth`].
210/// Uses the real espeak-ng binary acoustic data (phondata / phonindex /
211/// phontab) for authentic eSpeak NG sound quality.
212///
213/// Returns `(samples, sample_rate)` where `sample_rate` is always 22 050 Hz.
214///
215/// # Errors
216/// - [`Error::VoiceNotFound`] — the phoneme data files are missing.
217/// - [`Error::InvalidData`] — the phondata binary is corrupt.
218///
219/// # Example
220/// ```rust,no_run
221/// let (samples, rate) = espeak_ng::text_to_pcm("en", "hello world")?;
222/// assert_eq!(rate, 22050);
223/// assert!(!samples.is_empty());
224/// # Ok::<(), espeak_ng::Error>(())
225/// ```
226pub fn text_to_pcm(lang: &str, text: &str) -> Result<(PcmBuffer, u32)> {
227 #[cfg(feature = "c-oracle")]
228 {
229 let (samples, rate) = crate::oracle::text_to_pcm(lang, text);
230 if !samples.is_empty() {
231 return Ok((samples, rate));
232 }
233 }
234
235 let engine = EspeakNg::new(lang)?;
236 engine.synth(text)
237}