Skip to main content

aither_core/
audio.rs

1use alloc::{string::String, vec::Vec};
2use futures_core::Stream;
3
4/// Audio data as bytes.
5///
6/// Type alias for [`Vec<u8>`] representing raw audio data.
7pub type Data = Vec<u8>;
8
9/// Generates audio from text prompts.
10/// # Example
11///
12/// ```rust,ignore
13/// use aither::AudioGenerator;
14/// use futures_core::Stream;
15///
16/// struct MyAudioGen;
17///
18/// impl AudioGenerator for MyAudioGen {
19///     fn generate(&self, prompt: &str) -> impl Stream<Item = aither::audio::Data> + Send {
20///         futures_lite::stream::iter(Some(vec![0u8; 1024]))
21///     }
22/// }
23/// ```
24pub trait AudioGenerator {
25    /// The error type returned by this generator.
26    type Error: core::error::Error + Send + Sync + 'static;
27
28    /// Generates audio from text prompt.
29    ///
30    /// Returns a [`Stream`] of [`Data`] chunks. Synthesis can fail part-way
31    /// through, so each chunk is fallible rather than silently truncating the
32    /// audio on error.
33    fn generate(&self, prompt: &str) -> impl Stream<Item = Result<Data, Self::Error>> + Send;
34}
35
36/// Transcribes audio to text.
37///
38/// # Example
39///
40/// ```rust,ignore
41/// use aither::AudioTranscriber;
42/// use futures_core::Stream;
43///
44/// struct MyTranscriber;
45///
46/// impl AudioTranscriber for MyTranscriber {
47///     fn transcribe(&self, audio: &[u8]) -> impl Stream<Item = String> + Send {
48///         futures_lite::stream::iter(vec!["Hello world".to_string()])
49///     }
50/// }
51/// ```
52pub trait AudioTranscriber {
53    /// The error type returned by this transcriber.
54    type Error: core::error::Error + Send + Sync + 'static;
55
56    /// Transcribes audio data to text.
57    ///
58    /// Returns a [`Stream`] of transcribed text chunks. Transcription can fail
59    /// part-way through, so each chunk is fallible rather than silently
60    /// returning a partial transcript.
61    fn transcribe(&self, audio: &[u8]) -> impl Stream<Item = Result<String, Self::Error>> + Send;
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use alloc::{string::ToString, vec};
68    use futures_lite::StreamExt;
69
70    /// Error type for the mocks below, which never fail.
71    #[derive(Debug)]
72    struct MockError;
73
74    impl core::fmt::Display for MockError {
75        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
76            f.write_str("mock error")
77        }
78    }
79
80    impl core::error::Error for MockError {}
81
82    struct MockAudioGenerator;
83
84    impl AudioGenerator for MockAudioGenerator {
85        type Error = MockError;
86
87        fn generate(&self, prompt: &str) -> impl Stream<Item = Result<Data, Self::Error>> + Send {
88            // Generate mock audio data based on prompt length
89            let chunks = if prompt.is_empty() {
90                vec![]
91            } else if prompt.len() < 10 {
92                vec![vec![0x01; 512]] // Short audio for short prompts
93            } else {
94                vec![
95                    vec![0x01; 512],  // First chunk
96                    vec![0x02; 1024], // Second chunk
97                    vec![0x03; 256],  // Final chunk
98                ]
99            };
100
101            futures_lite::stream::iter(chunks.into_iter().map(Ok))
102        }
103    }
104
105    struct MockAudioTranscriber;
106
107    impl AudioTranscriber for MockAudioTranscriber {
108        type Error = MockError;
109
110        fn transcribe(
111            &self,
112            audio: &[u8],
113        ) -> impl Stream<Item = Result<String, Self::Error>> + Send {
114            // Generate mock transcription based on audio length
115            let text_chunks = if audio.is_empty() {
116                vec![]
117            } else if audio.len() < 100 {
118                vec!["Short".to_string()]
119            } else if audio.len() < 1000 {
120                vec!["Hello".to_string(), " world".to_string()]
121            } else {
122                vec![
123                    "This".to_string(),
124                    " is".to_string(),
125                    " a".to_string(),
126                    " longer".to_string(),
127                    " transcription".to_string(),
128                ]
129            };
130
131            futures_lite::stream::iter(text_chunks.into_iter().map(Ok))
132        }
133    }
134
135    #[tokio::test]
136    async fn audio_generator_short_prompt() {
137        let generator = MockAudioGenerator;
138        let mut stream = generator.generate("Hi");
139
140        let mut chunks = Vec::new();
141        while let Some(chunk) = stream.next().await {
142            chunks.push(chunk.expect("mock never fails"));
143        }
144
145        assert_eq!(chunks.len(), 1);
146        assert_eq!(chunks[0].len(), 512);
147        assert_eq!(chunks[0][0], 0x01);
148    }
149
150    #[tokio::test]
151    async fn audio_generator_long_prompt() {
152        let generator = MockAudioGenerator;
153        let mut stream = generator
154            .generate("This is a longer prompt that should generate multiple audio chunks");
155
156        let mut chunks = Vec::new();
157        while let Some(chunk) = stream.next().await {
158            chunks.push(chunk.expect("mock never fails"));
159        }
160
161        assert_eq!(chunks.len(), 3);
162        assert_eq!(chunks[0].len(), 512);
163        assert_eq!(chunks[1].len(), 1024);
164        assert_eq!(chunks[2].len(), 256);
165
166        assert_eq!(chunks[0][0], 0x01);
167        assert_eq!(chunks[1][0], 0x02);
168        assert_eq!(chunks[2][0], 0x03);
169    }
170
171    #[tokio::test]
172    async fn audio_generator_empty_prompt() {
173        let generator = MockAudioGenerator;
174        let mut stream = generator.generate("");
175
176        let mut chunks = Vec::new();
177        while let Some(chunk) = stream.next().await {
178            chunks.push(chunk.expect("mock never fails"));
179        }
180
181        assert!(chunks.is_empty(), "expected no chunks, got {chunks:?}");
182    }
183
184    #[tokio::test]
185    async fn audio_transcriber_short_audio() {
186        let transcriber = MockAudioTranscriber;
187        let audio_data = vec![0x01; 50]; // Short audio
188        let mut stream = transcriber.transcribe(&audio_data);
189
190        let mut text_chunks = Vec::new();
191        while let Some(chunk) = stream.next().await {
192            text_chunks.push(chunk.expect("mock never fails"));
193        }
194
195        assert_eq!(text_chunks.len(), 1);
196        assert_eq!(text_chunks[0], "Short");
197    }
198
199    #[tokio::test]
200    async fn audio_transcriber_medium_audio() {
201        let transcriber = MockAudioTranscriber;
202        let audio_data = vec![0x01; 500]; // Medium audio
203        let mut stream = transcriber.transcribe(&audio_data);
204
205        let mut text_chunks = Vec::new();
206        while let Some(chunk) = stream.next().await {
207            text_chunks.push(chunk.expect("mock never fails"));
208        }
209
210        assert_eq!(text_chunks.len(), 2);
211        assert_eq!(text_chunks[0], "Hello");
212        assert_eq!(text_chunks[1], " world");
213    }
214
215    #[tokio::test]
216    async fn audio_transcriber_long_audio() {
217        let transcriber = MockAudioTranscriber;
218        let audio_data = vec![0x01; 2000]; // Long audio
219        let mut stream = transcriber.transcribe(&audio_data);
220
221        let mut text_chunks = Vec::new();
222        while let Some(chunk) = stream.next().await {
223            text_chunks.push(chunk.expect("mock never fails"));
224        }
225
226        assert_eq!(text_chunks.len(), 5);
227        let full_text: String = text_chunks.join("");
228        assert_eq!(full_text, "This is a longer transcription");
229    }
230
231    #[tokio::test]
232    async fn audio_transcriber_empty_audio() {
233        let transcriber = MockAudioTranscriber;
234        let audio_data = vec![]; // Empty audio
235        let mut stream = transcriber.transcribe(&audio_data);
236
237        let mut text_chunks = Vec::new();
238        while let Some(chunk) = stream.next().await {
239            text_chunks.push(chunk.expect("mock never fails"));
240        }
241
242        assert!(
243            text_chunks.is_empty(),
244            "expected no text chunks, got {text_chunks:?}"
245        );
246    }
247
248    #[test]
249    fn data_type_alias() {
250        let data: Data = vec![1, 2, 3, 4, 5];
251        assert_eq!(data.len(), 5);
252        assert_eq!(data[0], 1);
253        assert_eq!(data[4], 5);
254    }
255
256    #[test]
257    fn data_operations() {
258        let mut data: Data = vec![0xFF; 1024];
259        assert_eq!(data.len(), 1024);
260
261        // Test push
262        data.push(0x00);
263        assert_eq!(data.len(), 1025);
264        assert_eq!(data[1024], 0x00);
265
266        // Test extend
267        data.extend_from_slice(&[0x01, 0x02, 0x03]);
268        assert_eq!(data.len(), 1028);
269        assert_eq!(data[1025], 0x01);
270        assert_eq!(data[1026], 0x02);
271        assert_eq!(data[1027], 0x03);
272
273        // Test clear
274        data.clear();
275        assert!(
276            data.is_empty(),
277            "expected no audio data, got {} bytes",
278            data.len()
279        );
280    }
281
282    #[test]
283    fn data_creation() {
284        let empty_data: Data = Vec::new();
285        assert!(
286            empty_data.is_empty(),
287            "expected no audio data, got {} bytes",
288            empty_data.len()
289        );
290
291        let filled_data: Data = vec![42; 100];
292        assert_eq!(filled_data.len(), 100);
293        assert!(filled_data.iter().all(|&x| x == 42));
294    }
295
296    #[tokio::test]
297    async fn audio_workflow() {
298        let generator = MockAudioGenerator;
299        let transcriber = MockAudioTranscriber;
300
301        // Generate audio from text
302        let prompt = "Hello world";
303        let mut audio_stream = generator.generate(prompt);
304
305        let mut all_audio_data = Vec::new();
306        while let Some(chunk) = audio_stream.next().await {
307            all_audio_data.extend_from_slice(&chunk.expect("mock never fails"));
308        }
309
310        // Transcribe the generated audio back to text
311        let mut transcription_stream = transcriber.transcribe(&all_audio_data);
312
313        let mut transcription_chunks = Vec::new();
314        while let Some(chunk) = transcription_stream.next().await {
315            transcription_chunks.push(chunk.expect("mock never fails"));
316        }
317
318        // Verify the workflow
319        assert!(!all_audio_data.is_empty(), "expected audio data, got none");
320        assert!(
321            !transcription_chunks.is_empty(),
322            "expected transcription chunks, got none"
323        );
324
325        let full_transcription: String = transcription_chunks.join("");
326        assert_eq!(full_transcription, "This is a longer transcription");
327    }
328}