use crate::dynamic::live_input::{setup_audio_input_loop, AudioDevAndCfg};
use crate::dynamic::window_top_btm::visualize_minifb::{
get_drawing_areas, setup_window, DEFAULT_H, DEFAULT_W,
};
use cpal::traits::StreamTrait;
use minifb::Key;
use plotters::chart::ChartContext;
use plotters::coord::cartesian::Cartesian2d;
use plotters::coord::types::RangedCoordf64;
use plotters::prelude::BitMapBackend;
use plotters::series::LineSeries;
use plotters::style::{BLACK, CYAN};
use plotters_bitmap::bitmap_pixel::BGRXPixel;
use ringbuffer::{AllocRingBuffer, RingBuffer};
use std::borrow::{Borrow, BorrowMut};
use std::ops::Range;
use std::sync::{Arc, Mutex};
pub mod pixel_buf;
pub mod visualize_minifb;
#[allow(missing_debug_implementations)]
pub enum TransformFn<'a> {
Basic(fn(&[f32], f32) -> Vec<f32>),
#[allow(clippy::complexity)]
Complex(&'a dyn Fn(&[f32], f32) -> Vec<(f64, f64)>),
}
#[allow(clippy::too_many_arguments)]
pub fn open_window_connect_audio(
name: &str,
preferred_height: Option<usize>,
preferred_width: Option<usize>,
preferred_x_range: Option<Range<f64>>,
preferred_y_range: Option<Range<f64>>,
x_desc: &str,
y_desc: &str,
input_dev_and_cfg: AudioDevAndCfg,
audio_data_transform_fn: TransformFn,
) {
let sample_rate = input_dev_and_cfg.cfg().sample_rate.0 as f32;
let latest_audio_data = init_ringbuffer(sample_rate as usize);
let audio_buffer_len = latest_audio_data.lock().unwrap().len();
let stream = setup_audio_input_loop(latest_audio_data.clone(), input_dev_and_cfg);
let time_per_sample = 1.0 / sample_rate as f64;
stream.play().unwrap();
let (mut window, top_cs, btm_cs, mut pixel_buf) = setup_window(
name,
preferred_height,
preferred_width,
preferred_x_range,
preferred_y_range,
x_desc,
y_desc,
audio_buffer_len,
time_per_sample,
);
window.set_target_fps(144);
while window.is_open() {
if window.is_key_down(Key::Escape) {
break;
}
let (top_drawing_area, btm_drawing_area) = get_drawing_areas(
pixel_buf.borrow_mut(),
preferred_width.unwrap_or(DEFAULT_W),
preferred_height.unwrap_or(DEFAULT_H),
);
let top_chart = top_cs.clone().restore(&top_drawing_area);
let btm_chart = btm_cs.clone().restore(&btm_drawing_area);
top_chart.plotting_area().fill(&BLACK).borrow();
btm_chart.plotting_area().fill(&BLACK).borrow();
let latest_audio_data = latest_audio_data.clone().lock().unwrap().to_vec();
fill_chart_waveform_over_time(
top_chart,
&latest_audio_data,
time_per_sample,
audio_buffer_len,
);
if let TransformFn::Basic(fnc) = audio_data_transform_fn {
let data = fnc(&latest_audio_data, sample_rate);
fill_chart_waveform_over_time(btm_chart, &data, time_per_sample, audio_buffer_len);
} else if let TransformFn::Complex(fnc) = audio_data_transform_fn {
let data = fnc(&latest_audio_data, sample_rate);
fill_chart_complex_fnc(btm_chart, data);
} else {
drop(btm_chart);
panic!("invalid transform fn variant");
}
drop(top_drawing_area);
drop(btm_drawing_area);
window
.update_with_buffer(
pixel_buf.borrow(),
preferred_width.unwrap_or(DEFAULT_W),
preferred_height.unwrap_or(DEFAULT_H),
)
.unwrap();
}
stream.pause().unwrap();
}
fn init_ringbuffer(sampling_rate: usize) -> Arc<Mutex<AllocRingBuffer<f32>>> {
let mut buf = AllocRingBuffer::new((5 * sampling_rate).next_power_of_two());
buf.fill(0.0);
Arc::new(Mutex::new(buf))
}
fn fill_chart_complex_fnc(
mut chart: ChartContext<BitMapBackend<BGRXPixel>, Cartesian2d<RangedCoordf64, RangedCoordf64>>,
audio_data: Vec<(f64, f64)>,
) {
chart
.draw_series(LineSeries::new(audio_data, &CYAN))
.unwrap();
}
fn fill_chart_waveform_over_time(
mut chart: ChartContext<BitMapBackend<BGRXPixel>, Cartesian2d<RangedCoordf64, RangedCoordf64>>,
audio_data: &[f32],
time_per_sample: f64,
audio_history_buf_len: usize,
) {
debug_assert_eq!(audio_data.len(), audio_history_buf_len);
let timeshift = audio_history_buf_len as f64 * time_per_sample;
let data_iter = audio_data
.iter()
.enumerate()
.filter(|(i, _)| *i % 4 == 0)
.map(|(i, amplitude)| {
let timestamp = time_per_sample * (i as f64) - timeshift;
(timestamp, (*amplitude) as f64)
});
chart
.draw_series(LineSeries::new(data_iter, &CYAN))
.unwrap();
}
#[cfg(test)]
mod tests {
use super::*;
#[ignore]
#[test]
fn test_record_live_audio_and_visualize() {
open_window_connect_audio(
"Test",
None,
None,
None,
None,
"x-axis",
"y-axis",
AudioDevAndCfg::new(None, None),
TransformFn::Basic(|vals, _| vals.to_vec()),
);
}
}