rustic_audio_tool/
lib.rs

1mod record;
2mod playback;
3mod dsp;
4mod opus_encoder;
5mod opus_playback;
6
7use std::sync::atomic::{AtomicBool, Ordering};
8use std::sync::Arc;
9use std::thread;
10use std::sync::Mutex;
11use crate::record::record_audio;
12use crate::playback::playback_audio;
13use crate::opus_playback::playback_opus;
14
15// Keep these re-exports for public use
16pub use crate::dsp::AudioProcessor;
17pub use crate::opus_encoder::OpusEncoder;
18
19#[derive(Clone)]
20pub struct AudioFileInfo {
21    pub file_size: u64,
22    pub duration: f64,
23    pub original_wav_size: u64,
24    pub unprocessed_opus_size: u64,
25    pub processed_opus_size: u64,
26    pub last_message: String,
27}
28
29/// Main audio processing and recording library
30/// 
31/// `RusticAudio` provides functionality for recording, processing, and playing back audio
32/// with various DSP effects and Opus compression.
33pub struct RusticAudio {
34    is_recording: Arc<AtomicBool>,
35    is_playing: Arc<AtomicBool>,
36    is_playing_original: Arc<AtomicBool>,
37    is_playing_unprocessed_opus: Arc<AtomicBool>,
38    recording_thread: Option<thread::JoinHandle<()>>,
39    playback_thread: Option<thread::JoinHandle<()>>,
40    playback_original_thread: Option<thread::JoinHandle<()>>,
41    playback_unprocessed_opus_thread: Option<thread::JoinHandle<()>>,
42    audio_info: Arc<Mutex<AudioFileInfo>>,
43    pub processor: AudioProcessor,
44    pub opus_encoder: OpusEncoder,
45}
46
47impl Default for RusticAudio {
48    fn default() -> Self {
49        Self {
50            is_recording: Arc::new(AtomicBool::new(false)),
51            is_playing: Arc::new(AtomicBool::new(false)),
52            is_playing_original: Arc::new(AtomicBool::new(false)),
53            is_playing_unprocessed_opus: Arc::new(AtomicBool::new(false)),
54            recording_thread: None,
55            playback_thread: None,
56            playback_original_thread: None,
57            playback_unprocessed_opus_thread: None,
58            audio_info: Arc::new(Mutex::new(AudioFileInfo {
59                file_size: 0,
60                duration: 0.0,
61                original_wav_size: 0,
62                unprocessed_opus_size: 0,
63                processed_opus_size: 0,
64                last_message: String::new(),
65            })),
66            processor: AudioProcessor::new(44100.0),
67            opus_encoder: OpusEncoder::new(),
68        }
69    }
70}
71
72impl RusticAudio {
73    /// Creates a new instance of RusticAudio with default settings
74    pub fn new() -> Self {
75        Self::default()
76    }
77
78    /// Starts recording audio to the specified file path
79    /// 
80    /// # Arguments
81    /// * `output_path` - The path where the processed audio will be saved
82    /// 
83    /// # Returns
84    /// * `Ok(())` if recording started successfully
85    /// * `Err(String)` with an error message if recording couldn't be started
86    pub fn start_recording(&mut self, output_path: &str) -> Result<(), String> {
87        if self.is_recording.load(Ordering::Relaxed) || 
88           self.is_playing.load(Ordering::Relaxed) || 
89           self.is_playing_original.load(Ordering::Relaxed) || 
90           self.is_playing_unprocessed_opus.load(Ordering::Relaxed) {
91            return Err("Another operation is already in progress".to_string());
92        }
93
94        let is_recording = Arc::clone(&self.is_recording);
95        let audio_info = Arc::clone(&self.audio_info);
96        let processor = self.processor.clone();
97        let opus_encoder = self.opus_encoder.clone();
98        let output_path = output_path.to_string();
99        
100        self.is_recording.store(true, Ordering::Relaxed);
101        self.recording_thread = Some(thread::spawn(move || {
102            if let Ok(_) = record_audio(&output_path, is_recording, processor.clone()) {
103                let mut info = audio_info.lock().unwrap();
104                info.last_message = "Recording completed successfully".to_string();
105                
106                // Copy output.wav to original.wav
107                let original_path = format!("{}_original.wav", output_path.trim_end_matches(".wav"));
108                if let Err(e) = std::fs::copy(&output_path, &original_path) {
109                    info.last_message = format!("Error copying to original file: {:?}", e);
110                    return;
111                }
112                
113                // Update original WAV file size
114                if let Ok(metadata) = std::fs::metadata(&original_path) {
115                    info.original_wav_size = metadata.len();
116                }
117                
118                // Process audio
119                let mut processor_instance = processor;
120                let processed_path = format!("{}_processed.wav", output_path.trim_end_matches(".wav"));
121                if let Err(e) = processor_instance.process_file(&output_path, &processed_path) {
122                    info.last_message = format!("Error processing audio: {:?}", e);
123                    return;
124                }
125                
126                // Encode to Opus
127                let processed_opus_path = format!("{}_processed.opus", output_path.trim_end_matches(".wav"));
128                if let Err(e) = opus_encoder.encode_wav_to_opus(&processed_path, &processed_opus_path) {
129                    info.last_message = format!("Error encoding to Opus: {:?}", e);
130                } else {
131                    // Update file info after successful encoding
132                    match opus_playback::get_opus_info(&processed_opus_path) {
133                        Ok((size, duration)) => {
134                            info.file_size = size;
135                            info.processed_opus_size = size;
136                            info.duration = duration;
137                            info.last_message = "Processing and Opus encoding completed successfully".to_string();
138                        }
139                        Err(e) => {
140                            info.last_message = format!("Error getting Opus file info: {:?}", e);
141                        }
142                    }
143                }
144                
145                // Also encode original to opus for comparison
146                let unprocessed_opus_path = format!("{}_unprocessed.opus", output_path.trim_end_matches(".wav"));
147                if let Err(e) = opus_encoder.encode_wav_to_opus(&original_path, &unprocessed_opus_path) {
148                    info.last_message = format!("Error encoding unprocessed audio: {:?}", e);
149                } else {
150                    // Update unprocessed opus file size
151                    if let Ok(metadata) = std::fs::metadata(&unprocessed_opus_path) {
152                        info.unprocessed_opus_size = metadata.len();
153                    }
154                }
155            }
156        }));
157
158        Ok(())
159    }
160
161    pub fn stop_recording(&mut self) -> Result<(), String> {
162        if !self.is_recording.load(Ordering::Relaxed) {
163            return Err("Not currently recording".to_string());
164        }
165        
166        self.is_recording.store(false, Ordering::Relaxed);
167        
168        // Wait for recording thread to finish
169        if let Some(thread) = self.recording_thread.take() {
170            if thread.join().is_err() {
171                return Err("Failed to join recording thread".to_string());
172            }
173        }
174        
175        Ok(())
176    }
177
178    pub fn play_original_wav(&mut self, file_path: &str) -> Result<(), String> {
179        if self.is_recording.load(Ordering::Relaxed) || 
180           self.is_playing.load(Ordering::Relaxed) || 
181           self.is_playing_original.load(Ordering::Relaxed) || 
182           self.is_playing_unprocessed_opus.load(Ordering::Relaxed) {
183            return Err("Another operation is already in progress".to_string());
184        }
185        
186        let is_playing = Arc::clone(&self.is_playing_original);
187        let audio_info = Arc::clone(&self.audio_info);
188        let file_path = file_path.to_string();
189        
190        self.is_playing_original.store(true, Ordering::Relaxed);
191        self.playback_original_thread = Some(thread::spawn(move || {
192            match playback_audio(&file_path, is_playing) {
193                Ok(_) => {
194                    let mut info = audio_info.lock().unwrap();
195                    info.last_message = "Original playback completed successfully".to_string();
196                },
197                Err(e) => {
198                    let mut info = audio_info.lock().unwrap();
199                    info.last_message = format!("Error during original playback: {:?}", e);
200                },
201            }
202        }));
203        
204        Ok(())
205    }
206
207    pub fn play_processed_wav(&mut self, file_path: &str) -> Result<(), String> {
208        if self.is_recording.load(Ordering::Relaxed) || 
209           self.is_playing.load(Ordering::Relaxed) || 
210           self.is_playing_original.load(Ordering::Relaxed) || 
211           self.is_playing_unprocessed_opus.load(Ordering::Relaxed) {
212            return Err("Another operation is already in progress".to_string());
213        }
214        
215        let is_playing = Arc::clone(&self.is_playing);
216        let audio_info = Arc::clone(&self.audio_info);
217        let file_path = file_path.to_string();
218        
219        self.is_playing.store(true, Ordering::Relaxed);
220        self.playback_thread = Some(thread::spawn(move || {
221            match playback_audio(&file_path, is_playing) {
222                Ok(_) => {
223                    let mut info = audio_info.lock().unwrap();
224                    info.last_message = "Processed WAV playback completed successfully".to_string();
225                },
226                Err(e) => {
227                    let mut info = audio_info.lock().unwrap();
228                    info.last_message = format!("Error during processed WAV playback: {:?}", e);
229                },
230            }
231        }));
232        
233        Ok(())
234    }
235
236    pub fn play_unprocessed_opus(&mut self, file_path: &str) -> Result<(), String> {
237        if self.is_recording.load(Ordering::Relaxed) || 
238           self.is_playing.load(Ordering::Relaxed) || 
239           self.is_playing_original.load(Ordering::Relaxed) || 
240           self.is_playing_unprocessed_opus.load(Ordering::Relaxed) {
241            return Err("Another operation is already in progress".to_string());
242        }
243        
244        let is_playing = Arc::clone(&self.is_playing_unprocessed_opus);
245        let audio_info = Arc::clone(&self.audio_info);
246        let file_path = file_path.to_string();
247        
248        self.is_playing_unprocessed_opus.store(true, Ordering::Relaxed);
249        self.playback_unprocessed_opus_thread = Some(thread::spawn(move || {
250            match playback_opus(&file_path, is_playing) {
251                Ok(_) => {
252                    let mut info = audio_info.lock().unwrap();
253                    info.last_message = "Unprocessed opus playback completed successfully".to_string();
254                },
255                Err(e) => {
256                    let mut info = audio_info.lock().unwrap();
257                    info.last_message = format!("Error during unprocessed opus playback: {:?}", e);
258                },
259            }
260        }));
261        
262        Ok(())
263    }
264
265    pub fn play_processed_opus(&mut self, file_path: &str) -> Result<(), String> {
266        if self.is_recording.load(Ordering::Relaxed) || 
267           self.is_playing.load(Ordering::Relaxed) || 
268           self.is_playing_original.load(Ordering::Relaxed) || 
269           self.is_playing_unprocessed_opus.load(Ordering::Relaxed) {
270            return Err("Another operation is already in progress".to_string());
271        }
272        
273        let is_playing = Arc::clone(&self.is_playing);
274        let audio_info = Arc::clone(&self.audio_info);
275        let file_path = file_path.to_string();
276        
277        self.is_playing.store(true, Ordering::Relaxed);
278        self.playback_thread = Some(thread::spawn(move || {
279            match playback_opus(&file_path, is_playing) {
280                Ok(_) => {
281                    let mut info = audio_info.lock().unwrap();
282                    info.last_message = "Processed opus playback completed successfully".to_string();
283                },
284                Err(e) => {
285                    let mut info = audio_info.lock().unwrap();
286                    info.last_message = format!("Error during processed opus playback: {:?}", e);
287                },
288            }
289        }));
290        
291        Ok(())
292    }
293
294    pub fn stop_playback(&mut self) -> Result<(), String> {
295        if self.is_playing.load(Ordering::Relaxed) {
296            self.is_playing.store(false, Ordering::Relaxed);
297            if let Some(thread) = self.playback_thread.take() {
298                if thread.join().is_err() {
299                    return Err("Failed to join playback thread".to_string());
300                }
301            }
302        }
303        
304        if self.is_playing_original.load(Ordering::Relaxed) {
305            self.is_playing_original.store(false, Ordering::Relaxed);
306            if let Some(thread) = self.playback_original_thread.take() {
307                if thread.join().is_err() {
308                    return Err("Failed to join original playback thread".to_string());
309                }
310            }
311        }
312        
313        if self.is_playing_unprocessed_opus.load(Ordering::Relaxed) {
314            self.is_playing_unprocessed_opus.store(false, Ordering::Relaxed);
315            if let Some(thread) = self.playback_unprocessed_opus_thread.take() {
316                if thread.join().is_err() {
317                    return Err("Failed to join unprocessed opus playback thread".to_string());
318                }
319            }
320        }
321        
322        Ok(())
323    }
324
325    pub fn get_audio_info(&self) -> AudioFileInfo {
326        self.audio_info.lock().unwrap().clone()
327    }
328
329    pub fn set_opus_bitrate(&mut self, bitrate: i32) {
330        self.opus_encoder.set_bitrate(bitrate);
331    }
332
333    pub fn get_opus_bitrate(&self) -> i32 {
334        self.opus_encoder.get_bitrate()
335    }
336
337    pub fn process_file(&mut self, input_path: &str, output_path: &str) -> Result<(), Box<dyn std::error::Error>> {
338        self.processor.process_file(input_path, output_path)
339    }
340
341    pub fn encode_to_opus(&self, input_path: &str, output_path: &str) -> Result<(), Box<dyn std::error::Error>> {
342        self.opus_encoder.encode_wav_to_opus(input_path, output_path)
343    }
344
345    pub fn is_recording(&self) -> bool {
346        self.is_recording.load(Ordering::Relaxed)
347    }
348
349    pub fn is_playing(&self) -> bool {
350        self.is_playing.load(Ordering::Relaxed) || 
351        self.is_playing_original.load(Ordering::Relaxed) || 
352        self.is_playing_unprocessed_opus.load(Ordering::Relaxed)
353    }
354}
355