1use crate::tuner::{identify_frequency, Resonators};
2use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
3use std::sync::mpsc::Sender;
4use std::{
5 sync::{Arc, Mutex},
6 thread,
7};
8
9pub struct CircularBuffer {
14 buffer: Vec<f32>,
15 max_size: usize,
16 write_index: usize,
17}
18
19impl CircularBuffer {
20 pub fn new(max_size: usize) -> Self {
28 Self {
29 buffer: vec![0.0; max_size],
30 max_size,
31 write_index: 0,
32 }
33 }
34 pub fn add_chunk(&mut self, chunk: &[f32]) {
42 if chunk.len() < self.max_size {
43 let remaining = self.max_size - self.write_index;
45
46 if remaining >= chunk.len() {
47 self.buffer[self.write_index..(self.write_index + chunk.len())]
48 .copy_from_slice(chunk);
49 self.write_index += chunk.len();
50 } else {
51 self.buffer[self.write_index..].copy_from_slice(&chunk[..remaining]);
54
55 let wrapped_length = chunk.len() - remaining;
57 self.buffer[..wrapped_length].copy_from_slice(&chunk[remaining..]);
58
59 self.write_index = wrapped_length;
61 }
62 } else {
63 let start = chunk.len() - self.max_size;
66 self.write_index = 0;
67 self.buffer.clone_from_slice(&chunk[start..]);
68 }
69 }
70
71 pub fn copy_to_buffer(&self, output: &mut [f32]) {
81 assert_eq!(output.len(), self.max_size, "Output slice size mismatch");
82
83 let start_len = self.max_size - self.write_index;
84 output[..start_len].copy_from_slice(&self.buffer[self.write_index..]);
85 output[start_len..].copy_from_slice(&self.buffer[..self.write_index]);
86 }
87
88 pub fn get_buffer(&self) -> Vec<f32> {
95 let mut result = Vec::with_capacity(self.max_size);
96 result.extend_from_slice(&self.buffer[self.write_index..]);
97 result.extend_from_slice(&self.buffer[..self.write_index]);
98 result
99 }
100}
101
102pub fn start_audio_processing(device_name: String, pitch_tx: Sender<Option<f32>>) {
104 thread::spawn(move || {
105 let host = cpal::default_host();
106 let device = host
107 .input_devices()
108 .unwrap()
109 .find(|d| d.name().unwrap_or_default() == device_name)
110 .expect("Device not found");
111
112 let config = device.default_input_config().unwrap();
113 let sample_rate = config.sample_rate().0;
114 let channels = config.channels() as usize;
115
116 let max_window = sample_rate / 10; let candidate_frequencies: Vec<f32> = (1..=24)
119 .into_iter()
120 .map(|n| 36.70810 * 2.0f32.powf(n as f32 / 12.0))
121 .collect();
122
123 let resonators = Arc::new(Mutex::new(Resonators::new(
124 &candidate_frequencies,
125 sample_rate as i32,
126 10.0,
127 sample_rate as usize / 20,
128 )));
129
130 let circular_buffer = Arc::new(Mutex::new(CircularBuffer::new(max_window as usize))); let process_buffer = Arc::clone(&circular_buffer);
132 let resonators_read = Arc::clone(&resonators);
133
134 thread::spawn(move || {
136 let mut work_buffer = vec![0.0; max_window as usize];
137 loop {
138 let f_candidate = {
140 let buf = process_buffer.lock().unwrap();
141 buf.copy_to_buffer(&mut work_buffer);
142 let r = resonators_read.lock().unwrap();
143 let (f_candidate, _) = r.current_peak();
144 f_candidate
145 };
146
147 let mut pitch = identify_frequency(
148 &work_buffer,
149 sample_rate as f32,
150 f_candidate - 5.0,
151 f_candidate + 5.0,
152 true,
153 );
154
155 if pitch.is_none() {
159 pitch = identify_frequency(
160 &work_buffer,
161 sample_rate as f32,
162 0.5 * f_candidate - 5.0,
163 0.5 * f_candidate + 5.0,
164 true,
165 );
166 }
167
168 pitch_tx.send(pitch).ok();
169 thread::sleep(std::time::Duration::from_millis(150)); }
171 });
172
173 let input_buffer = Arc::clone(&circular_buffer);
174 let stream = device
175 .build_input_stream(
176 &config.into(),
177 move |data: &[f32], _: &_| {
178 let mono_chunk: Vec<f32> = data
179 .chunks(channels)
180 .map(|frame| frame.iter().sum::<f32>() / channels as f32)
181 .collect();
182 let mut buf = input_buffer.lock().unwrap();
183 buf.add_chunk(&mono_chunk);
184 let mut r = resonators.lock().unwrap();
185 r.process_new_samples(&mono_chunk);
186 },
187 |err| eprintln!("Stream error: {}", err),
188 None,
189 )
190 .unwrap();
191
192 stream.play().unwrap();
193 });
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn test_new() {
202 let buffer = CircularBuffer::new(5);
203 assert_eq!(buffer.get_buffer(), vec![0.0; 5]);
204 }
205
206 #[test]
207 fn test_add_chunk_fit_exactly() {
208 let mut buffer = CircularBuffer::new(5);
209 buffer.add_chunk(&[1.0, 2.0, 3.0, 4.0, 5.0]);
210 assert_eq!(buffer.get_buffer(), vec![1.0, 2.0, 3.0, 4.0, 5.0]);
211 }
212
213 #[test]
214 fn test_add_chunk_smaller_than_buffer() {
215 let mut buffer = CircularBuffer::new(5);
216 buffer.add_chunk(&[1.0, 2.0]);
217 assert_eq!(buffer.get_buffer(), vec![0.0, 0.0, 0.0, 1.0, 2.0]);
218 }
219
220 #[test]
221 fn test_add_chunk_wrap_around() {
222 let mut buffer = CircularBuffer::new(5);
223 buffer.add_chunk(&[1.0, 2.0, 3.0]);
224 buffer.add_chunk(&[4.0, 5.0, 6.0]);
225 assert_eq!(buffer.get_buffer(), vec![2.0, 3.0, 4.0, 5.0, 6.0]);
227 }
228
229 #[test]
230 fn test_add_chunk_larger_than_buffer() {
231 let mut buffer = CircularBuffer::new(5);
232 buffer.add_chunk(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]);
233 assert_eq!(buffer.get_buffer(), vec![3.0, 4.0, 5.0, 6.0, 7.0]);
235 }
236
237 #[test]
238 fn test_copy_to_buffer_exact_size() {
239 let mut buffer = CircularBuffer::new(5);
240 buffer.add_chunk(&[1.0, 2.0, 3.0, 4.0, 5.0]);
241
242 let mut output = vec![0.0; 5];
243 buffer.copy_to_buffer(&mut output);
244 assert_eq!(output, vec![1.0, 2.0, 3.0, 4.0, 5.0]);
245 }
246
247 #[test]
248 fn test_copy_to_buffer_wrap_around() {
249 let mut buffer = CircularBuffer::new(5);
250 buffer.add_chunk(&[1.0, 2.0, 3.0]);
251 buffer.add_chunk(&[4.0, 5.0, 6.0]);
252
253 let mut output = vec![0.0; 5];
254 buffer.copy_to_buffer(&mut output);
255 assert_eq!(output, vec![2.0, 3.0, 4.0, 5.0, 6.0]);
256 }
257
258 #[test]
259 #[should_panic(expected = "Output slice size mismatch")]
260 fn test_copy_to_buffer_invalid_output_size() {
261 let mut buffer = CircularBuffer::new(5);
262 buffer.add_chunk(&[1.0, 2.0, 3.0]);
263
264 let mut output = vec![0.0; 4]; buffer.copy_to_buffer(&mut output);
266 }
267}