feat: add audio visualizer #3
+45
-21
@@ -1,6 +1,10 @@
|
|||||||
use std::{
|
use std::{
|
||||||
io::{self, stdout},
|
io::{self, stdout},
|
||||||
sync::{Arc, Mutex, mpsc},
|
sync::{
|
||||||
|
Arc, Mutex,
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
mpsc,
|
||||||
|
},
|
||||||
thread,
|
thread,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
@@ -143,8 +147,9 @@ impl Drop for TerminalGuard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AudioVisualizer {
|
struct AudioVisualizer {
|
||||||
pub bar_data: Arc<Mutex<Vec<u64>>>,
|
bar_data: Arc<Mutex<Vec<u64>>>,
|
||||||
|
stop_flag: Arc<AtomicBool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a implementation largely lifted from these
|
// This is a implementation largely lifted from these
|
||||||
@@ -152,18 +157,22 @@ pub struct AudioVisualizer {
|
|||||||
// 1. CAVA (C): https://github.com/karlstav/cava
|
// 1. CAVA (C): https://github.com/karlstav/cava
|
||||||
// 2. cli-visualizer (C++): https://github.com/dpayne/cli-visualizer
|
// 2. cli-visualizer (C++): https://github.com/dpayne/cli-visualizer
|
||||||
impl AudioVisualizer {
|
impl AudioVisualizer {
|
||||||
pub fn new(num_bars: usize) -> Self {
|
fn new(num_bars: usize) -> Self {
|
||||||
//
|
|
||||||
let bar_data = Arc::new(Mutex::new(vec![0; num_bars]));
|
let bar_data = Arc::new(Mutex::new(vec![0; num_bars]));
|
||||||
let bar_data_clone = Arc::clone(&bar_data);
|
let bar_data_clone = Arc::clone(&bar_data);
|
||||||
|
let stop_flag = Arc::new(AtomicBool::new(false));
|
||||||
|
let stop_clone = Arc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
if let Err(e) = Self::run_audio_loop(bar_data_clone, num_bars) {
|
if let Err(e) = Self::run_audio_loop(bar_data_clone, num_bars, stop_clone) {
|
||||||
eprintln!("Audio capture error: {:?}", e);
|
eprintln!("Audio capture error: {:?}", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self { bar_data }
|
Self {
|
||||||
|
bar_data,
|
||||||
|
stop_flag,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Doing best effort to support cross-platform functionality
|
// Doing best effort to support cross-platform functionality
|
||||||
@@ -216,7 +225,7 @@ impl AudioVisualizer {
|
|||||||
// this allows bass, mids, and trembls to have equal visual
|
// this allows bass, mids, and trembls to have equal visual
|
||||||
// proportions during display
|
// proportions during display
|
||||||
//
|
//
|
||||||
// i.e.
|
// i.e.
|
||||||
// bins like this [0-500Hz] [500-1k] [1k-1.5k] [1.5k-2k] [2k-2.5k] [2.5k-3k] [3k-12kHz]
|
// bins like this [0-500Hz] [500-1k] [1k-1.5k] [1.5k-2k] [2k-2.5k] [2.5k-3k] [3k-12kHz]
|
||||||
// vs
|
// vs
|
||||||
// bins like this [20-60Hz] [60-250Hz] [250-500Hz] [500-2kHz] [2k-4kHz] [4k-8kHz] [8k-12kHz]
|
// bins like this [20-60Hz] [60-250Hz] [250-500Hz] [500-2kHz] [2k-4kHz] [4k-8kHz] [8k-12kHz]
|
||||||
@@ -304,7 +313,7 @@ impl AudioVisualizer {
|
|||||||
smoothed
|
smoothed
|
||||||
}
|
}
|
||||||
|
|
||||||
// songs can be quiet and load so we try to adjust
|
// songs can be quiet and loud so we try to adjust
|
||||||
// sensitivity to avoid bars becoming flattened or
|
// sensitivity to avoid bars becoming flattened or
|
||||||
// clipped
|
// clipped
|
||||||
fn adjust_autosens(autosens: &mut f32, bars: &[f32]) {
|
fn adjust_autosens(autosens: &mut f32, bars: &[f32]) {
|
||||||
@@ -321,6 +330,7 @@ impl AudioVisualizer {
|
|||||||
fn run_audio_loop(
|
fn run_audio_loop(
|
||||||
bar_data: Arc<Mutex<Vec<u64>>>,
|
bar_data: Arc<Mutex<Vec<u64>>>,
|
||||||
num_bars: usize,
|
num_bars: usize,
|
||||||
|
stop: Arc<AtomicBool>,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let host = cpal::default_host();
|
let host = cpal::default_host();
|
||||||
let (device, config) = Self::get_audio_device(&host)?;
|
let (device, config) = Self::get_audio_device(&host)?;
|
||||||
@@ -353,16 +363,16 @@ impl AudioVisualizer {
|
|||||||
let stream = device.build_input_stream(
|
let stream = device.build_input_stream(
|
||||||
config,
|
config,
|
||||||
move |data: &[f32], _| {
|
move |data: &[f32], _| {
|
||||||
let mut buf = buffer_clone.lock().unwrap();
|
if let Ok(mut buf) = buffer_clone.lock() {
|
||||||
|
for chunk in data.chunks(channels) {
|
||||||
|
let mono: f32 = chunk.iter().sum::<f32>() / channels as f32;
|
||||||
|
buf.push(mono);
|
||||||
|
}
|
||||||
|
|
||||||
for chunk in data.chunks(channels) {
|
if buf.len() > chunk_size * 2 {
|
||||||
let mono: f32 = chunk.iter().sum::<f32>() / channels as f32;
|
let drain_amt = buf.len() - chunk_size;
|
||||||
buf.push(mono);
|
buf.drain(0..drain_amt);
|
||||||
}
|
}
|
||||||
|
|
||||||
if buf.len() > chunk_size * 2 {
|
|
||||||
let drain_amt = buf.len() - chunk_size;
|
|
||||||
buf.drain(0..drain_amt);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|err| eprintln!("Stream error: {}", err),
|
|err| eprintln!("Stream error: {}", err),
|
||||||
@@ -371,11 +381,14 @@ impl AudioVisualizer {
|
|||||||
|
|
||||||
stream.play()?;
|
stream.play()?;
|
||||||
|
|
||||||
loop {
|
while !stop.load(Ordering::Relaxed) {
|
||||||
thread::sleep(std::time::Duration::from_millis(16));
|
thread::sleep(std::time::Duration::from_millis(16));
|
||||||
|
|
||||||
let samples = {
|
let samples = {
|
||||||
let buf = audio_buffer.lock().unwrap();
|
let buf = match audio_buffer.lock() {
|
||||||
|
Ok(b) => b,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
if buf.len() < chunk_size {
|
if buf.len() < chunk_size {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -384,7 +397,10 @@ impl AudioVisualizer {
|
|||||||
|
|
||||||
Self::apply_hann_window(&samples, &mut windowed_buffer);
|
Self::apply_hann_window(&samples, &mut windowed_buffer);
|
||||||
|
|
||||||
fft.process(&mut windowed_buffer, &mut spectrum).unwrap();
|
if let Err(e) = fft.process(&mut windowed_buffer, &mut spectrum) {
|
||||||
|
eprintln!("FFT error: {:?}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let raw_bars = Self::process_fft_magnitudes(
|
let raw_bars = Self::process_fft_magnitudes(
|
||||||
&spectrum,
|
&spectrum,
|
||||||
@@ -407,6 +423,14 @@ impl AudioVisualizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for AudioVisualizer {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.stop_flag.store(true, Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user