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]) {
|
pub fn push(&mut self, new_samples: [[Complex<f64>; 480]; 2]) {
|
||||||
if self.buffer.len() == FFT_WINDOW {
|
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;
|
self.n_iter = (self.n_iter + 1) % FFT_WINDOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compute(&mut self) -> Option<FFTResult> {
|
pub fn compute(&mut self) -> Option<FFTResult> {
|
||||||
if self.n_iter != 0 {
|
if self.n_iter % 2 != 0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let mut FFTResult = FFTResult {
|
|
||||||
bins_l: [0.0; 20],
|
|
||||||
bins_r: [0.0; 20],
|
|
||||||
};
|
|
||||||
let (mut buff_l, mut buff_r) = (
|
let (mut buff_l, mut buff_r) = (
|
||||||
[Complex {
|
[Complex {
|
||||||
re: 0.0f64,
|
re: 0.0f64,
|
||||||
|
@ -57,22 +53,43 @@ impl FFTFrame {
|
||||||
}; FFT_FRAME_SIZE],
|
}; FFT_FRAME_SIZE],
|
||||||
);
|
);
|
||||||
for i in 0..FFT_WINDOW {
|
for i in 0..FFT_WINDOW {
|
||||||
let [l, r] = self.buffer.pop_front().unwrap();
|
let [l, r] = &self.buffer[i];
|
||||||
buff_l[i..i + 480].copy_from_slice(&l);
|
buff_l[i..i + 480].copy_from_slice(l);
|
||||||
buff_r[i..i + 480].copy_from_slice(&r);
|
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_l);
|
||||||
self.fft_instance.process(&mut buff_r);
|
self.fft_instance.process(&mut buff_r);
|
||||||
|
|
||||||
let (mut bins_l, mut bins_r) = ([0.0; 20], [0.0; 20]);
|
let mut result = [0.0; 20];
|
||||||
for i in 0..20 {
|
let mut bin_start = 0;
|
||||||
for j in 0..120 {
|
for bin_i in 0..20 {
|
||||||
bins_l[i] += buff_l[120 * i + j].re;
|
let bin_end =
|
||||||
bins_r[i] += buff_r[120 * i + j].re;
|
(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;
|
Some(result)
|
||||||
bins_r[i] /= 20.0;
|
}
|
||||||
|
|
||||||
|
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 size: u32,
|
||||||
pub bytes_received: u32,
|
pub bytes_received: u32,
|
||||||
pub last_byte_range_start: u32,
|
pub last_byte_range_start: u32,
|
||||||
pub spectrogram: FFTResult,
|
pub spectrogram: Vec<(f64, f64)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Metadata {
|
impl Metadata {
|
||||||
pub fn new() -> 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 {
|
Metadata {
|
||||||
id: String::new(),
|
id: String::new(),
|
||||||
name: String::new(),
|
name: String::new(),
|
||||||
|
@ -30,10 +35,7 @@ impl Metadata {
|
||||||
size: 0,
|
size: 0,
|
||||||
bytes_received: 0,
|
bytes_received: 0,
|
||||||
last_byte_range_start: 0,
|
last_byte_range_start: 0,
|
||||||
spectrogram: FFTResult {
|
spectrogram,
|
||||||
bins_l: [0.0; 20],
|
|
||||||
bins_r: [0.0; 20],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +61,8 @@ impl Metadata {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_spectrogram(&mut self, bins: FFTResult) {
|
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_events()?;
|
||||||
self.handle_inputs()?;
|
self.handle_inputs()?;
|
||||||
self.update()?;
|
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);
|
thread::sleep(time_left_cycle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use layout::Flex;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::{Constraint, Layout},
|
layout::{Constraint, Layout},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
widgets::{Paragraph, Widget, Wrap},
|
widgets::{Axis, BarChart, Block, Chart, Dataset, Gauge, Paragraph, Widget, Wrap},
|
||||||
};
|
};
|
||||||
use ratatui_image::{picker::Picker, protocol::StatefulProtocol, StatefulImage};
|
use ratatui_image::{picker::Picker, protocol::StatefulProtocol, StatefulImage};
|
||||||
|
|
||||||
|
@ -25,14 +25,15 @@ impl Widget for &mut Root {
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
let layout_main = Layout::vertical([
|
let layout_main = Layout::vertical([
|
||||||
Constraint::Ratio(1, 5),
|
Constraint::Ratio(1, 6),
|
||||||
Constraint::Ratio(3, 5),
|
Constraint::Ratio(2, 6),
|
||||||
Constraint::Ratio(1, 5),
|
Constraint::Ratio(2, 6),
|
||||||
|
Constraint::Ratio(1, 6),
|
||||||
])
|
])
|
||||||
.flex(Flex::Center);
|
.flex(Flex::Center);
|
||||||
let [_, info, progress] = layout_main.areas(area);
|
let [_, info, spec, _] = layout_main.areas(area);
|
||||||
self.render_info(info, buf);
|
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);
|
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) {
|
fn render_cover(&mut self, area: Rect, buf: &mut Buffer) {
|
||||||
StatefulImage::new(None).render(area, buf, &mut self.image_state);
|
StatefulImage::new(None).render(area, buf, &mut self.image_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_track_info(&self, area: Rect, buf: &mut Buffer) {
|
fn render_track_info(&self, area: Rect, buf: &mut Buffer) {
|
||||||
let layout_track = Layout::vertical([
|
let layout_track = Layout::vertical([
|
||||||
Constraint::Max(2),
|
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))
|
Ok(target_dur.saturating_sub(time_since))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FFTResult {
|
pub type FFTResult = [f64; 20];
|
||||||
pub bins_l: [f64; 20],
|
|
||||||
pub bins_r: [f64; 20],
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue