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