Add an audio visualizer

This commit is contained in:
Muaz Ahmad 2024-12-11 16:18:17 +05:00
parent 3108c16566
commit 9f53765c3b
5 changed files with 67 additions and 37 deletions

View file

@ -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 })
}
}

View file

@ -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];
}
}
}

View file

@ -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);
}
}

View file

@ -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),

View file

@ -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];