diff options
author | Justin Worthe <justin.worthe@gmail.com> | 2017-04-18 21:30:06 +0200 |
---|---|---|
committer | Justin Worthe <justin.worthe@gmail.com> | 2017-04-18 21:30:06 +0200 |
commit | 15f2b84c46cc81199f76643af2d030d2ab768601 (patch) | |
tree | e3df761c5c114789de11884e982e28023e3a5b80 | |
parent | d9c2952c36f272faba767081bf4c96cc4aa868e2 (diff) |
Added basic oscilloscope functionality to watch the waveform
The triggering algorithm isn't great at the moment. Seems the
interesting signals end up crossing zero a few times. I need to take
things like maxima into account as well.
I'm also running out of vertical space. I need to start making the GUI a
bit smarter, and allow turning some of these graphs on/off at runtime.
-rw-r--r-- | src/gui.rs | 41 | ||||
-rw-r--r-- | src/transforms.rs | 9 |
2 files changed, 48 insertions, 2 deletions
@@ -14,6 +14,7 @@ struct RustyUi { dropdown: gtk::ComboBoxText, pitch_label: gtk::Label, pitch_error_indicator: gtk::DrawingArea, + oscilloscope_chart: gtk::DrawingArea, freq_chart: gtk::DrawingArea, correlation_chart: gtk::DrawingArea } @@ -27,6 +28,7 @@ struct ApplicationState { struct CrossThreadState { pitch: String, error: f64, + signal: Vec<f64>, freq_spectrum: Vec<::transforms::FrequencyBucket>, correlation: Vec<f64> } @@ -46,6 +48,7 @@ pub fn start_gui() -> Result<(), String> { let cross_thread_state = Arc::new(RwLock::new(CrossThreadState { pitch: String::new(), error: 0.0, + signal: Vec::new(), freq_spectrum: Vec::new(), correlation: Vec::new() })); @@ -57,6 +60,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_oscilloscope_drawing_area_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()); @@ -85,13 +89,17 @@ fn create_window(microphones: Vec<(u32, String)>) -> RustyUi { let pitch_error_indicator = gtk::DrawingArea::new(); pitch_error_indicator.set_size_request(600, 50); layout_box.add(&pitch_error_indicator); + + let oscilloscope_chart = gtk::DrawingArea::new(); + oscilloscope_chart.set_size_request(600, 250); + layout_box.add(&oscilloscope_chart); let freq_chart = gtk::DrawingArea::new(); - freq_chart.set_size_request(600, 400); + freq_chart.set_size_request(600, 250); layout_box.add(&freq_chart); let correlation_chart = gtk::DrawingArea::new(); - correlation_chart.set_size_request(600, 400); + correlation_chart.set_size_request(600, 250); layout_box.add(&correlation_chart); window.show_all(); @@ -100,6 +108,7 @@ fn create_window(microphones: Vec<(u32, String)>) -> RustyUi { dropdown: dropdown, pitch_label: pitch_label, pitch_error_indicator: pitch_error_indicator, + oscilloscope_chart: oscilloscope_chart, freq_chart: freq_chart, correlation_chart: correlation_chart } @@ -147,6 +156,7 @@ fn start_processing_audio(mic_receiver: Receiver<Vec<f64>>, cross_thread_state: None => {continue;} }; + let signal = ::transforms::align_to_rising_edge(&samples); let frequency_domain = ::transforms::fft(&samples, 44100.0); let correlation = ::transforms::correlation(&samples); let fundamental = ::transforms::find_fundamental_frequency_correlation(&samples, 44100.0); @@ -158,6 +168,7 @@ fn start_processing_audio(mic_receiver: Receiver<Vec<f64>>, cross_thread_state: match cross_thread_state.write() { Ok(mut state) => { state.pitch = pitch; + state.signal = signal; state.freq_spectrum = frequency_domain; state.correlation = correlation; state.error = error @@ -174,6 +185,7 @@ fn setup_pitch_label_callbacks(state: Rc<RefCell<ApplicationState>>, cross_threa let ref ui = state.borrow().ui; ui.pitch_label.set_label(pitch.as_ref()); ui.pitch_error_indicator.queue_draw(); + ui.oscilloscope_chart.queue_draw(); ui.correlation_chart.queue_draw(); ui.freq_chart.queue_draw(); @@ -204,6 +216,31 @@ fn setup_pitch_error_indicator_callbacks(state: Rc<RefCell<ApplicationState>>, c }); } +fn setup_oscilloscope_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.oscilloscope_chart; + canvas.connect_draw(move |ref canvas, ref context| { + let ref signal = cross_thread_state.read().unwrap().signal; + let width = canvas.get_allocated_width() as f64; + 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 }); + + context.new_path(); + context.move_to(0.0, mid_height); + + for (i, intensity) in signal.iter().enumerate() { + let x = i as f64 * width / len; + let y = mid_height - (intensity * mid_height / max); + context.line_to(x, y); + } + + context.stroke(); + + gtk::Inhibit(false) + }); +} fn setup_freq_drawing_area_callbacks(state: Rc<RefCell<ApplicationState>>, cross_thread_state: Arc<RwLock<CrossThreadState>>) { let outer_state = state.clone(); diff --git a/src/transforms.rs b/src/transforms.rs index 0ff3e17..72aad8a 100644 --- a/src/transforms.rs +++ b/src/transforms.rs @@ -227,3 +227,12 @@ fn c4_is_correct() { fn f5_is_correct() { assert_eq!(hz_to_pitch(698.46), "F5 +0"); } + + +pub fn align_to_rising_edge(samples: &Vec<f64>) -> Vec<f64> { + samples.iter() + .skip_while(|x| !x.is_sign_negative()) + .skip_while(|x| x.is_sign_negative()) + .cloned() + .collect() +} |