1#[derive(Debug, Clone, Copy, PartialEq)]
7pub enum PitchAlgorithm {
8 Auto,
10 PYIN,
12 YIN,
14 Autocorr,
16}
17
18#[derive(Debug, Clone, PartialEq)]
20pub struct AnalysisConfig {
21 pub sample_rate: u32,
23 pub frame_size: usize,
25 pub hop_size: usize,
27 pub min_frequency: f32,
29 pub max_frequency: f32,
31 pub algorithm: PitchAlgorithm,
33 pub threshold: f32,
35 pub min_confidence: f32,
37 pub interpolate: bool,
39}
40
41impl Default for AnalysisConfig {
42 fn default() -> Self {
43 Self {
44 sample_rate: 16000,
45 frame_size: 2048,
46 hop_size: 1024,
47 min_frequency: 80.0,
48 max_frequency: 400.0,
49 algorithm: PitchAlgorithm::Auto,
50 threshold: 0.15,
51 min_confidence: 0.5,
52 interpolate: true,
53 }
54 }
55}
56
57impl AnalysisConfig {
58 pub fn new() -> Self {
60 Self::default()
61 }
62
63 pub fn with_sample_rate(mut self, sample_rate: u32) -> Self {
65 self.sample_rate = sample_rate;
66 self
67 }
68
69 pub fn with_frame_size(mut self, frame_size: usize) -> Self {
71 self.frame_size = frame_size;
72 self
73 }
74
75 pub fn with_hop_size(mut self, hop_size: usize) -> Self {
77 self.hop_size = hop_size;
78 self
79 }
80
81 pub fn with_min_frequency(mut self, min_frequency: f32) -> Self {
83 self.min_frequency = min_frequency;
84 self
85 }
86
87 pub fn with_max_frequency(mut self, max_frequency: f32) -> Self {
89 self.max_frequency = max_frequency;
90 self
91 }
92
93 pub fn with_algorithm(mut self, algorithm: PitchAlgorithm) -> Self {
95 self.algorithm = algorithm;
96 self
97 }
98
99 pub fn with_threshold(mut self, threshold: f32) -> Self {
101 self.threshold = threshold;
102 self
103 }
104
105 pub fn with_min_confidence(mut self, min_confidence: f32) -> Self {
107 self.min_confidence = min_confidence;
108 self
109 }
110
111 pub fn with_interpolate(mut self, interpolate: bool) -> Self {
113 self.interpolate = interpolate;
114 self
115 }
116
117 pub fn validate(&self) -> Result<(), String> {
128 if self.sample_rate == 0 {
129 return Err("sample_rate must be greater than 0".into());
130 }
131
132 if self.min_frequency >= self.max_frequency {
133 return Err(format!(
134 "min_frequency ({}) must be less than max_frequency ({})",
135 self.min_frequency, self.max_frequency
136 ));
137 }
138
139 if self.min_frequency <= 0.0 {
140 return Err("min_frequency must be greater than 0".into());
141 }
142
143 if self.frame_size < 512 {
144 return Err("frame_size must be at least 512 samples".into());
145 }
146
147 if self.hop_size == 0 {
148 return Err("hop_size must be greater than 0".into());
149 }
150
151 if self.hop_size > self.frame_size {
152 return Err(format!(
153 "hop_size ({}) cannot exceed frame_size ({})",
154 self.hop_size, self.frame_size
155 ));
156 }
157
158 if !(0.0..=1.0).contains(&self.threshold) {
159 return Err(format!(
160 "threshold ({}) must be in range [0.0, 1.0]",
161 self.threshold
162 ));
163 }
164
165 if !(0.0..=1.0).contains(&self.min_confidence) {
166 return Err(format!(
167 "min_confidence ({}) must be in range [0.0, 1.0]",
168 self.min_confidence
169 ));
170 }
171
172 Ok(())
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn test_default_config() {
182 let config = AnalysisConfig::default();
183 assert_eq!(config.sample_rate, 16000);
184 assert_eq!(config.frame_size, 2048);
185 assert_eq!(config.hop_size, 1024);
186 assert_eq!(config.min_frequency, 80.0);
187 assert_eq!(config.max_frequency, 400.0);
188 assert_eq!(config.algorithm, PitchAlgorithm::Auto);
189 assert_eq!(config.threshold, 0.15);
190 assert_eq!(config.min_confidence, 0.5);
191 assert!(config.interpolate);
192 }
193
194 #[test]
195 fn test_builder_pattern() {
196 let config = AnalysisConfig::default()
197 .with_sample_rate(48000)
198 .with_frame_size(4096)
199 .with_algorithm(PitchAlgorithm::PYIN);
200
201 assert_eq!(config.sample_rate, 48000);
202 assert_eq!(config.frame_size, 4096);
203 assert_eq!(config.algorithm, PitchAlgorithm::PYIN);
204 }
205
206 #[test]
207 fn test_validation_valid() {
208 let config = AnalysisConfig::default();
209 assert!(config.validate().is_ok());
210 }
211
212 #[test]
213 fn test_validation_min_max_frequency() {
214 let config = AnalysisConfig::default()
215 .with_min_frequency(400.0)
216 .with_max_frequency(80.0);
217 assert!(config.validate().is_err());
218
219 let config = AnalysisConfig::default()
220 .with_min_frequency(200.0)
221 .with_max_frequency(200.0);
222 assert!(config.validate().is_err());
223 }
224
225 #[test]
226 fn test_validation_frame_size() {
227 let config = AnalysisConfig::default().with_frame_size(256);
228 assert!(config.validate().is_err());
229 }
230
231 #[test]
232 fn test_validation_hop_size() {
233 let config = AnalysisConfig::default()
234 .with_frame_size(1024)
235 .with_hop_size(2048);
236 assert!(config.validate().is_err());
237
238 let config = AnalysisConfig::default().with_hop_size(0);
239 assert!(config.validate().is_err());
240 }
241
242 #[test]
243 fn test_validation_threshold() {
244 let config = AnalysisConfig::default().with_threshold(-0.1);
245 assert!(config.validate().is_err());
246
247 let config = AnalysisConfig::default().with_threshold(1.5);
248 assert!(config.validate().is_err());
249 }
250
251 #[test]
252 fn test_validation_min_confidence() {
253 let config = AnalysisConfig::default().with_min_confidence(-0.1);
254 assert!(config.validate().is_err());
255
256 let config = AnalysisConfig::default().with_min_confidence(1.5);
257 assert!(config.validate().is_err());
258 }
259
260 #[test]
261 fn test_pitch_algorithm_enum() {
262 let auto = PitchAlgorithm::Auto;
263 let pyin = PitchAlgorithm::PYIN;
264 let yin = PitchAlgorithm::YIN;
265 let autocorr = PitchAlgorithm::Autocorr;
266
267 assert_eq!(auto, PitchAlgorithm::Auto);
268 assert_ne!(auto, pyin);
269 assert_ne!(pyin, yin);
270 assert_ne!(yin, autocorr);
271 }
272}