Skip to main content

transcribe_cpp/
family.rs

1//! Typed family-extension options.
2//!
3//! Each family exposes model-specific knobs via a kind-tagged extension struct
4//! (`include/transcribe/<family>.h`). These typed options mirror those structs:
5//! every field is an `Option`, and only the fields you set override the
6//! defaults the C `*_init()` stamps. A [`RunExtension`] attaches to
7//! [`RunOptions`](crate::RunOptions); a [`StreamExtension`] attaches to
8//! [`StreamOptions`](crate::StreamOptions).
9//!
10//! Probe [`Model::accepts_ext`](crate::Model::accepts_ext) to learn whether a
11//! loaded model accepts a given kind on a slot; an unaccepted extension is
12//! rejected by `run`/`stream` with [`Error::InvalidArgument`](crate::Error).
13
14use std::ffi::CString;
15
16use transcribe_cpp_sys as sys;
17
18use crate::error::Result;
19
20/// Whisper run-extension knobs (run slot): initial prompt, temperature
21/// fallback, and decode thresholds. `None` keeps the family default.
22/// `initial_prompt` cannot be combined with `RunOptions::vocabulary` / `prompt`.
23#[derive(Debug, Clone, Default, PartialEq)]
24#[cfg_attr(
25    feature = "serde",
26    derive(serde::Serialize, serde::Deserialize),
27    serde(default)
28)]
29pub struct WhisperRunOptions {
30    pub initial_prompt: Option<String>,
31    pub condition_on_prev_tokens: Option<bool>,
32    pub temperature: Option<f32>,
33    pub temperature_inc: Option<f32>,
34    pub compression_ratio_thold: Option<f32>,
35    pub logprob_thold: Option<f32>,
36    pub no_speech_thold: Option<f32>,
37    pub max_prev_context_tokens: Option<i32>,
38    pub seed: Option<u32>,
39    pub max_initial_timestamp: Option<f32>,
40}
41
42/// Moonshine-streaming stream-extension knobs (stream slot).
43#[derive(Debug, Clone, Default, PartialEq, Eq)]
44#[cfg_attr(
45    feature = "serde",
46    derive(serde::Serialize, serde::Deserialize),
47    serde(default)
48)]
49pub struct MoonshineStreamingOptions {
50    pub min_decode_interval_ms: Option<i32>,
51}
52
53/// Parakeet cache-aware streaming knobs (stream slot).
54#[derive(Debug, Clone, Default, PartialEq, Eq)]
55#[cfg_attr(
56    feature = "serde",
57    derive(serde::Serialize, serde::Deserialize),
58    serde(default)
59)]
60pub struct ParakeetStreamOptions {
61    pub att_context_right: Option<i32>,
62}
63
64/// Parakeet chunked-attention buffered streaming knobs (stream slot).
65#[derive(Debug, Clone, Default, PartialEq, Eq)]
66#[cfg_attr(
67    feature = "serde",
68    derive(serde::Serialize, serde::Deserialize),
69    serde(default)
70)]
71pub struct ParakeetBufferedStreamOptions {
72    pub left_ms: Option<i32>,
73    pub chunk_ms: Option<i32>,
74    pub right_ms: Option<i32>,
75}
76
77/// Voxtral-realtime streaming knobs (stream slot).
78#[derive(Debug, Clone, Default, PartialEq, Eq)]
79#[cfg_attr(
80    feature = "serde",
81    derive(serde::Serialize, serde::Deserialize),
82    serde(default)
83)]
84pub struct VoxtralRealtimeStreamOptions {
85    pub num_delay_tokens: Option<i32>,
86    pub min_decode_interval_ms: Option<i32>,
87}
88
89/// Sortformer streaming operating point (latency / accuracy trade-off).
90/// The menu is discrete (jointly-tuned bundles), not a latency dial;
91/// `Default` keeps the GGUF-shipped checkpoint configuration.
92#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
93#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
94pub enum SortformerPreset {
95    #[default]
96    Default,
97    /// ~30.4 s algorithmic lookahead; the offline-file operating point.
98    VeryHighLatency,
99    /// ~10.0 s lookahead.
100    HighLatency,
101    /// ~1.04 s lookahead; the real-time point (compute-heavy per audio
102    /// second — many small windows).
103    LowLatency,
104}
105
106impl SortformerPreset {
107    fn to_sys(self) -> sys::transcribe_sortformer_preset {
108        match self {
109            SortformerPreset::Default => {
110                sys::transcribe_sortformer_preset::TRANSCRIBE_SORTFORMER_PRESET_DEFAULT
111            }
112            SortformerPreset::VeryHighLatency => {
113                sys::transcribe_sortformer_preset::TRANSCRIBE_SORTFORMER_PRESET_VERY_HIGH_LATENCY
114            }
115            SortformerPreset::HighLatency => {
116                sys::transcribe_sortformer_preset::TRANSCRIBE_SORTFORMER_PRESET_HIGH_LATENCY
117            }
118            SortformerPreset::LowLatency => {
119                sys::transcribe_sortformer_preset::TRANSCRIBE_SORTFORMER_PRESET_LOW_LATENCY
120            }
121        }
122    }
123}
124
125/// Sortformer diarizer run-extension knobs (run slot). Sortformer produces
126/// speaker segments, no text; read results via the speaker-segment
127/// accessors. `None` keeps the family default (the GGUF-shipped cfg).
128#[derive(Debug, Clone, Default, PartialEq, Eq)]
129#[cfg_attr(
130    feature = "serde",
131    derive(serde::Serialize, serde::Deserialize),
132    serde(default)
133)]
134pub struct SortformerStreamOptions {
135    pub preset: Option<SortformerPreset>,
136}
137
138/// A family extension for the run slot (offline `run`/`run_batch`).
139#[derive(Debug, Clone, PartialEq)]
140#[non_exhaustive]
141#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
142pub enum RunExtension {
143    Whisper(WhisperRunOptions),
144    Sortformer(SortformerStreamOptions),
145}
146
147/// A family extension for the stream slot.
148#[derive(Debug, Clone, PartialEq, Eq)]
149#[non_exhaustive]
150#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
151pub enum StreamExtension {
152    ParakeetStream(ParakeetStreamOptions),
153    ParakeetBuffered(ParakeetBufferedStreamOptions),
154    MoonshineStreaming(MoonshineStreamingOptions),
155    VoxtralRealtime(VoxtralRealtimeStreamOptions),
156}
157
158/// Owns a materialized run-slot C extension struct (and any strings it points
159/// at) so its address — and the `transcribe_ext` pointer handed to the C API —
160/// stays valid for the duration of the native call. Boxed for a stable address
161/// across moves of the holder.
162pub(crate) enum RunExtRaw {
163    Whisper {
164        ext: Box<sys::transcribe_whisper_run_ext>,
165        _prompt: Option<CString>,
166    },
167    Sortformer(Box<sys::transcribe_sortformer_stream_ext>),
168}
169
170impl RunExtRaw {
171    pub(crate) fn ext_ptr(&self) -> *const sys::transcribe_ext {
172        match self {
173            // `ext` is field 0, so &ext == &the family struct.
174            RunExtRaw::Whisper { ext, .. } => {
175                (&**ext) as *const sys::transcribe_whisper_run_ext as *const sys::transcribe_ext
176            }
177            RunExtRaw::Sortformer(e) => {
178                (&**e) as *const sys::transcribe_sortformer_stream_ext as *const sys::transcribe_ext
179            }
180        }
181    }
182}
183
184impl RunExtension {
185    pub(crate) fn materialize(&self) -> Result<RunExtRaw> {
186        match self {
187            RunExtension::Whisper(o) => {
188                let mut ext: sys::transcribe_whisper_run_ext = unsafe { std::mem::zeroed() };
189                unsafe { sys::transcribe_whisper_run_ext_init(&mut ext) };
190                // Propagate an interior NUL as Error::Nul (like language /
191                // target_language) instead of silently dropping the prompt.
192                let prompt = o.initial_prompt.as_deref().map(CString::new).transpose()?;
193                if let Some(c) = prompt.as_ref() {
194                    ext.initial_prompt = c.as_ptr();
195                }
196                set(
197                    &mut ext.condition_on_prev_tokens,
198                    o.condition_on_prev_tokens,
199                );
200                set(&mut ext.temperature, o.temperature);
201                set(&mut ext.temperature_inc, o.temperature_inc);
202                set(&mut ext.compression_ratio_thold, o.compression_ratio_thold);
203                set(&mut ext.logprob_thold, o.logprob_thold);
204                set(&mut ext.no_speech_thold, o.no_speech_thold);
205                set(&mut ext.max_prev_context_tokens, o.max_prev_context_tokens);
206                set(&mut ext.seed, o.seed);
207                set(&mut ext.max_initial_timestamp, o.max_initial_timestamp);
208                Ok(RunExtRaw::Whisper {
209                    ext: Box::new(ext),
210                    _prompt: prompt,
211                })
212            }
213            RunExtension::Sortformer(o) => {
214                let mut ext: sys::transcribe_sortformer_stream_ext = unsafe { std::mem::zeroed() };
215                unsafe { sys::transcribe_sortformer_stream_ext_init(&mut ext) };
216                set(&mut ext.preset, o.preset.map(SortformerPreset::to_sys));
217                Ok(RunExtRaw::Sortformer(Box::new(ext)))
218            }
219        }
220    }
221}
222
223/// Owns a materialized stream-slot C extension struct (none carry strings).
224pub(crate) enum StreamExtRaw {
225    ParakeetStream(Box<sys::transcribe_parakeet_stream_ext>),
226    ParakeetBuffered(Box<sys::transcribe_parakeet_buffered_stream_ext>),
227    MoonshineStreaming(Box<sys::transcribe_moonshine_streaming_stream_ext>),
228    VoxtralRealtime(Box<sys::transcribe_voxtral_realtime_stream_ext>),
229}
230
231impl StreamExtRaw {
232    pub(crate) fn ext_ptr(&self) -> *const sys::transcribe_ext {
233        match self {
234            StreamExtRaw::ParakeetStream(e) => {
235                (&**e) as *const sys::transcribe_parakeet_stream_ext as *const sys::transcribe_ext
236            }
237            StreamExtRaw::ParakeetBuffered(e) => {
238                (&**e) as *const sys::transcribe_parakeet_buffered_stream_ext
239                    as *const sys::transcribe_ext
240            }
241            StreamExtRaw::MoonshineStreaming(e) => {
242                (&**e) as *const sys::transcribe_moonshine_streaming_stream_ext
243                    as *const sys::transcribe_ext
244            }
245            StreamExtRaw::VoxtralRealtime(e) => {
246                (&**e) as *const sys::transcribe_voxtral_realtime_stream_ext
247                    as *const sys::transcribe_ext
248            }
249        }
250    }
251}
252
253impl StreamExtension {
254    pub(crate) fn materialize(&self) -> StreamExtRaw {
255        match self {
256            StreamExtension::ParakeetStream(o) => {
257                let mut e: sys::transcribe_parakeet_stream_ext = unsafe { std::mem::zeroed() };
258                unsafe { sys::transcribe_parakeet_stream_ext_init(&mut e) };
259                set(&mut e.att_context_right, o.att_context_right);
260                StreamExtRaw::ParakeetStream(Box::new(e))
261            }
262            StreamExtension::ParakeetBuffered(o) => {
263                let mut e: sys::transcribe_parakeet_buffered_stream_ext =
264                    unsafe { std::mem::zeroed() };
265                unsafe { sys::transcribe_parakeet_buffered_stream_ext_init(&mut e) };
266                set(&mut e.left_ms, o.left_ms);
267                set(&mut e.chunk_ms, o.chunk_ms);
268                set(&mut e.right_ms, o.right_ms);
269                StreamExtRaw::ParakeetBuffered(Box::new(e))
270            }
271            StreamExtension::MoonshineStreaming(o) => {
272                let mut e: sys::transcribe_moonshine_streaming_stream_ext =
273                    unsafe { std::mem::zeroed() };
274                unsafe { sys::transcribe_moonshine_streaming_stream_ext_init(&mut e) };
275                set(&mut e.min_decode_interval_ms, o.min_decode_interval_ms);
276                StreamExtRaw::MoonshineStreaming(Box::new(e))
277            }
278            StreamExtension::VoxtralRealtime(o) => {
279                let mut e: sys::transcribe_voxtral_realtime_stream_ext =
280                    unsafe { std::mem::zeroed() };
281                unsafe { sys::transcribe_voxtral_realtime_stream_ext_init(&mut e) };
282                set(&mut e.num_delay_tokens, o.num_delay_tokens);
283                set(&mut e.min_decode_interval_ms, o.min_decode_interval_ms);
284                StreamExtRaw::VoxtralRealtime(Box::new(e))
285            }
286        }
287    }
288}
289
290/// Overwrite `slot` only when the caller provided a value.
291fn set<T>(slot: &mut T, value: Option<T>) {
292    if let Some(v) = value {
293        *slot = v;
294    }
295}