M Cargo.lock => Cargo.lock +21 -0
@@ 48,6 48,26 @@ dependencies = [
]
[[package]]
+name = "crossbeam-channel"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
+dependencies = [
+ "cfg-if",
+ "lazy_static",
+]
+
+[[package]]
name = "git-testament"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 257,6 277,7 @@ name = "soundview"
version = "0.1.0"
dependencies = [
"anyhow",
+ "crossbeam-channel",
"git-testament",
"sdl2",
"tracing",
M Cargo.toml => Cargo.toml +1 -0
@@ 10,6 10,7 @@ repository = "https://sr.ht/~nickbp/soundview/"
[dependencies]
anyhow = "1.0"
+crossbeam-channel = "0.5"
git-testament = "0.2"
sdl2 = "0.35"
tracing = "0.1"
D src/events.rs => src/events.rs +0 -56
@@ 1,56 0,0 @@
-use anyhow::Result;
-use tracing::{debug, info};
-
-use sdl2::event::{Event, WindowEvent};
-use sdl2::keyboard::{Keycode, Mod};
-use sdl2::EventPump;
-
-use crate::recorder::Recorder;
-
-pub fn events_loop(mut event_pump: EventPump, recorder: Recorder) -> Result<()> {
- loop {
- let event = event_pump.wait_event_timeout(100);
- if let Some(event) = event {
- match event {
- Event::Quit {..} |
- // Esc, Q, Ctrl+C, Alt+F4: Quit
- Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
- return Ok(());
- },
- Event::KeyDown { keycode: Some(Keycode::Q), .. } => {
- return Ok(());
- },
- Event::KeyDown { keymod: Mod::LCTRLMOD | Mod::RCTRLMOD, keycode: Some(Keycode::C), .. } => {
- return Ok(());
- },
- Event::KeyDown { keymod: Mod::LALTMOD | Mod::RALTMOD, keycode: Some(Keycode::F4), .. } => {
- return Ok(());
- },
-
- // R, Space: Rotate
- Event::KeyDown { keycode: Some(Keycode::R) | Some(Keycode::Space), .. } => {
- info!("rotate");
- },
-
- // A, D, I: Switch Audio Device Input
- Event::KeyDown { keycode: Some(Keycode::A) | Some(Keycode::D) | Some(Keycode::I), .. } => {
- info!("next device");
- },
-
- Event::Window {win_event: WindowEvent::SizeChanged(x, y), ..} | Event::Window {win_event: WindowEvent::Resized(x,y), ..} => {
- info!("resize: x={}, y={}", x, y);
- },
-
- Event::AudioDeviceAdded {iscapture: true, which, ..} => {
- recorder.device_added(which)?;
- },
- Event::AudioDeviceRemoved {iscapture: true, which: idx, ..} => {
- info!("capture device removed: idx={}", idx);
- },
- _ => {
- debug!("ignored event: {:?}", event);
- }
- }
- }
- }
-}
A src/fourier.rs => src/fourier.rs +20 -0
@@ 0,0 1,20 @@
+use crossbeam_channel::{Receiver, Sender};
+use tracing::{debug, warn};
+
+pub fn process_audio_loop(recv_audio: Receiver<Vec<f32>>, send_processed: Sender<Vec<f32>>) {
+ loop {
+ match recv_audio.recv() {
+ Ok(mut audio) => {
+ audio.truncate(5); // TODO actual processing...
+ if let Err(e) = send_processed.send(audio) {
+ warn!("Failed to send processed data: {}", e);
+ }
+ },
+ Err(e) => {
+ // This generally means that the input channel has closed
+ debug!("exiting audio processing thread: {}", e);
+ break;
+ }
+ }
+ }
+}
M src/lib.rs => src/lib.rs +1 -2
@@ 1,6 1,5 @@
-pub mod events;
+pub mod fourier;
pub mod hsl;
/// Utilities relating to setting up log output.
pub mod logging;
pub mod recorder;
-pub mod render;
M src/main.rs => src/main.rs +98 -17
@@ 1,8 1,15 @@
-use anyhow::{anyhow, Result};
+use anyhow::{anyhow, bail, Result};
use git_testament::{git_testament, render_testament};
use tracing::{debug, info, warn};
-use soundview::{events, logging, recorder, render};
+use sdl2::event::{Event, WindowEvent};
+use sdl2::keyboard::{Keycode, Mod};
+use sdl2::pixels::Color;
+
+use soundview::{fourier, logging, recorder};
+
+use std::thread;
+use std::time::Duration;
git_testament!(TESTAMENT);
@@ 36,25 43,99 @@ fn main() -> Result<()> {
debug!("disabling screensaver");
video_subsystem.disable_screen_saver();
}
- let window = video_subsystem.window("soundview", 800, 600)
+ let mut canvas = video_subsystem.window("soundview", 800, 600)
.resizable()
.build()
+ .map_err(|e| anyhow!(e))?
+ .into_canvas()
+ .accelerated()
+ .present_vsync()
+ .build()
.map_err(|e| anyhow!(e))?;
- // TODO run events handling and rendering in separate threads?
- render::render_loop(
- window.into_canvas()
- .accelerated()
- .present_vsync()
- .build()
- .map_err(|e| anyhow!(e))?
- )?;
-
- // TODO clean teardown after events_loop exits
- events::events_loop(
- sdl_context.event_pump().map_err(|e| anyhow!(e))?,
- recorder::Recorder::new(sdl_context.audio().map_err(|e| anyhow!(e))?)
- )?;
+ let (send_audio, recv_audio) = crossbeam_channel::bounded::<Vec<f32>>(100);
+ let (send_processed, recv_processed) = crossbeam_channel::bounded::<Vec<f32>>(100);
+
+ let mut thread_handles = Vec::new();
+ thread_handles.push(
+ thread::Builder::new()
+ .name("fourier".to_string())
+ .spawn(move || {
+ fourier::process_audio_loop(recv_audio, send_processed)
+ })?
+ );
+
+ {
+ // Ensure that rec (and send_audio) go out of scope when we're exiting,
+ // so that the audio processing thread shuts down.
+ let mut rec = recorder::Recorder::new(sdl_context.audio().map_err(|e| anyhow!(e))?, send_audio);
+
+ let mut i = 0;
+ 'mainloop: loop {
+ // handle events (if any)
+ // TODO could break this out into a separate function, but it needs to interact with everything else...
+ if let Some(event) = sdl_context.event_pump().map_err(|e| anyhow!(e))?.poll_event() {
+ match event {
+ // <Close window>, Esc, Q, Ctrl+C, Alt+F4: Quit
+ Event::Quit {..} |
+ Event::KeyDown { keycode: Some(Keycode::Escape) | Some(Keycode::Q), .. } |
+ Event::KeyDown { keymod: Mod::LCTRLMOD | Mod::RCTRLMOD, keycode: Some(Keycode::C), .. } |
+ Event::KeyDown { keymod: Mod::LALTMOD | Mod::RALTMOD, keycode: Some(Keycode::F4), .. } => {
+ break 'mainloop;
+ },
+
+ // R, Space: Rotate
+ Event::KeyDown { keycode: Some(Keycode::R) | Some(Keycode::Space), .. } => {
+ info!("rotate");
+ },
+
+ // A, D, I: Switch Audio Device Input
+ Event::KeyDown { keycode: Some(Keycode::A) | Some(Keycode::D) | Some(Keycode::I), .. } => {
+ info!("next device");
+ },
+
+ Event::Window {win_event: WindowEvent::SizeChanged(x, y), ..} | Event::Window {win_event: WindowEvent::Resized(x,y), ..} => {
+ info!("resize: x={}, y={}", x, y);
+ },
+
+ Event::AudioDeviceAdded {iscapture: true, which: idx, ..} => {
+ rec.device_added(idx)?;
+ },
+ Event::AudioDeviceRemoved {iscapture: true, which: idx, ..} => {
+ info!("capture device removed: idx={}", idx);
+ },
+ _ => {
+ debug!("ignored event: {:?}", event);
+ }
+ }
+ }
+
+ // render
+ while let Ok(audio) = recv_processed.try_recv() {
+ debug!("got processed audio: {:?}", audio.len()); // TODO actual rendering...
+ }
+ i = (i + 1) % 255;
+ canvas.set_draw_color(Color::RGB(i, 64, 255 - i));
+ canvas.clear();
+
+ canvas.present();
+ thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));
+ }
+ }
+
+ for thread in thread_handles {
+ let thread_name = if let Some(name) = &thread.thread().name() {
+ name.to_string()
+ } else {
+ "???".to_string()
+ };
+ info!("Waiting for thread to exit: {}", thread_name);
+ // Would use with_context but the error type is weird
+ match thread.join().err() {
+ Some(e) => bail!("Failed to wait for thread to exit: {} {:?}", thread_name, e),
+ None => {}
+ }
+ }
Ok(())
}
M src/recorder.rs => src/recorder.rs +25 -21
@@ 1,67 1,71 @@
use anyhow::{anyhow, Result};
-use tracing::info;
+use tracing::{info, warn};
-use sdl2::audio::{AudioCallback, AudioSpecDesired};
+use sdl2::audio::{AudioCallback, AudioDevice, AudioSpecDesired};
use sdl2::AudioSubsystem;
-struct Handler {
+struct Callback {
+ audio_out: crossbeam_channel::Sender<Vec<f32>>
}
-impl AudioCallback for Handler {
+impl AudioCallback for Callback {
type Channel = f32;
fn callback(&mut self, out: &mut [f32]) {
- let mut val: f32 = 0.;
- for x in out.iter() {
- if *x < 0. {
- val += -x;
- } else {
- val += x;
- }
+ if let Err(e) = self.audio_out.send(Vec::from(out)) {
+ warn!("Failed to send audio: {}", e);
}
- info!("callback {} {:?}", out.len(), val);
}
}
pub struct Recorder {
audio_subsystem: AudioSubsystem,
+ audio_out: crossbeam_channel::Sender<Vec<f32>>,
+ rec_dev: Option<AudioDevice<Callback>>
}
impl Recorder {
- pub fn new(audio_subsystem: AudioSubsystem) -> Recorder {
+ pub fn new(audio_subsystem: AudioSubsystem, audio_out: crossbeam_channel::Sender<Vec<f32>>) -> Recorder {
Recorder {
- audio_subsystem
+ audio_subsystem,
+ audio_out,
+ rec_dev: Option::None
}
}
- pub fn device_added(self: &Recorder, which: u32) -> Result<()> {
+ pub fn device_added(self: &mut Recorder, which: u32) -> Result<()> {
let name = self.audio_subsystem.audio_capture_device_name(which).map_err(|e| anyhow!(e))?;
- info!("capture device added: idx={} name={:?}", which, name);
if name != "Monitor of Built-in Audio Analog Stereo" {
// ignore
+ info!("added capture device (ignored): idx={} name={:?}", which, name);
return Ok(());
}
- info!("setting up capture");
+ info!("added capture device (setting up): idx={} name={:?}", which, name);
self.record(name.as_str())
}
- fn record(self: &Recorder, device_name: &str) -> Result<()> {
- // TODO events arent logged, this probably needs to be in different thread
+ fn record(self: &mut Recorder, device_name: &str) -> Result<()> {
let desired_spec = AudioSpecDesired {
freq: Some(44100),
channels: Some(1), // mono
samples: None // default sample size, power of 2
};
- let rec_dev = self.audio_subsystem.open_capture(device_name, &desired_spec, |_actual_spec| {
+ let audio_out_cpy = self.audio_out.clone();
+ let rec_dev = self.audio_subsystem.open_capture(device_name, &desired_spec, |actual_spec| {
+ info!("Capturing audio from '{}' with spec: {:?}", device_name, actual_spec);
// initialize the audio callback
- Handler {
+ Callback {
+ audio_out: audio_out_cpy
}
}).map_err(|e| anyhow!(e))?;
// Start playback
rec_dev.resume();
+ // Save rec_dev so that it doesn't get closed automatically
+ self.rec_dev = Some(rec_dev);
+
Ok(())
}
}
D src/render.rs => src/render.rs +0 -21
@@ 1,21 0,0 @@
-use anyhow::Result;
-
-use sdl2::pixels::Color;
-use sdl2::render::Canvas;
-use sdl2::video::Window;
-use std::time::Duration;
-
-pub fn render_loop(mut canvas: Canvas<Window>) -> Result<()> {
- let mut i = 0;
-
- loop {
- i = (i + 1) % 255;
- canvas.set_draw_color(Color::RGB(i, 64, 255 - i));
- canvas.clear();
-
- canvas.present();
- ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));
- }
-
- Ok(())
-}