Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 7c1542b

Browse files
authored
feat: YIN algorithm (TheAlgorithms#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)

AltStyle によって変換されたページ (->オリジナル) /