Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
14 changes: 11 additions & 3 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::sync::{Arc, Mutex};

use projectm_rs::core::{ProjectMHandle, Projectm};
use sdl2::video::GLProfile;

Expand All @@ -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<Mutex<ProjectMHandle>>;

/// Application state
pub struct App {
pm: ProjectMHandle,
pm: ProjectMWrapped,
playlist: projectm_rs::playlist::Playlist,
sdl_context: sdl2::Sdl,
window: sdl2::video::Window,
Expand Down Expand Up @@ -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,
Expand Down
86 changes: 51 additions & 35 deletions src/app/audio.rs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -16,12 +19,13 @@ pub struct Audio {
device_index: AudioDeviceIndex,
is_capturing: bool,
frame_rate: Option<FrameRate>,
capturing_device: Option<AudioDevice<AudioCaptureCallback>>,
projectm: ProjectMHandle,
capturing_device: Option<Box<AudioDevice<AudioCaptureCallback>>>,
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 {
Expand Down Expand Up @@ -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();
Expand All @@ -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<AudioCaptureDevice> {
Expand All @@ -96,66 +103,75 @@ 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) {
let current_device_name = self.get_current_device_name();
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<u8>,
// position: usize,
// we need to keep a reference to the projectm instance to
// add the audio data to it
pm: Arc<Mutex<ProjectMHandle>>,
}
unsafe impl Send for AudioCaptureCallback {}
unsafe impl Sync for AudioCaptureCallback {}
Expand All @@ -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);
}
}
13 changes: 8 additions & 5 deletions src/app/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,35 +39,38 @@ 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);
}

// 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<String> = 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)
}
}
11 changes: 9 additions & 2 deletions src/app/main_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
Expand Down