summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJustin Worthe <justin@worthe-it.co.za>2017-07-03 20:57:16 +0200
committerJustin Worthe <justin@worthe-it.co.za>2017-07-03 20:57:16 +0200
commitf7b208ff5c3c9d77465e745bc9ded1d87ed32335 (patch)
tree540118fb9be13afac90e32b1d27b6aee07008e9c /src
parent9be0310e2f807be352f4ce864af42d156dd6b8d6 (diff)
Added silence and noise detection
Also updated GUI to present note and error in a friendlier way
Diffstat (limited to 'src')
-rw-r--r--src/gui.rs20
-rw-r--r--src/transforms.rs85
2 files changed, 60 insertions, 45 deletions
diff --git a/src/gui.rs b/src/gui.rs
index 6ab2b50..41668a3 100644
--- a/src/gui.rs
+++ b/src/gui.rs
@@ -106,7 +106,7 @@ fn create_window(microphones: Vec<(u32, String)>) -> RustyUi {
vbox.add(&pitch_label);
let pitch_error_indicator = gtk::DrawingArea::new();
- pitch_error_indicator.set_size_request(600, 50);
+ pitch_error_indicator.set_size_request(600, 70);
vbox.add(&pitch_error_indicator);
let oscilloscope_chart = gtk::DrawingArea::new();
@@ -224,18 +224,28 @@ fn setup_pitch_error_indicator_callbacks(state: Rc<RefCell<ApplicationState>>, c
let ref canvas = outer_state.borrow().ui.pitch_error_indicator;
canvas.connect_draw(move |ref canvas, ref context| {
let error = cross_thread_state.read().unwrap().error;
+
let width = canvas.get_allocated_width() as f64;
let midpoint = width / 2.0;
- let height = canvas.get_allocated_height() as f64;
+ let line_indicator_height = 20.0;
+ let color_indicator_height = canvas.get_allocated_height() as f64 - line_indicator_height;
+
+
+ let error_line_x = midpoint + error * midpoint / 50.0;
+ context.new_path();
+ context.move_to(error_line_x, 0.0);
+ context.line_to(error_line_x, line_indicator_height);
+ context.stroke();
+
//flat on the left
context.set_source_rgb(0.0, 0.0, if error < 0.0 {-error/50.0} else {0.0});
- context.rectangle(0.0, 0.0, midpoint, height);
+ context.rectangle(0.0, line_indicator_height, midpoint, color_indicator_height+line_indicator_height);
context.fill();
//sharp on the right
context.set_source_rgb(if error > 0.0 {error/50.0} else {0.0}, 0.0, 0.0);
- context.rectangle(midpoint, 0.0, width, height);
+ context.rectangle(midpoint, line_indicator_height, width, color_indicator_height+line_indicator_height);
context.fill();
gtk::Inhibit(false)
@@ -251,7 +261,7 @@ fn setup_oscilloscope_drawing_area_callbacks(state: Rc<RefCell<ApplicationState>
let len = 512.0; //Set as a constant so signal won't change size based on zero point.
let height = canvas.get_allocated_height() as f64;
let mid_height = height / 2.0;
- let max = signal.iter().map(|x| x.abs()).fold(0.0, |max, x| if max > x { max } else { x });
+ let max = 1.0;
context.new_path();
context.move_to(0.0, mid_height);
diff --git a/src/transforms.rs b/src/transforms.rs
index 7bcdf7c..eeb175e 100644
--- a/src/transforms.rs
+++ b/src/transforms.rs
@@ -13,16 +13,20 @@ impl FrequencyBucket {
}
}
-pub fn fft(input: &Vec<f64>, sample_rate: f64) -> Vec<FrequencyBucket> {
- let frames = input.len();
+pub fn remove_mean_offset(input: &Vec<f64>) -> Vec<f64> {
let mean_input = input.iter().sum::<f64>()/input.len() as f64;
+ input.iter().map(|x|x-mean_input).collect()
+}
+
+pub fn fft(input: &Vec<f64>, sample_rate: f64) -> Vec<FrequencyBucket> {
+ let mut intensities = remove_mean_offset(&input);
- let mut intensities = input.iter().map(|x|x-mean_input).collect::<Vec<_>>();
+ let frames = intensities.len();
let plan = dft::Plan::new(dft::Operation::Forward, frames);
- dft::transform(&mut intensities, &plan);
-
let frequency_resolution = sample_rate / 2.0 / frames as f64;
+ dft::transform(&mut intensities, &plan);
+
intensities.iter().enumerate().map(|(index, &value)| {
let index = index as f64;
FrequencyBucket {
@@ -33,21 +37,6 @@ pub fn fft(input: &Vec<f64>, sample_rate: f64) -> Vec<FrequencyBucket> {
}).collect()
}
-pub fn find_fundamental_frequency(frequency_domain: &Vec<FrequencyBucket>) -> Option<f64> {
- //TODO look at all significant frequencies, find fundamental
- //TODO return None is none of them are significant
- let positive_buckets = frequency_domain.iter().filter(|x| x.intensity > 0.0).cloned().collect::<Vec<_>>();
- let average_intensity = positive_buckets.iter().map(|x| x.intensity).sum::<f64>() / frequency_domain.len() as f64;
- let significant_buckets = positive_buckets.iter().filter(|x| x.intensity > average_intensity).cloned().collect::<Vec<_>>();
-
- let max_bucket = significant_buckets.iter()
- .fold(None as Option<::transforms::FrequencyBucket>, |max, next|
- if max.is_none() || max.clone().unwrap().intensity < next.intensity { Some(next.clone()) } else { max }
- ).unwrap();
-
- Some(max_bucket.ave_freq())
-}
-
pub fn correlation(input: &Vec<f64>) -> Vec<f64> {
let mut correlation = Vec::with_capacity(input.len());
for offset in 0..input.len() {
@@ -62,7 +51,13 @@ pub fn correlation(input: &Vec<f64>) -> Vec<f64> {
}
pub fn find_fundamental_frequency_correlation(input: &Vec<f64>, sample_rate: f64) -> Option<f64> {
- let correlation = correlation(&input);
+ let intensities = remove_mean_offset(&input);
+
+ if intensities.iter().all(|&x| x.abs() < 0.1) {
+ return None;
+ }
+
+ let correlation = correlation(&intensities);
let mut first_peak_width = 0;
for offset in 0..correlation.len() {
@@ -83,8 +78,13 @@ pub fn find_fundamental_frequency_correlation(input: &Vec<f64>, sample_rate: f64
let (peak_index, _) = peak;
let refined_peak_index = refine_fundamentals(&correlation, peak_index as f64);
-
- Some(sample_rate / refined_peak_index)
+
+ if is_noise(&correlation, refined_peak_index) {
+ None
+ }
+ else {
+ Some(sample_rate / refined_peak_index)
+ }
}
fn refine_fundamentals(correlation: &Vec<f64>, initial_guess: f64) -> f64 {
@@ -107,15 +107,19 @@ fn refine_fundamentals(correlation: &Vec<f64>, initial_guess: f64) -> f64 {
(low_bound + high_bound) / 2.0
}
+fn is_noise(correlation: &Vec<f64>, fundamental: f64) -> bool {
+ let value_at_point = interpolate(&correlation, fundamental);
+ let score_data_points = 2 * correlation.len() / fundamental.ceil() as usize;
+ let score = score_guess(&correlation, fundamental, score_data_points);
+
+ value_at_point > 2.0*score
+}
+
fn score_guess(correlation: &Vec<f64>, period: f64, data_points: usize) -> f64 {
let mut score = 0.0;
- for i in 0..data_points {
- if i % 2 == 0 {
- score += i as f64 * interpolate(&correlation, i as f64 * period / 2.0);
- }
- else {
- score -= i as f64 * interpolate(&correlation, i as f64 * period / 2.0);
- }
+ for i in 1..data_points {
+ let expected_sign = if i % 2 == 0 { 1.0 } else { -1.0 };
+ score += expected_sign * 0.5 * i as f64 * interpolate(&correlation, i as f64 * period / 2.0);
}
score
}
@@ -135,7 +139,7 @@ fn interpolate(correlation: &Vec<f64>, x: f64) -> f64 {
let x1 = x.ceil();
let y1 = correlation[x1 as usize];
- if (x0 as usize == x1 as usize) {
+ if x0 as usize == x1 as usize {
y0
}
else {
@@ -244,18 +248,18 @@ pub fn hz_to_cents_error(hz: f64) -> f64 {
pub fn hz_to_pitch(hz: f64) -> String {
let pitch_names = [
- "C",
+ "C ",
"C#",
- "D",
+ "D ",
"Eb",
- "E",
- "F",
+ "E ",
+ "F ",
"F#",
- "G",
+ "G ",
"G#",
- "A",
+ "A ",
"Bb",
- "B"
+ "B "
];
let midi_number = hz_to_midi_number(hz);
@@ -273,8 +277,9 @@ pub fn hz_to_pitch(hz: f64) -> String {
//don't need to adjust pitch here, that's already been done by the round above
cents -= 100;
}
-
- format!("{}{} {:+}", name, octave, cents)
+
+ format!("{}{}", name, octave)
+// format!("{}{} {:+}", name, octave, cents)
}
#[test]