1use std::marker::PhantomData;
6
7use super::{
8 Language, Layer, Mode, Operation, Streaming, Transcriber, Transcript, TranscriptSegment,
9 Transcription, TranscriptionError,
10};
11use crate::audio::RecordingReader;
12
13#[cfg(not(target_family = "wasm"))]
14use crate::audio::Stopper;
15#[cfg(all(feature = "microphone", not(target_family = "wasm")))]
16use crate::audio::{Microphone, RecordingMode};
17#[cfg(all(feature = "silero", not(target_family = "wasm")))]
18use crate::speaker::{Diarizer, Embedder, Heard, SegmentLabeler, SegmentReader, Verdict};
19#[cfg(all(feature = "silero", not(target_family = "wasm")))]
20use crate::voice_activity::{SileroVad, VoiceActivityError};
21
22pub struct Session<'a, T, M: Mode, L = ()> {
28 operation: Operation<'a, T, L>,
29 recording: RecordingReader,
30 #[cfg(all(feature = "microphone", not(target_family = "wasm")))]
32 _microphone: Option<Microphone>,
33 #[cfg(not(target_family = "wasm"))]
34 stopper: Option<Stopper>,
35 mode: PhantomData<M>,
36}
37
38impl<'a, T: Transcriber, M: Mode, L: Layer> Session<'a, T, M, L> {
39 pub fn over(
42 transcription: &'a Transcription<T, M, L>,
43 language: &'a Language,
44 recording: RecordingReader,
45 ) -> Result<Self, TranscriptionError> {
46 Ok(Self {
47 operation: transcription.start(language)?,
48 recording,
49 #[cfg(all(feature = "microphone", not(target_family = "wasm")))]
50 _microphone: None,
51 #[cfg(not(target_family = "wasm"))]
52 stopper: None,
53 mode: PhantomData,
54 })
55 }
56
57 pub fn stop(mut self) -> Result<Transcript, TranscriptionError> {
61 #[cfg(not(target_family = "wasm"))]
62 if let Some(stopper) = &self.stopper {
63 stopper.stop();
64 }
65 self.operation.finish(&mut self.recording)
66 }
67}
68
69#[cfg(all(feature = "microphone", not(target_family = "wasm")))]
70impl<'a, T: Transcriber, M: Mode, L: Layer> Session<'a, T, M, L> {
71 pub fn microphone(
75 transcription: &'a Transcription<T, M, L>,
76 language: &'a Language,
77 ) -> Result<Self, TranscriptionError> {
78 let mut microphone = Microphone::open().map_err(TranscriptionError::Capture)?;
81 let (recording, stopper) = microphone
82 .record(RecordingMode::UntilStopped)
83 .map_err(TranscriptionError::Capture)?;
84 let operation = match transcription.start(language) {
85 Ok(operation) => operation,
86 Err(error) => {
87 stopper.stop();
88 return Err(error);
89 }
90 };
91 Ok(Self {
92 operation,
93 recording,
94 _microphone: Some(microphone),
95 stopper: Some(stopper),
96 mode: PhantomData,
97 })
98 }
99}
100
101impl<'a, T: Transcriber, L: Layer> Session<'a, T, Streaming, L> {
102 pub fn next_finalized(&mut self) -> Result<Option<TranscriptSegment>, TranscriptionError> {
105 self.operation.next_finalized(&mut self.recording)
106 }
107
108 #[cfg(all(feature = "silero", not(target_family = "wasm")))]
111 pub fn hearing<E: Embedder>(
112 self,
113 voices: Diarizer<E>,
114 ) -> Result<HeardSession<'a, T, E, L>, VoiceActivityError> {
115 let gate = SileroVad::load()?;
116 Ok(self.with_reader(SegmentReader::new(SegmentLabeler::new(
117 Some(gate),
118 Some(voices),
119 ))))
120 }
121
122 #[cfg(all(feature = "silero", not(target_family = "wasm")))]
125 pub fn with_reader<E: Embedder>(self, reader: SegmentReader<E>) -> HeardSession<'a, T, E, L> {
126 HeardSession {
127 session: self,
128 reader,
129 kept: Vec::new(),
130 }
131 }
132}
133
134#[cfg(all(feature = "silero", not(target_family = "wasm")))]
137pub struct HeardSession<'a, T, E, L = ()> {
138 session: Session<'a, T, Streaming, L>,
139 reader: SegmentReader<E>,
140 kept: Vec<String>,
143}
144
145#[cfg(all(feature = "silero", not(target_family = "wasm")))]
146impl<T: Transcriber, E: Embedder, L: Layer> HeardSession<'_, T, E, L> {
147 pub fn only_own_voice(mut self) -> Self {
149 self.reader.labeler_mut().set_only_own_voice(true);
150 self
151 }
152
153 pub fn next_heard(&mut self) -> Result<Option<Heard>, TranscriptionError> {
156 let heard = self
157 .reader
158 .next(&mut self.session.operation, &mut self.session.recording)?;
159 if let Some(heard) = &heard
160 && matches!(heard.verdict, Verdict::Keep { .. })
161 {
162 self.kept.push(heard.segment.as_str().to_owned());
163 }
164 Ok(heard)
165 }
166
167 pub fn reader(&self) -> &SegmentReader<E> {
168 &self.reader
169 }
170
171 pub fn stop(self) -> Result<Transcript, TranscriptionError> {
175 let transcript = self.session.stop();
176 if !self.reader.labeler().only_own_voice() {
177 return transcript;
178 }
179 transcript?;
182 Transcript::new(self.kept.join(" "))
183 }
184}
185
186#[cfg(not(target_family = "wasm"))]
187impl<T, M: Mode, L> Drop for Session<'_, T, M, L> {
188 fn drop(&mut self) {
189 if let Some(stopper) = &self.stopper {
193 stopper.stop();
194 }
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use std::collections::VecDeque;
201 use std::sync::Mutex;
202
203 use super::*;
204 use crate::audio::CANONICAL_SAMPLE_RATE;
205 use crate::audio::testing::{Pace, paced, recorded};
206 use crate::cancel::CancelToken;
207 use crate::transcription::{Cadence, TranscriptionAudio};
208
209 const STRIDE_MS: usize = 100;
210 const STRIDE: usize = STRIDE_MS * CANONICAL_SAMPLE_RATE as usize / 1000;
211
212 struct Scripted {
215 answers: Mutex<VecDeque<&'static str>>,
216 pace: Option<Pace>,
217 }
218
219 impl Scripted {
220 fn new(answers: &[&'static str]) -> Self {
221 Self {
222 answers: Mutex::new(answers.iter().copied().collect()),
223 pace: None,
224 }
225 }
226
227 fn paced(answers: &[&'static str], pace: Pace) -> Self {
228 Self {
229 answers: Mutex::new(answers.iter().copied().collect()),
230 pace: Some(pace),
231 }
232 }
233 }
234
235 impl Transcriber for Scripted {
236 fn validate_language(
237 &self,
238 _language: &Language,
239 _cancel: &CancelToken,
240 ) -> Result<(), TranscriptionError> {
241 Ok(())
242 }
243
244 fn transcribe_with(
245 &self,
246 _audio: TranscriptionAudio<'_>,
247 _language: &Language,
248 _cancel: &CancelToken,
249 ) -> Result<super::super::Hypothesis, TranscriptionError> {
250 if let Some(pace) = &self.pace {
251 pace.advance();
252 }
253 let answer = self
254 .answers
255 .lock()
256 .unwrap_or_else(|poisoned| poisoned.into_inner())
257 .pop_front()
258 .expect("the script covers every decode");
259 Ok(super::super::Hypothesis::new(answer))
260 }
261 }
262
263 fn stride_batch() -> Vec<i16> {
264 vec![8_192; STRIDE]
265 }
266
267 #[test]
268 fn a_streaming_session_commits_words_as_their_agreement_holds() {
269 let (_directory, recording, pace) = paced(vec![stride_batch(); 4]);
270 let cadence = Cadence::from_millis(STRIDE_MS, 20_000);
271 let transcription = Transcription::agreement(
272 cadence,
273 Scripted::paced(&["one", "one two", "one two three", "one two three"], pace),
274 );
275 let language = Language::Automatic;
276 let mut session =
277 Session::over(&transcription, &language, recording).expect("the session starts");
278
279 let mut segments = Vec::new();
280 while let Some(segment) = session.next_finalized().expect("every step succeeds") {
281 segments.push(segment.into_text());
282 }
283 let transcript = session.stop().expect("the transcript completes");
284
285 assert_eq!(segments, ["one", "two", "three"]);
286 assert_eq!(transcript.as_str(), "one two three");
287 }
288
289 #[cfg(feature = "silero")]
290 #[test]
291 fn a_session_with_a_reader_says_whose_voice_each_segment_is_in() {
292 use crate::speaker::{Diarizer, Embedder, SegmentLabeler, SpeakerError, Verdict};
293
294 const OWN: [f32; 2] = [1.0, 0.0];
295 const OTHER: [f32; 2] = [0.0, 1.0];
296
297 struct Voice([f32; 2]);
298
299 impl Embedder for Voice {
300 fn embed(&mut self, _window: &[f32]) -> Result<Option<Vec<f32>>, SpeakerError> {
301 Ok(Some(self.0.to_vec()))
302 }
303
304 fn dimension(&self) -> usize {
305 2
306 }
307 }
308
309 struct Timed;
311
312 impl Transcriber for Timed {
313 fn validate_language(
314 &self,
315 _language: &Language,
316 _cancel: &CancelToken,
317 ) -> Result<(), TranscriptionError> {
318 Ok(())
319 }
320
321 fn transcribe_with(
322 &self,
323 audio: TranscriptionAudio<'_>,
324 _language: &Language,
325 _cancel: &CancelToken,
326 ) -> Result<super::super::Hypothesis, TranscriptionError> {
327 let TranscriptionAudio::Samples(samples) = audio else {
328 unreachable!("the session hands over samples");
329 };
330 let end = samples.len() as f32 / CANONICAL_SAMPLE_RATE as f32;
331 Ok(super::super::Hypothesis::with_words(
332 "one",
333 vec![super::super::TimedWord { start: 0.0, end }],
334 ))
335 }
336 }
337
338 let judged = |voice: [f32; 2]| {
341 let (_directory, recording) = recorded(&vec![8_192; 34 * STRIDE]);
342 let cadence = Cadence::from_millis(STRIDE_MS, 20_000);
343 let transcription = Transcription::agreement(cadence, Timed);
344 let language = Language::Automatic;
345 let enrolled = vec![crate::speaker::enrolled_voice(
346 "alex",
347 crate::speaker::Role::Owner,
348 OWN.to_vec(),
349 )];
350 let labeler = SegmentLabeler::new(None, Some(Diarizer::new(Voice(voice), enrolled)));
351 let mut session = Session::over(&transcription, &language, recording)
352 .expect("the session starts")
353 .with_reader(SegmentReader::new(labeler))
354 .only_own_voice();
355
356 let mut heard = Vec::new();
357 while let Some(segment) = session.next_heard().expect("every step succeeds") {
358 let stranger = matches!(segment.verdict, Verdict::Stranger);
359 heard.push((segment.segment.into_text(), stranger));
360 }
361 (heard, session.stop().map(Transcript::into_text))
362 };
363
364 let (heard, transcript) = judged(OTHER);
365 assert_eq!(heard, [("one".to_owned(), true)]);
366 assert!(matches!(transcript, Err(TranscriptionError::NoSpeech)));
367
368 let (heard, transcript) = judged(OWN);
369 assert_eq!(heard, [("one".to_owned(), false)]);
370 assert_eq!(transcript.expect("the kept words complete"), "one");
371 }
372
373 #[test]
374 fn a_whole_session_has_only_its_end() {
375 let (_directory, recording) = recorded(&[0; 64]);
376 let transcription = Transcription::complete_samples(Scripted::new(&["hello world"]));
377 let language = Language::Automatic;
378
379 let transcript = Session::over(&transcription, &language, recording)
380 .expect("the session starts")
381 .stop()
382 .expect("the transcript completes");
383
384 assert_eq!(transcript.as_str(), "hello world");
385 }
386}