Skip to content

Commit 7c1542b

Browse files
authored
feat: YIN algorithm (#940)
1 parent db3f011 commit 7c1542b

File tree

4 files changed

+344
-0
lines changed

4 files changed

+344
-0
lines changed

DIRECTORY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,8 @@
294294
* [Ternary Search Min Max](https://github.com/TheAlgorithms/Rust/blob/master/src/searching/ternary_search_min_max.rs)
295295
* [Ternary Search Min Max Recursive](https://github.com/TheAlgorithms/Rust/blob/master/src/searching/ternary_search_min_max_recursive.rs)
296296
* [Ternary Search Recursive](https://github.com/TheAlgorithms/Rust/blob/master/src/searching/ternary_search_recursive.rs)
297+
* Signal Analysis
298+
* [YIN](https://github.com/TheAlgorithms/Rust/blob/master/src/signal_analysis/yin.rs)
297299
* Sorting
298300
* [Bead Sort](https://github.com/TheAlgorithms/Rust/blob/master/src/sorting/bead_sort.rs)
299301
* [Binary Insertion Sort](https://github.com/TheAlgorithms/Rust/blob/master/src/sorting/binary_insertion_sort.rs)

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub mod math;
1616
pub mod navigation;
1717
pub mod number_theory;
1818
pub mod searching;
19+
pub mod signal_analysis;
1920
pub mod sorting;
2021
pub mod string;
2122

src/signal_analysis/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
mod yin;
2+
pub use self::yin::{Yin, YinResult};

src/signal_analysis/yin.rs

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
use std::f64;
2+
3+
#[derive(Clone, Debug)]
4+
pub struct YinResult {
5+
sample_rate: f64,
6+
best_lag: usize,
7+
cmndf: Vec<f64>,
8+
}
9+
10+
impl YinResult {
11+
pub fn get_frequency(&self) -> f64 {
12+
self.sample_rate / self.best_lag as f64
13+
}
14+
15+
pub fn get_frequency_with_interpolation(&self) -> f64 {
16+
let best_lag_with_interpolation = parabolic_interpolation(self.best_lag, &self.cmndf);
17+
self.sample_rate / best_lag_with_interpolation
18+
}
19+
}
20+
21+
fn parabolic_interpolation(lag: usize, cmndf: &[f64]) -> f64 {
22+
let x0 = lag.saturating_sub(1); // max(0, lag-1)
23+
let x2 = usize::min(cmndf.len() - 1, lag + 1);
24+
let s0 = cmndf[x0];
25+
let s1 = cmndf[lag];
26+
let s2 = cmndf[x2];
27+
let denom = s0 - 2.0 * s1 + s2;
28+
if denom == 0.0 {
29+
return lag as f64;
30+
}
31+
let delta = (s0 - s2) / (2.0 * denom);
32+
lag as f64 + delta
33+
}
34+
35+
#[derive(Clone, Debug)]
36+
pub struct Yin {
37+
threshold: f64,
38+
min_lag: usize,
39+
max_lag: usize,
40+
sample_rate: f64,
41+
}
42+
43+
impl Yin {
44+
pub fn init(
45+
threshold: f64,
46+
min_expected_frequency: f64,
47+
max_expected_frequency: f64,
48+
sample_rate: f64,
49+
) -> Yin {
50+
let min_lag = (sample_rate / max_expected_frequency) as usize;
51+
let max_lag = (sample_rate / min_expected_frequency) as usize;
52+
Yin {
53+
threshold,
54+
min_lag,
55+
max_lag,
56+
sample_rate,
57+
}
58+
}
59+
60+
pub fn yin(&self, frequencies: &[f64]) -> Result<YinResult, String> {
61+
let df = difference_function_values(frequencies, self.max_lag);
62+
let cmndf = cumulative_mean_normalized_difference_function(&df, self.max_lag);
63+
let best_lag = find_cmndf_argmin(&cmndf, self.min_lag, self.max_lag, self.threshold);
64+
match best_lag {
65+
_ if best_lag == 0 => Err(format!(
66+
"Could not find lag value which minimizes CMNDF below the given threshold {}",
67+
self.threshold
68+
)),
69+
_ => Ok(YinResult {
70+
sample_rate: self.sample_rate,
71+
best_lag,
72+
cmndf,
73+
}),
74+
}
75+
}
76+
}
77+
78+
#[allow(clippy::needless_range_loop)]
79+
fn difference_function_values(frequencies: &[f64], max_lag: usize) -> Vec<f64> {
80+
let mut df_list = vec![0.0; max_lag + 1];
81+
for lag in 1..=max_lag {
82+
df_list[lag] = difference_function(frequencies, lag);
83+
}
84+
df_list
85+
}
86+
87+
fn difference_function(f: &[f64], lag: usize) -> f64 {
88+
let mut sum = 0.0;
89+
let n = f.len();
90+
for i in 0..(n - lag) {
91+
let diff = f[i] - f[i + lag];
92+
sum += diff * diff;
93+
}
94+
sum
95+
}
96+
97+
const EPSILON: f64 = 1e-10;
98+
fn cumulative_mean_normalized_difference_function(df: &[f64], max_lag: usize) -> Vec<f64> {
99+
let mut cmndf = vec![0.0; max_lag + 1];
100+
cmndf[0] = 1.0;
101+
let mut sum = 0.0;
102+
for lag in 1..=max_lag {
103+
sum += df[lag];
104+
cmndf[lag] = lag as f64 * df[lag] / if sum == 0.0 { EPSILON } else { sum };
105+
}
106+
cmndf
107+
}
108+
109+
fn find_cmndf_argmin(cmndf: &[f64], min_lag: usize, max_lag: usize, threshold: f64) -> usize {
110+
let mut lag = min_lag;
111+
while lag <= max_lag {
112+
if cmndf[lag] < threshold {
113+
while lag < max_lag && cmndf[lag + 1] < cmndf[lag] {
114+
lag += 1;
115+
}
116+
return lag;
117+
}
118+
lag += 1;
119+
}
120+
0
121+
}
122+
123+
#[cfg(test)]
124+
mod tests {
125+
use super::*;
126+
127+
fn generate_sine_wave(frequency: f64, sample_rate: f64, duration_secs: f64) -> Vec<f64> {
128+
let total_samples = (sample_rate * duration_secs).round() as usize;
129+
let two_pi_f = 2.0 * std::f64::consts::PI * frequency;
130+
131+
(0..total_samples)
132+
.map(|n| {
133+
let t = n as f64 / sample_rate;
134+
(two_pi_f * t).sin()
135+
})
136+
.collect()
137+
}
138+
139+
fn diff_from_actual_frequency_smaller_than_threshold(
140+
result_frequency: f64,
141+
actual_frequency: f64,
142+
threshold: f64,
143+
) -> bool {
144+
let result_diff_from_actual_freq = (result_frequency - actual_frequency).abs();
145+
result_diff_from_actual_freq < threshold
146+
}
147+
148+
fn interpolation_better_than_raw_result(result: YinResult, frequency: f64) -> bool {
149+
let result_frequency = result.get_frequency();
150+
let refined_frequency = result.get_frequency_with_interpolation();
151+
let result_diff = (result_frequency - frequency).abs();
152+
let refined_diff = (refined_frequency - frequency).abs();
153+
refined_diff < result_diff
154+
}
155+
156+
#[test]
157+
fn test_simple_sine() {
158+
let sample_rate = 1000.0;
159+
let frequency = 12.0;
160+
let seconds = 10.0;
161+
let signal = generate_sine_wave(frequency, sample_rate, seconds);
162+
163+
let min_expected_frequency = 10.0;
164+
let max_expected_frequency = 100.0;
165+
166+
let yin = Yin::init(
167+
0.1,
168+
min_expected_frequency,
169+
max_expected_frequency,
170+
sample_rate,
171+
);
172+
173+
let result = yin.yin(signal.as_slice());
174+
assert!(result.is_ok());
175+
let yin_result = result.unwrap();
176+
177+
assert!(diff_from_actual_frequency_smaller_than_threshold(
178+
yin_result.get_frequency(),
179+
frequency,
180+
1.0
181+
));
182+
assert!(diff_from_actual_frequency_smaller_than_threshold(
183+
yin_result.get_frequency_with_interpolation(),
184+
frequency,
185+
1.0,
186+
));
187+
188+
assert!(interpolation_better_than_raw_result(yin_result, frequency));
189+
}
190+
191+
#[test]
192+
fn test_sine_frequency_range() {
193+
let sample_rate = 5000.0;
194+
for freq in 30..50 {
195+
let frequency = freq as f64;
196+
let seconds = 2.0;
197+
let signal = generate_sine_wave(frequency, sample_rate, seconds);
198+
199+
let min_expected_frequency = 5.0;
200+
let max_expected_frequency = 100.0;
201+
202+
let yin = Yin::init(
203+
0.1,
204+
min_expected_frequency,
205+
max_expected_frequency,
206+
sample_rate,
207+
);
208+
let result = yin.yin(signal.as_slice());
209+
assert!(result.is_ok());
210+
let yin_result = result.unwrap();
211+
212+
if (sample_rate as i32 % freq) == 0 {
213+
assert_eq!(yin_result.get_frequency(), frequency);
214+
} else {
215+
assert!(diff_from_actual_frequency_smaller_than_threshold(
216+
yin_result.get_frequency(),
217+
frequency,
218+
1.0
219+
));
220+
assert!(diff_from_actual_frequency_smaller_than_threshold(
221+
yin_result.get_frequency_with_interpolation(),
222+
frequency,
223+
1.0,
224+
));
225+
226+
assert!(interpolation_better_than_raw_result(yin_result, frequency));
227+
}
228+
}
229+
}
230+
231+
#[test]
232+
fn test_harmonic_sines() {
233+
let sample_rate = 44100.0;
234+
let seconds = 2.0;
235+
let frequency_1 = 50.0; // Minimal/Fundamental frequency - this is what YIN should find
236+
let signal_1 = generate_sine_wave(frequency_1, sample_rate, seconds);
237+
let frequency_2 = 150.0;
238+
let signal_2 = generate_sine_wave(frequency_2, sample_rate, seconds);
239+
let frequency_3 = 300.0;
240+
let signal_3 = generate_sine_wave(frequency_3, sample_rate, seconds);
241+
242+
let min_expected_frequency = 10.0;
243+
let max_expected_frequency = 500.0;
244+
245+
let yin = Yin::init(
246+
0.1,
247+
min_expected_frequency,
248+
max_expected_frequency,
249+
sample_rate,
250+
);
251+
252+
let total_samples = (sample_rate * seconds).round() as usize;
253+
let combined_signal: Vec<f64> = (0..total_samples)
254+
.map(|n| signal_1[n] + signal_2[n] + signal_3[n])
255+
.collect();
256+
257+
let result = yin.yin(&combined_signal);
258+
assert!(result.is_ok());
259+
let yin_result = result.unwrap();
260+
261+
assert!(diff_from_actual_frequency_smaller_than_threshold(
262+
yin_result.get_frequency(),
263+
frequency_1,
264+
1.0
265+
));
266+
}
267+
268+
#[test]
269+
fn test_unharmonic_sines() {
270+
let sample_rate = 44100.0;
271+
let seconds = 2.0;
272+
let frequency_1 = 50.0;
273+
let signal_1 = generate_sine_wave(frequency_1, sample_rate, seconds);
274+
let frequency_2 = 66.0;
275+
let signal_2 = generate_sine_wave(frequency_2, sample_rate, seconds);
276+
let frequency_3 = 300.0;
277+
let signal_3 = generate_sine_wave(frequency_3, sample_rate, seconds);
278+
279+
let min_expected_frequency = 10.0;
280+
let max_expected_frequency = 500.0;
281+
282+
let yin = Yin::init(
283+
0.1,
284+
min_expected_frequency,
285+
max_expected_frequency,
286+
sample_rate,
287+
);
288+
289+
let total_samples = (sample_rate * seconds).round() as usize;
290+
let combined_signal: Vec<f64> = (0..total_samples)
291+
.map(|n| signal_1[n] + signal_2[n] + signal_3[n])
292+
.collect();
293+
294+
let result = yin.yin(&combined_signal);
295+
assert!(result.is_ok());
296+
let yin_result = result.unwrap();
297+
298+
let expected_frequency = (frequency_1 - frequency_2).abs();
299+
assert!(diff_from_actual_frequency_smaller_than_threshold(
300+
yin_result.get_frequency(),
301+
expected_frequency,
302+
1.0
303+
));
304+
assert!(interpolation_better_than_raw_result(
305+
yin_result,
306+
expected_frequency
307+
));
308+
}
309+
310+
#[test]
311+
fn test_err() {
312+
let sample_rate = 2500.0;
313+
let seconds = 2.0;
314+
let frequency = 440.0;
315+
316+
// Can't find frequency 440 between 500 and 700
317+
let min_expected_frequency = 500.0;
318+
let max_expected_frequency = 700.0;
319+
let yin = Yin::init(
320+
0.1,
321+
min_expected_frequency,
322+
max_expected_frequency,
323+
sample_rate,
324+
);
325+
326+
let signal = generate_sine_wave(frequency, sample_rate, seconds);
327+
let result = yin.yin(&signal);
328+
assert!(result.is_err());
329+
330+
let yin_with_suitable_frequency_range = Yin::init(
331+
0.1,
332+
min_expected_frequency - 100.0,
333+
max_expected_frequency,
334+
sample_rate,
335+
);
336+
let result = yin_with_suitable_frequency_range.yin(&signal);
337+
assert!(result.is_ok());
338+
}
339+
}

0 commit comments

Comments
 (0)