summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJustin Worthe <justin.worthe@gmail.com>2017-03-25 12:46:14 +0200
committerJustin Worthe <justin.worthe@gmail.com>2017-03-25 12:46:14 +0200
commitd9c2952c36f272faba767081bf4c96cc4aa868e2 (patch)
tree3761263c4b4d034973ccbe04d23c36060e12b0ae /src
parentbbbb024a37fc5ea2938537cc567c2eb6a294aaab (diff)
Added a graphical indication of the pith's sharpness or flatness
I'm not 100% happy with the precision of the correlation function at this point. I'm hoping that with a bit more work, I can get it to be more precise, and then the indicator will be more meaningful.
Diffstat (limited to 'src')
-rw-r--r--src/gui.rs41
-rw-r--r--src/transforms.rs22
2 files changed, 56 insertions, 7 deletions
diff --git a/src/gui.rs b/src/gui.rs
index 8ec87d9..e1901c1 100644
--- a/src/gui.rs
+++ b/src/gui.rs
@@ -13,6 +13,7 @@ use std::sync::mpsc::*;
struct RustyUi {
dropdown: gtk::ComboBoxText,
pitch_label: gtk::Label,
+ pitch_error_indicator: gtk::DrawingArea,
freq_chart: gtk::DrawingArea,
correlation_chart: gtk::DrawingArea
}
@@ -25,6 +26,7 @@ struct ApplicationState {
struct CrossThreadState {
pitch: String,
+ error: f64,
freq_spectrum: Vec<::transforms::FrequencyBucket>,
correlation: Vec<f64>
}
@@ -43,6 +45,7 @@ pub fn start_gui() -> Result<(), String> {
let cross_thread_state = Arc::new(RwLock::new(CrossThreadState {
pitch: String::new(),
+ error: 0.0,
freq_spectrum: Vec::new(),
correlation: Vec::new()
}));
@@ -53,6 +56,7 @@ pub fn start_gui() -> Result<(), String> {
start_processing_audio(mic_receiver, cross_thread_state.clone());
setup_pitch_label_callbacks(state.clone(), cross_thread_state.clone());
+ setup_pitch_error_indicator_callbacks(state.clone(), cross_thread_state.clone());
setup_freq_drawing_area_callbacks(state.clone(), cross_thread_state.clone());
setup_correlation_drawing_area_callbacks(state.clone(), cross_thread_state.clone());
@@ -78,6 +82,10 @@ fn create_window(microphones: Vec<(u32, String)>) -> RustyUi {
let pitch_label = gtk::Label::new(None);
layout_box.add(&pitch_label);
+ let pitch_error_indicator = gtk::DrawingArea::new();
+ pitch_error_indicator.set_size_request(600, 50);
+ layout_box.add(&pitch_error_indicator);
+
let freq_chart = gtk::DrawingArea::new();
freq_chart.set_size_request(600, 400);
layout_box.add(&freq_chart);
@@ -91,6 +99,7 @@ fn create_window(microphones: Vec<(u32, String)>) -> RustyUi {
RustyUi {
dropdown: dropdown,
pitch_label: pitch_label,
+ pitch_error_indicator: pitch_error_indicator,
freq_chart: freq_chart,
correlation_chart: correlation_chart
}
@@ -141,9 +150,9 @@ fn start_processing_audio(mic_receiver: Receiver<Vec<f64>>, cross_thread_state:
let frequency_domain = ::transforms::fft(&samples, 44100.0);
let correlation = ::transforms::correlation(&samples);
let fundamental = ::transforms::find_fundamental_frequency_correlation(&samples, 44100.0);
- let pitch = match fundamental {
- Some(fundamental) => ::transforms::hz_to_pitch(fundamental),
- None => "".to_string()
+ let (pitch, error) = match fundamental {
+ Some(fundamental) => (::transforms::hz_to_pitch(fundamental), ::transforms::hz_to_cents_error(fundamental)),
+ None => ("".to_string(), 0.0)
};
match cross_thread_state.write() {
@@ -151,6 +160,7 @@ fn start_processing_audio(mic_receiver: Receiver<Vec<f64>>, cross_thread_state:
state.pitch = pitch;
state.freq_spectrum = frequency_domain;
state.correlation = correlation;
+ state.error = error
},
Err(_) => {}
};
@@ -163,6 +173,7 @@ fn setup_pitch_label_callbacks(state: Rc<RefCell<ApplicationState>>, cross_threa
let ref pitch = cross_thread_state.read().unwrap().pitch;
let ref ui = state.borrow().ui;
ui.pitch_label.set_label(pitch.as_ref());
+ ui.pitch_error_indicator.queue_draw();
ui.correlation_chart.queue_draw();
ui.freq_chart.queue_draw();
@@ -170,6 +181,30 @@ fn setup_pitch_label_callbacks(state: Rc<RefCell<ApplicationState>>, cross_threa
});
}
+fn setup_pitch_error_indicator_callbacks(state: Rc<RefCell<ApplicationState>>, cross_thread_state: Arc<RwLock<CrossThreadState>>) {
+ let outer_state = state.clone();
+ 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;
+
+ //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.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.fill();
+
+ gtk::Inhibit(false)
+ });
+}
+
+
fn setup_freq_drawing_area_callbacks(state: Rc<RefCell<ApplicationState>>, cross_thread_state: Arc<RwLock<CrossThreadState>>) {
let outer_state = state.clone();
let ref canvas = outer_state.borrow().ui.freq_chart;
diff --git a/src/transforms.rs b/src/transforms.rs
index 9eee406..0ff3e17 100644
--- a/src/transforms.rs
+++ b/src/transforms.rs
@@ -77,9 +77,7 @@ pub fn find_fundamental_frequency_correlation(input: &Vec<f64>, sample_rate: f64
.fold((0, 0.0 as f64), |(xi, xmag), (yi, &ymag)| if ymag > xmag { (yi, ymag) } else { (xi, xmag) });
let (peak_index, _) = peak;
-
- let peak_period = peak_index as f64 / sample_rate;
- Some(peak_period.recip())
+ Some(sample_rate / peak_index as f64)
}
#[cfg(test)]
@@ -160,6 +158,21 @@ mod tests {
}
}
+pub fn hz_to_midi_number(hz: f64) -> f64 {
+ 69.0 + 12.0 * (hz / 440.0).log2()
+}
+
+pub fn hz_to_cents_error(hz: f64) -> f64 {
+ let midi_number = hz_to_midi_number(hz);
+ let cents = (midi_number * 100.0).round() % 100.0;
+ if cents >= 50.0 {
+ cents - 100.0
+ }
+ else {
+ cents
+ }
+}
+
pub fn hz_to_pitch(hz: f64) -> String {
let pitch_names = [
"C",
@@ -176,7 +189,7 @@ pub fn hz_to_pitch(hz: f64) -> String {
"B"
];
- let midi_number = 69.0 + 12.0 * (hz / 440.0).log2();
+ let midi_number = hz_to_midi_number(hz);
//midi_number of 0 is C-1.
let rounded_pitch = midi_number.round() as i32;
@@ -188,6 +201,7 @@ pub fn hz_to_pitch(hz: f64) -> String {
let mut cents = ((midi_number * 100.0).round() % 100.0) as i32;
if cents >= 50 {
+ //don't need to adjust pitch here, that's already been done by the round above
cents -= 100;
}