From 037d1348ec5516097dbceaecce544b829935e074 Mon Sep 17 00:00:00 2001 From: Mischa Spiegelmock Date: Sat, 15 Apr 2023 20:26:46 -0700 Subject: [PATCH 1/2] Wrap projectM instance access in a mutex --- src/app.rs | 14 +++++++++++--- src/app/audio.rs | 46 ++++++++++++++++++++++++++++++-------------- src/app/config.rs | 13 ++++++++----- src/app/main_loop.rs | 11 +++++++++-- 4 files changed, 60 insertions(+), 24 deletions(-) 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..f66f795 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 @@ -17,11 +20,12 @@ pub struct Audio { is_capturing: bool, frame_rate: Option, capturing_device: Option>, - projectm: ProjectMHandle, + 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 { @@ -105,7 +112,8 @@ impl Audio { samples: Some(buffer_size), }; - let audio_device = self + // open audio device for capture + let audio_device = match self .audio_subsystem // sdl .open_capture(None, &desired_spec, |_spec| { println!( @@ -118,14 +126,19 @@ impl Audio { // return callback fn AudioCaptureCallback { - pm: self.projectm, + pm: self.projectm.clone(), // spec, // buffer_size, // buffer: vec![0; buffer_size as usize], // position: 0, } - }) - .unwrap(); + }) { + Ok(device) => device, + Err(e) => { + println!("Error opening audio device: {}", e); + return; + } + }; // take ownership of device self.capturing_device = Some(audio_device); @@ -140,18 +153,21 @@ 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() ); self.is_capturing = false; - // drop(self.capturing_device); // stop capturing self.capturing_device = None; } } struct AudioCaptureCallback { - pm: ProjectMHandle, + // audio_tx: mpsc::Sender>, + + // we need to keep a reference to the projectm instance to + // add the audio data to it + pm: Arc>, // spec: sdl2::audio::AudioSpec, // buffer_size: SampleFormat, // buffer: Vec, @@ -166,7 +182,9 @@ 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; - projectm_rs::core::Projectm::pcm_add_float(pm, out.to_vec(), 2); + { + 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(); From ee25f78a64f7bc1dca262ef7e8fa10dd71bdc35d Mon Sep 17 00:00:00 2001 From: Mischa Spiegelmock Date: Sat, 15 Apr 2023 22:39:00 -0700 Subject: [PATCH 2/2] more cleanup --- Cargo.toml | 1 + src/app/audio.rs | 54 +++++++++++++++++++++++------------------------- 2 files changed, 27 insertions(+), 28 deletions(-) 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/audio.rs b/src/app/audio.rs index f66f795..8455f79 100644 --- a/src/app/audio.rs +++ b/src/app/audio.rs @@ -19,7 +19,7 @@ pub struct Audio { device_index: AudioDeviceIndex, is_capturing: bool, frame_rate: Option, - capturing_device: Option>, + capturing_device: Option>>, projectm: ProjectMWrapped, } @@ -103,23 +103,28 @@ 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()), }; // 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); @@ -127,10 +132,6 @@ impl Audio { // return callback fn AudioCaptureCallback { pm: self.projectm.clone(), - // spec, - // buffer_size, - // buffer: vec![0; buffer_size as usize], - // position: 0, } }) { Ok(device) => device, @@ -140,12 +141,12 @@ impl Audio { } }; + // 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) { @@ -157,21 +158,20 @@ impl Audio { 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; - self.capturing_device = None; } } struct AudioCaptureCallback { - // audio_tx: mpsc::Sender>, - // we need to keep a reference to the projectm instance to // add the audio data to it pm: Arc>, - // spec: sdl2::audio::AudioSpec, - // buffer_size: SampleFormat, - // buffer: Vec, - // position: usize, } unsafe impl Send for AudioCaptureCallback {} unsafe impl Sync for AudioCaptureCallback {} @@ -182,9 +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.lock().unwrap(); - projectm_rs::core::Projectm::pcm_add_float(pm, out.to_vec(), 2); - } + let pm = *self.pm.lock().unwrap(); + projectm_rs::core::Projectm::pcm_add_float(pm, out.to_vec(), 2); } }