summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJustin Worthe <justin.worthe@gmail.com>2017-04-18 21:30:06 +0200
committerJustin Worthe <justin.worthe@gmail.com>2017-04-18 21:30:06 +0200
commit15f2b84c46cc81199f76643af2d030d2ab768601 (patch)
treee3df761c5c114789de11884e982e28023e3a5b80 /src
parentd9c2952c36f272faba767081bf4c96cc4aa868e2 (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.
Diffstat (limited to 'src')
-rw-r--r--src/gui.rs41
-rw-r--r--src/transforms.rs9
2 files changed, 48 insertions, 2 deletions
diff --git a/src/gui.rs b/src/gui.rs
index e1901c1..f9cf53f 100644
--- a/src/gui.rs
+++ b/src/gui.rs
@@ -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()
+}