diff options
author | Justin Worthe <justin.worthe@gmail.com> | 2016-09-25 16:19:58 +0200 |
---|---|---|
committer | Justin Worthe <justin.worthe@gmail.com> | 2016-09-25 16:19:58 +0200 |
commit | 59607a6ba83048d8f92920d974f4ab2c6ecbaae6 (patch) | |
tree | 92a92d2e12409700049188414648d796f6757797 |
GUI, lists PA devices, backend can watch audio stream
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Cargo.lock | 304 | ||||
-rw-r--r-- | Cargo.toml | 9 | ||||
-rw-r--r-- | src/audio.rs | 74 | ||||
-rw-r--r-- | src/gui.rs | 28 | ||||
-rw-r--r-- | src/lib.rs | 6 | ||||
-rw-r--r-- | src/main.rs | 11 | ||||
-rw-r--r-- | src/transforms.rs | 53 |
8 files changed, 486 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..7e12927 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,304 @@ +[root] +name = "musician_training" +version = "0.1.0" +dependencies = [ + "dft 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "gtk 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "portaudio 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "atk-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bitflags" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bitflags" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "c_vec" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cairo-rs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "c_vec 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)", + "cairo-sys-rs 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "dft" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-complex 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gdk" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cairo-rs 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gdk-pixbuf 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gdk-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "pango 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gdk-pixbuf-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gio-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gdk-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cairo-sys-rs 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gdk-pixbuf-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gio-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "pango-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gio" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gio-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gio-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "glib" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gio-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "glib-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gobject-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gtk" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cairo-rs 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cairo-sys-rs 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gdk 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gdk-pixbuf 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gdk-pixbuf-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gdk-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gio-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gtk-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "pango 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gtk-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "atk-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cairo-sys-rs 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gdk-pixbuf-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gdk-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gio-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "pango-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", + "num-iter 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-complex" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-integer" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-iter" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-traits" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pango" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "glib 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "pango-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pango-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pkg-config" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "portaudio" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "num 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7b65fb2 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "musician_training" +version = "0.1.0" +authors = ["Justin Worthe <justin.worthe@gmail.com>"] + +[dependencies] +portaudio = "0.6.4" +dft = "0.4.3" +gtk = "0.1.0" diff --git a/src/audio.rs b/src/audio.rs new file mode 100644 index 0000000..526a227 --- /dev/null +++ b/src/audio.rs @@ -0,0 +1,74 @@ +extern crate portaudio; +use portaudio as pa; + +const SAMPLE_RATE: f64 = 44100.0; +const FRAMES: usize = 512; + +pub fn get_device_list() -> Result<Vec<(u32, String)>, pa::Error> { + let pa = try!(pa::PortAudio::new()); + let default_host = try!(pa.default_host_api()); + println!("Using default host: {:#?}", default_host); + + let mut list = Vec::new(); + let devices = try!(pa.devices()); + for device in devices { + let (pa::DeviceIndex(idx), info) = try!(device); + if info.max_input_channels == 0 { + continue; + } + list.push((idx, info.name.to_string())); + } + Ok(list) +} + +pub fn run(device_index: u32) -> Result<(), pa::Error> { + let pa = try!(pa::PortAudio::new()); + + let input_info = try!(pa.device_info(pa::DeviceIndex(device_index))); + println!("Using {} for input", input_info.name); + + // Construct the input stream parameters. + let latency = input_info.default_low_input_latency; + let input_params = pa::StreamParameters::<f32>::new(pa::DeviceIndex(device_index), 1, true, latency); + + // Check that the stream format is supported. + try!(pa.is_input_format_supported(input_params, SAMPLE_RATE)); + + // Construct the settings with which we'll open our duplex stream. + let settings = pa::InputStreamSettings::new(input_params, SAMPLE_RATE, FRAMES as u32); + + // We'll use this channel to send the count_down to the main thread for fun. + let (sender, receiver) = ::std::sync::mpsc::channel(); + + // A callback to pass to the non-blocking stream. + let callback = move |pa::InputStreamCallbackArgs { buffer, .. }| { + sender.send(buffer.iter().map(|x| *x as f64).collect()).ok(); + pa::Continue + }; + + // Construct a stream with input and output sample types of f32. + let mut stream = try!(pa.open_non_blocking_stream(settings, callback)); + + try!(stream.start()); + + let mut samples_index = 0; + // Do some stuff! + while let Ok(samples) = receiver.recv() { + samples_index += 1; + if samples_index % 100 != 0 { + continue; + } + + let frequency_domain = ::transforms::fft(samples, SAMPLE_RATE); + + let max_frequency = frequency_domain.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().max_freq; + println!("{}Hz", max_frequency.floor()); + } + + try!(stream.stop()); + + Ok(()) +} diff --git a/src/gui.rs b/src/gui.rs new file mode 100644 index 0000000..66ae1f7 --- /dev/null +++ b/src/gui.rs @@ -0,0 +1,28 @@ +use gtk; +use gtk::prelude::*; + +pub fn start_gui() -> Result<(), String> { + try!(gtk::init().map_err(|_| "Failed to initialize GTK.")); + + // Create the main window. + let window = gtk::Window::new(gtk::WindowType::Toplevel); + window.set_title("Musician Training"); + + let audio_devices = try!(::audio::get_device_list().map_err(|e| e.to_string())); + let dropdown = gtk::ComboBoxText::new(); + for (index, name) in audio_devices { + dropdown.append(Some(format!("{}", index).as_ref()), name.as_ref()); + } + window.add(&dropdown); + window.set_default_size(300, 300); + + window.show_all(); + + window.connect_delete_event(|_, _| { + gtk::main_quit(); + gtk::prelude::Inhibit(false) + }); + + gtk::main(); + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..81da674 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,6 @@ +extern crate gtk; +extern crate portaudio; + +pub mod transforms; +pub mod gui; +pub mod audio; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..0eea7f3 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,11 @@ +extern crate musician_training; + +use musician_training::*; + +fn main() { + let gui_result = gui::start_gui(); + if gui_result.is_err() { + println!("Failed to initialize"); + return; + } +} diff --git a/src/transforms.rs b/src/transforms.rs new file mode 100644 index 0000000..6b491ac --- /dev/null +++ b/src/transforms.rs @@ -0,0 +1,53 @@ +extern crate dft; + +#[derive(Clone, Debug)] +pub struct FrequencyBucket { + pub min_freq: f64, + pub max_freq: f64, + pub intensity: f64 +} + +pub fn fft(input: Vec<f64>, sample_rate: f64) -> Vec<FrequencyBucket> { + let frames = input.len(); + let plan = dft::Plan::new(dft::Operation::Forward, frames); + let mut intensities = input.clone(); + dft::transform(&mut intensities, &plan); + + let frequency_resolution = sample_rate / 2.0 / frames as f64; + + intensities.iter().enumerate().map(|(index, &value)| { + let index = index as f64; + FrequencyBucket { + min_freq: index * frequency_resolution, + max_freq: (index+1.0) * frequency_resolution, + intensity: value + } + }).collect() +} + +#[test] +fn fft_on_sine_wave() { + use std::f64::consts; + + let sample_rate = 44100.0 as f64; + let amplitude = 1.0 as f64; + let frames = 16384; + let frequency = 10000.0 as f64; //10KHz + let samples = (0..frames) + .map(|x| { + let t = x as f64 / sample_rate; + (2.0 as f64 * consts::PI * frequency * t).sin() * amplitude + }).collect(); + + let result = fft(samples, sample_rate); + + let peak = result.iter() + .fold(None as Option<FrequencyBucket>, |max, next| + if max.is_none() || max.clone().unwrap().intensity < next.intensity { Some(next.clone()) } else { max } + ).unwrap(); + + println!("{:?}", peak); + + assert!(peak.min_freq <= frequency); + assert!(peak.max_freq >= frequency); +} |