diff --git a/Cargo.toml b/Cargo.toml index b81ce59..d5dfdb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ rust-version = "1.68.2" libc = "*" sdl2 = "0.35" projectm-rs = "1.0.5" +# projectm-rs = { path = "../projectm-rs" } #projectm-rs = { git = "https://github.com/projectM-visualizer/projectm-rs" } # gl = "0.14.0" diff --git a/src/app.rs b/src/app.rs index 3f50a9e..eacbb89 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,3 +1,5 @@ +use std::sync::{Arc, Mutex}; + use projectm_rs::core::{ProjectMHandle, Projectm}; use sdl2::video::GLProfile; @@ -7,9 +9,12 @@ pub mod main_loop; pub mod playlist; pub mod video; +/// Thread-safe wrapper around the projectM instance. +pub type ProjectMWrapped = Arc>; + /// Application state pub struct App { - pm: ProjectMHandle, + pm: ProjectMWrapped, playlist: projectm_rs::playlist::Playlist, sdl_context: sdl2::Sdl, window: sdl2::video::Window, @@ -65,11 +70,14 @@ impl App { let (width, height) = window.drawable_size(); // highDPI aware Projectm::set_window_size(pm, width.try_into().unwrap(), height.try_into().unwrap()); + // create a mutex to protect the projectM instance + let pm = Arc::new(Mutex::new(pm)); + // initialize audio - let audio = audio::Audio::new(&sdl_context, pm); + let audio = audio::Audio::new(&sdl_context, pm.clone()); Self { - pm, + pm: pm.clone(), playlist, sdl_context, window, diff --git a/src/app/audio.rs b/src/app/audio.rs index d42b7d9..8455f79 100644 --- a/src/app/audio.rs +++ b/src/app/audio.rs @@ -1,7 +1,10 @@ use projectm_rs::core::ProjectMHandle; use sdl2::audio::{AudioCallback, AudioDevice, AudioSpecDesired}; +use std::sync::Arc; +use std::sync::Mutex; use super::config::FrameRate; +use super::ProjectMWrapped; type AudioDeviceIndex = u32; type SampleFormat = f32; // format of audio samples @@ -16,12 +19,13 @@ pub struct Audio { device_index: AudioDeviceIndex, is_capturing: bool, frame_rate: Option, - capturing_device: Option>, - projectm: ProjectMHandle, + capturing_device: Option>>, + projectm: ProjectMWrapped, } +/// Wrapper around the audio subsystem to capture audio and pass it to projectM. impl Audio { - pub fn new(sdl_context: &sdl2::Sdl, projectm: ProjectMHandle) -> Self { + pub fn new(sdl_context: &sdl2::Sdl, projectm: ProjectMWrapped) -> Self { let audio_subsystem = sdl_context.audio().unwrap(); Self { @@ -52,7 +56,8 @@ impl Audio { } } - pub fn set_device(&mut self, device_index: AudioDeviceIndex) { + /// Start capturing audio from device_index. + pub fn capture_device(&mut self, device_index: AudioDeviceIndex) { self.stop_audio_capture(); self.device_index = device_index; self.begin_audio_capture(); @@ -64,12 +69,14 @@ impl Audio { .expect("could not get audio device") } + /// Select a new audio device and start capturing audio from it. pub fn open_next_device(&mut self) { let device_list = self.get_device_list(); let current_device_index = self.device_index; - let next_device_index = current_device_index + 1 % device_list.len() as AudioDeviceIndex; - self.set_device(next_device_index); + let next_device_index = (current_device_index + 1) % device_list.len() as AudioDeviceIndex; + println!("Opening next device: {}", next_device_index); + self.capture_device(next_device_index); } fn get_device_list(&self) -> Vec { @@ -96,43 +103,50 @@ impl Audio { let sample_rate: u32 = 44100; let frame_rate = self.frame_rate.unwrap(); - // should be enough for 1 frame - let buffer_size = (sample_rate / frame_rate) as u16; + // how many samples to capture at a time + // should be enough for 1 frame or less + // should not be larger than max_samples / channels + let max_samples: usize = projectm_rs::core::Projectm::pcm_get_max_samples() + .try_into() + .unwrap(); + let samples_per_frame = (sample_rate / frame_rate) as usize; + let buffer_size = std::cmp::min(max_samples / 2, samples_per_frame); + println!("Buffer size: {}", buffer_size); let desired_spec = AudioSpecDesired { freq: Some(sample_rate.try_into().unwrap()), channels: Some(2), - samples: Some(buffer_size), + samples: Some(buffer_size.try_into().unwrap()), }; - let audio_device = self + // open audio device for capture + let device_name = self.get_current_device_name(); + let audio_device = match self .audio_subsystem // sdl - .open_capture(None, &desired_spec, |_spec| { - println!( - "Beginning audio capture for device {}", - self.get_current_device_name() - ); + .open_capture(device_name.as_str(), &desired_spec, |_spec| { + println!("Beginning audio capture for device {}", device_name); // print spec println!("Audio Spec: {:?}", _spec); // return callback fn AudioCaptureCallback { - pm: self.projectm, - // spec, - // buffer_size, - // buffer: vec![0; buffer_size as usize], - // position: 0, + pm: self.projectm.clone(), } - }) - .unwrap(); + }) { + Ok(device) => device, + Err(e) => { + println!("Error opening audio device: {}", e); + return; + } + }; + + // start capturing + audio_device.resume(); // take ownership of device - self.capturing_device = Some(audio_device); + self.capturing_device = Some(Box::new(audio_device)); self.is_capturing = true; - - // play device - self.capturing_device.as_mut().unwrap().resume(); } pub fn stop_audio_capture(&mut self) { @@ -140,22 +154,24 @@ impl Audio { println!("Stopping audio capture for device {}", current_device_name); println!( - "current capture device: {:?}", + "Current capture device status: {:?}", self.capturing_device.as_ref().unwrap().status() ); + // take ownership of device + // capture device will be dropped when this function returns + // and the audio callback will stop being called + let device = self.capturing_device.take().unwrap(); + device.pause(); + self.is_capturing = false; - // drop(self.capturing_device); // stop capturing - self.capturing_device = None; } } struct AudioCaptureCallback { - pm: ProjectMHandle, - // spec: sdl2::audio::AudioSpec, - // buffer_size: SampleFormat, - // buffer: Vec, - // position: usize, + // we need to keep a reference to the projectm instance to + // add the audio data to it + pm: Arc>, } unsafe impl Send for AudioCaptureCallback {} unsafe impl Sync for AudioCaptureCallback {} @@ -166,7 +182,7 @@ impl AudioCallback for AudioCaptureCallback { // we are receiving some chunk of audio data // we need to pass it to projectm fn callback(&mut self, out: &mut [SampleFormat]) { - let pm = self.pm; + let pm = *self.pm.lock().unwrap(); projectm_rs::core::Projectm::pcm_add_float(pm, out.to_vec(), 2); } } diff --git a/src/app/config.rs b/src/app/config.rs index 22afee0..20fe783 100644 --- a/src/app/config.rs +++ b/src/app/config.rs @@ -39,6 +39,8 @@ impl Default for Config { impl App { pub fn load_config(&self, config: &Config) { + let pm = *self.pm.lock().unwrap(); + // load presets if provided if let Some(preset_path) = &config.preset_path { self.add_preset_path(preset_path); @@ -46,28 +48,29 @@ impl App { // set frame rate if provided if let Some(frame_rate) = config.frame_rate { - Projectm::set_fps(self.pm, frame_rate) + Projectm::set_fps(pm, frame_rate) } // load textures if provided if let Some(texture_path) = &config.texture_path { let mut paths: Vec = Vec::new(); paths.push(texture_path.into()); - Projectm::set_texture_search_paths(self.pm, &paths, 1); + Projectm::set_texture_search_paths(pm, &paths, 1); } // set beat sensitivity if provided if let Some(beat_sensitivity) = config.beat_sensitivity { - Projectm::set_beat_sensitivity(self.pm, beat_sensitivity); + Projectm::set_beat_sensitivity(pm, beat_sensitivity); } // set preset duration if provided if let Some(preset_duration) = config.preset_duration { - Projectm::set_preset_duration(self.pm, preset_duration); + Projectm::set_preset_duration(pm, preset_duration); } } pub fn get_frame_rate(&self) -> FrameRate { - Projectm::get_fps(self.pm) + let pm = *self.pm.lock().unwrap(); + Projectm::get_fps(pm) } } diff --git a/src/app/main_loop.rs b/src/app/main_loop.rs index 88f814f..d1f8885 100644 --- a/src/app/main_loop.rs +++ b/src/app/main_loop.rs @@ -81,7 +81,11 @@ impl App { // Next audio capture input device (ctl-I, cmd-I) Event::KeyUp { keycode: Some(Keycode::I), - keymod: sdl2::keyboard::Mod::LCTRLMOD, + keymod: + sdl2::keyboard::Mod::LCTRLMOD + | sdl2::keyboard::Mod::RCTRLMOD + | sdl2::keyboard::Mod::LGUIMOD + | sdl2::keyboard::Mod::RGUIMOD, .. } => { self.audio.open_next_device(); @@ -97,7 +101,10 @@ impl App { dummy_audio::generate_random_audio_data(self.pm); // render a frame - Projectm::render_frame(self.pm); + { + let pm = *self.pm.lock().unwrap(); + Projectm::render_frame(pm); + } // swap buffers self.window.gl_swap_window();