Add an audio visualizer
This commit is contained in:
parent
3108c16566
commit
9f53765c3b
5 changed files with 67 additions and 37 deletions
|
@ -32,20 +32,16 @@ impl FFTFrame {
|
|||
|
||||
pub fn push(&mut self, new_samples: [[Complex<f64>; 480]; 2]) {
|
||||
if self.buffer.len() == FFT_WINDOW {
|
||||
self.buffer.pop_front();
|
||||
self.buffer.pop_back();
|
||||
}
|
||||
self.buffer.push_back(new_samples);
|
||||
self.buffer.push_front(new_samples);
|
||||
self.n_iter = (self.n_iter + 1) % FFT_WINDOW;
|
||||
}
|
||||
|
||||
pub fn compute(&mut self) -> Option<FFTResult> {
|
||||
if self.n_iter != 0 {
|
||||
if self.n_iter % 2 != 0 {
|
||||
return None;
|
||||
}
|
||||
let mut FFTResult = FFTResult {
|
||||
bins_l: [0.0; 20],
|
||||
bins_r: [0.0; 20],
|
||||
};
|
||||
let (mut buff_l, mut buff_r) = (
|
||||
[Complex {
|
||||
re: 0.0f64,
|
||||
|
@ -57,22 +53,43 @@ impl FFTFrame {
|
|||
}; FFT_FRAME_SIZE],
|
||||
);
|
||||
for i in 0..FFT_WINDOW {
|
||||
let [l, r] = self.buffer.pop_front().unwrap();
|
||||
buff_l[i..i + 480].copy_from_slice(&l);
|
||||
buff_r[i..i + 480].copy_from_slice(&r);
|
||||
let [l, r] = &self.buffer[i];
|
||||
buff_l[i..i + 480].copy_from_slice(l);
|
||||
buff_r[i..i + 480].copy_from_slice(r);
|
||||
}
|
||||
self.hamming(&mut buff_l);
|
||||
self.hamming(&mut buff_r);
|
||||
self.fft_instance.process(&mut buff_l);
|
||||
self.fft_instance.process(&mut buff_r);
|
||||
|
||||
let (mut bins_l, mut bins_r) = ([0.0; 20], [0.0; 20]);
|
||||
for i in 0..20 {
|
||||
for j in 0..120 {
|
||||
bins_l[i] += buff_l[120 * i + j].re;
|
||||
bins_r[i] += buff_r[120 * i + j].re;
|
||||
let mut result = [0.0; 20];
|
||||
let mut bin_start = 0;
|
||||
for bin_i in 0..20 {
|
||||
let bin_end =
|
||||
(f64::powi((bin_i + 1) as f64 / 20.0, 2) * FFT_FRAME_SIZE as f64) as usize;
|
||||
let max_l = buff_l[bin_start..std::cmp::min(bin_end, FFT_FRAME_SIZE)]
|
||||
.iter()
|
||||
.map(|x| x.norm_sqr() / FFT_FRAME_SIZE as f64)
|
||||
.max_by(|a, b| a.total_cmp(b))
|
||||
.unwrap();
|
||||
let max_r = buff_r[bin_start..std::cmp::min(bin_end, FFT_FRAME_SIZE)]
|
||||
.iter()
|
||||
.map(|x| x.norm_sqr() / FFT_FRAME_SIZE as f64)
|
||||
.max_by(|a, b| a.total_cmp(b))
|
||||
.unwrap();
|
||||
result[bin_i] =
|
||||
(10.0 * f64::log10(f64::max(max_l, max_r))).clamp(-70.0, 0.0) / 70.0 + 1.0;
|
||||
bin_start = bin_end;
|
||||
}
|
||||
bins_l[i] /= 20.0;
|
||||
bins_r[i] /= 20.0;
|
||||
Some(result)
|
||||
}
|
||||
|
||||
fn hamming(&self, buff: &mut [Complex<f64>]) {
|
||||
for i in 0..buff.len() {
|
||||
let mult = 25.0 / 46.0
|
||||
- 21.0 / 46.0
|
||||
* f64::cos(2.0 * std::f64::consts::PI * i as f64 / FFT_FRAME_SIZE as f64);
|
||||
buff[i].re *= mult;
|
||||
}
|
||||
Some(FFTResult { bins_l, bins_r })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,11 +15,16 @@ pub struct Metadata {
|
|||
pub size: u32,
|
||||
pub bytes_received: u32,
|
||||
pub last_byte_range_start: u32,
|
||||
pub spectrogram: FFTResult,
|
||||
pub spectrogram: Vec<(f64, f64)>,
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
pub fn new() -> Metadata {
|
||||
let mut spectrogram = vec![(0.0, 0.0); 20];
|
||||
spectrogram
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.for_each(|(i, x)| x.0 = i as f64);
|
||||
Metadata {
|
||||
id: String::new(),
|
||||
name: String::new(),
|
||||
|
@ -30,10 +35,7 @@ impl Metadata {
|
|||
size: 0,
|
||||
bytes_received: 0,
|
||||
last_byte_range_start: 0,
|
||||
spectrogram: FFTResult {
|
||||
bins_l: [0.0; 20],
|
||||
bins_r: [0.0; 20],
|
||||
},
|
||||
spectrogram,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,6 +61,8 @@ impl Metadata {
|
|||
}
|
||||
|
||||
pub fn update_spectrogram(&mut self, bins: FFTResult) {
|
||||
self.spectrogram = bins;
|
||||
for i in 0..20 {
|
||||
self.spectrogram[i].1 = self.spectrogram[i].1 * 0.8 + 0.2 * bins[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ impl Player {
|
|||
self.handle_events()?;
|
||||
self.handle_inputs()?;
|
||||
self.update()?;
|
||||
let time_left_cycle = time_rem(st, Duration::from_millis(49))?;
|
||||
let time_left_cycle = time_rem(st, Duration::from_millis(19))?;
|
||||
thread::sleep(time_left_cycle);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ use layout::Flex;
|
|||
use ratatui::{
|
||||
layout::{Constraint, Layout},
|
||||
prelude::*,
|
||||
widgets::{Paragraph, Widget, Wrap},
|
||||
widgets::{Axis, BarChart, Block, Chart, Dataset, Gauge, Paragraph, Widget, Wrap},
|
||||
};
|
||||
use ratatui_image::{picker::Picker, protocol::StatefulProtocol, StatefulImage};
|
||||
|
||||
|
@ -25,14 +25,15 @@ impl Widget for &mut Root {
|
|||
Self: Sized,
|
||||
{
|
||||
let layout_main = Layout::vertical([
|
||||
Constraint::Ratio(1, 5),
|
||||
Constraint::Ratio(3, 5),
|
||||
Constraint::Ratio(1, 5),
|
||||
Constraint::Ratio(1, 6),
|
||||
Constraint::Ratio(2, 6),
|
||||
Constraint::Ratio(2, 6),
|
||||
Constraint::Ratio(1, 6),
|
||||
])
|
||||
.flex(Flex::Center);
|
||||
let [_, info, progress] = layout_main.areas(area);
|
||||
let [_, info, spec, _] = layout_main.areas(area);
|
||||
self.render_info(info, buf);
|
||||
self.render_progress(progress, buf);
|
||||
self.render_spectrogram(spec, buf);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,10 +56,21 @@ impl Root {
|
|||
self.render_track_info(track_info, buf);
|
||||
}
|
||||
|
||||
fn render_progress(&self, area: Rect, buf: &mut Buffer) {}
|
||||
fn render_spectrogram(&self, area: Rect, buf: &mut Buffer) {
|
||||
eprintln!("{:?}", self.metadata.spectrogram);
|
||||
Chart::new(vec![Dataset::default()
|
||||
.graph_type(ratatui::widgets::GraphType::Bar)
|
||||
.data(&self.metadata.spectrogram)])
|
||||
.style(Style::default().fg(Color::Gray))
|
||||
.x_axis(Axis::default().bounds([0.0, 19.0]))
|
||||
.y_axis(Axis::default().bounds([0.0, 1.0]))
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
fn render_cover(&mut self, area: Rect, buf: &mut Buffer) {
|
||||
StatefulImage::new(None).render(area, buf, &mut self.image_state);
|
||||
}
|
||||
|
||||
fn render_track_info(&self, area: Rect, buf: &mut Buffer) {
|
||||
let layout_track = Layout::vertical([
|
||||
Constraint::Max(2),
|
||||
|
|
|
@ -76,7 +76,4 @@ pub fn time_rem(start_time: SystemTime, target_dur: Duration) -> Result<Duration
|
|||
Ok(target_dur.saturating_sub(time_since))
|
||||
}
|
||||
|
||||
pub struct FFTResult {
|
||||
pub bins_l: [f64; 20],
|
||||
pub bins_r: [f64; 20],
|
||||
}
|
||||
pub type FFTResult = [f64; 20];
|
||||
|
|
Loading…
Reference in a new issue