Skip to main content

aurum_core/tts/
provider.rs

1//! TTS provider trait and shared result types.
2
3use crate::error::Result;
4use async_trait::async_trait;
5use serde::{Deserialize, Serialize};
6
7// Deserialize remains for BackendKind / options enums only; SynthesisResult is
8// Serialize-only (JOE-1614).
9
10/// Default sample rate produced by the KittenTTS nano engine.
11pub const DEFAULT_SAMPLE_RATE_HZ: u32 = 24_000;
12
13/// Backend classification for honesty JSON (JOE-1937).
14///
15/// Wire form in [`crate::dto::TtsMetaDto`] is the snake_case string
16/// (`"local"` / `"remote"`). Schema version stays at 1: `backend_kind` was
17/// already a free string in the external DTO.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(rename_all = "snake_case")]
20pub enum BackendKind {
21    /// On-device ONNX inference.
22    Local,
23    /// Remote provider over the network (normalized to mono PCM in-process).
24    Remote,
25}
26
27impl BackendKind {
28    /// Stable honesty / DTO label.
29    pub fn as_str(self) -> &'static str {
30        match self {
31            Self::Local => "local",
32            Self::Remote => "remote",
33        }
34    }
35}
36
37/// Options controlling a single synthesis request.
38#[derive(Debug, Clone)]
39pub struct SynthesisOptions {
40    pub model: String,
41    pub voice: String,
42    pub language: String,
43    /// Optional requested sample rate. Must equal the adapter native rate when set;
44    /// non-native values are rejected (no metadata-only relabeling, no resampler).
45    pub sample_rate_hz: Option<u32>,
46    /// Speaking rate multiplier (must be finite and within engine range).
47    pub speaking_rate: f32,
48    /// Wall-clock timeout in milliseconds (enforced by the local provider).
49    pub timeout_ms: u64,
50    /// Optional cooperative cancel flag.
51    pub cancel: Option<crate::cancel::CancelFlag>,
52    /// When true, never hit the network for missing voice packs.
53    pub local_only: bool,
54    /// Optional local model-pack directory (JOE-1619). When set, loads artifacts
55    /// from the pack manifest instead of the built-in catalogue cache. Requires a
56    /// known adapter; bare ONNX paths are rejected by pack load.
57    pub pack_dir: Option<std::path::PathBuf>,
58    /// Allow `local_unverified` trust for [`Self::pack_dir`] (explicit opt-in).
59    pub allow_unverified: bool,
60}
61
62impl Default for SynthesisOptions {
63    fn default() -> Self {
64        Self {
65            model: crate::tts::DEFAULT_TTS_MODEL.to_string(),
66            voice: crate::tts::DEFAULT_TTS_VOICE.to_string(),
67            language: "en".to_string(),
68            sample_rate_hz: None,
69            speaking_rate: 1.0,
70            timeout_ms: crate::tts::DEFAULT_TIMEOUT_MS,
71            cancel: None,
72            local_only: false,
73            pack_dir: None,
74            allow_unverified: false,
75        }
76    }
77}
78
79/// Normalized result returned by every TTS provider.
80///
81/// **Not** deserializable as a whole: PCM must not be reconstructed from JSON
82/// metadata (JOE-1614). Use [`crate::dto::TtsMetaDto`] for external contracts.
83#[derive(Debug, Clone, Serialize)]
84pub struct SynthesisResult {
85    /// Mono PCM samples (signed 16-bit).
86    #[serde(skip)]
87    pub pcm_i16_mono: Vec<i16>,
88    /// Actual sample rate of [`Self::pcm_i16_mono`] (always the adapter native rate).
89    pub sample_rate_hz: u32,
90    /// Always mono (1 channel) for Aurum synthesis results.
91    pub channels: u16,
92    pub backend_kind: BackendKind,
93    pub provider: String,
94    /// Canonical model id actually used.
95    pub model: String,
96    /// Canonical voice id actually used.
97    pub voice: String,
98    pub language: String,
99    /// Duration derived from final PCM length and actual sample rate.
100    pub duration_ms: u64,
101    /// Character count of the synthesized (complete) text.
102    pub text_chars: usize,
103    /// Always `false` under complete-or-error policy.
104    pub text_truncated: bool,
105    /// Number of model-safe chunks synthesized and concatenated.
106    pub chunk_count: usize,
107    /// Characters of source text actually synthesized (equals `text_chars`).
108    pub synthesized_chars: usize,
109    /// Adapter id that executed synthesis (JOE-1576).
110    #[serde(default, skip_serializing_if = "Option::is_none")]
111    pub adapter: Option<String>,
112    /// Trust mode: builtin | verified | local_unverified.
113    #[serde(default, skip_serializing_if = "Option::is_none")]
114    pub trust: Option<String>,
115    /// Provenance: builtin | custom | local_pack.
116    #[serde(default, skip_serializing_if = "Option::is_none")]
117    pub provenance: Option<String>,
118}
119
120/// Provider trait for local (and future) TTS backends.
121#[async_trait]
122pub trait SynthesisProvider: Send + Sync {
123    fn name(&self) -> &'static str;
124
125    async fn synthesize(&self, text: &str, opts: &SynthesisOptions) -> Result<SynthesisResult>;
126
127    async fn preload(&self, model: &str, voice: &str) -> Result<()>;
128}