FFT computation and value updating

This commit is contained in:
Muaz Ahmad 2024-12-10 16:37:18 +05:00
parent 1c3dc2abd7
commit 3108c16566
7 changed files with 146 additions and 17 deletions

57
Cargo.lock generated
View file

@ -1366,6 +1366,15 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
]
[[package]]
name = "num-derive"
version = "0.4.2"
@ -1578,6 +1587,15 @@ dependencies = [
"yansi",
]
[[package]]
name = "primal-check"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08"
dependencies = [
"num-integer",
]
[[package]]
name = "proc-macro2"
version = "1.0.92"
@ -1882,6 +1900,21 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustfft"
version = "6.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43806561bc506d0c5d160643ad742e3161049ac01027b5e6d7524091fd401d86"
dependencies = [
"num-complex",
"num-integer",
"num-traits",
"primal-check",
"strength_reduce",
"transpose",
"version_check",
]
[[package]]
name = "rustix"
version = "0.38.41"
@ -2131,6 +2164,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strength_reduce"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82"
[[package]]
name = "strsim"
version = "0.11.1"
@ -2166,11 +2205,13 @@ dependencies = [
"crossterm",
"image",
"md5",
"num-complex",
"pipewire",
"rand",
"ratatui",
"ratatui-image",
"reqwest",
"rustfft",
"serde",
"serde_json",
"toml",
@ -2415,6 +2456,16 @@ dependencies = [
"once_cell",
]
[[package]]
name = "transpose"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e"
dependencies = [
"num-integer",
"strength_reduce",
]
[[package]]
name = "try-lock"
version = "0.2.5"
@ -2508,6 +2559,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "want"
version = "0.3.1"

View file

@ -7,11 +7,13 @@ edition = "2021"
crossterm = "0.28.1"
image = "0.25.5"
md5 = "0.7.0"
num-complex = "0.4.6"
pipewire = "0.8.0"
rand = "0.8.5"
ratatui = "0.29.0"
ratatui-image = "3.0.0"
reqwest = { version = "0.12.9", features = ["blocking", "json"] }
rustfft = "6.2.0"
serde = { version = "1.0.215", features = ["derive"] }
serde_json = "1.0.133"
toml = "0.8.19"

View file

@ -1,29 +1,78 @@
use num_complex::{Complex, Complex64};
use rustfft::{Fft, FftPlanner};
use crate::utils::FFTResult;
use std::collections::VecDeque;
use std::{collections::VecDeque, sync::Arc};
const FFT_WINDOW: usize = 5;
const FFT_FRAME_SIZE: usize = FFT_WINDOW * 480;
pub struct FFTFrame {
buffer: VecDeque<[[i16; 480]; 2]>,
buffer: VecDeque<[[Complex<f64>; 480]; 2]>,
planner: FftPlanner<f64>,
fft_instance: Arc<dyn Fft<f64>>,
n_iter: usize,
}
impl FFTFrame {
pub fn new() -> FFTFrame {
let mut planner = FftPlanner::new();
let plan = planner.plan_fft_forward(FFT_FRAME_SIZE);
let mut frame = FFTFrame {
buffer: VecDeque::new(),
planner,
fft_instance: plan,
n_iter: 0,
};
for _ in 0..5 {
frame.push([[0; 480]; 2]);
for _ in 0..FFT_WINDOW {
frame.push([[Complex { re: 0.0, im: 0.0 }; 480]; 2]);
}
frame
}
pub fn push(&mut self, new_samples: [[i16; 480]; 2]) {
if self.buffer.len() == 5 {
pub fn push(&mut self, new_samples: [[Complex<f64>; 480]; 2]) {
if self.buffer.len() == FFT_WINDOW {
self.buffer.pop_front();
}
self.buffer.push_back(new_samples);
self.n_iter = (self.n_iter + 1) % FFT_WINDOW;
}
pub fn compute(&self) -> FFTResult {
unimplemented!()
pub fn compute(&mut self) -> Option<FFTResult> {
if self.n_iter != 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,
im: 0.0f64,
}; FFT_FRAME_SIZE],
[Complex {
re: 0.0f64,
im: 0.0f64,
}; 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);
}
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;
}
bins_l[i] /= 20.0;
bins_r[i] /= 20.0;
}
Some(FFTResult { bins_l, bins_r })
}
}

View file

@ -8,6 +8,8 @@ use std::{
time::{Duration, SystemTime},
};
use num_complex::Complex;
use crate::{
config::Settings,
player::PlayerEvent,
@ -83,13 +85,16 @@ impl SoundManager {
}
}
fn fft_compute(&mut self, frame: &[u8]) -> FFTResult {
let mut samples = [[0; 480]; 2];
fn fft_compute(&mut self, frame: &[u8]) -> Option<FFTResult> {
let mut samples = [[Complex {
re: 0.0f64,
im: 0.0f64,
}; 480]; 2];
for i in 0..frame.len() / 4 {
for j in 0..2 {
let mut val_buff = [0; 2];
val_buff.copy_from_slice(&frame[4 * i + 2 * j..4 * i + 2 * j + 2]);
samples[i][j] = i16::from_le_bytes(val_buff);
samples[j][i].re = i16::from_le_bytes(val_buff) as f64 / 32767.0;
}
}
self.fft_frame.push(samples);
@ -98,8 +103,9 @@ impl SoundManager {
fn push_samples(&mut self) -> Result<(), Error> {
let (mut frame, fetch_more) = self.decoder_context.next_sample();
let bins = self.fft_compute(frame.as_slice());
self.player_chan.send(PlayerEvent::FFTBins(bins))?;
if let Some(bins) = self.fft_compute(frame.as_slice()) {
self.player_chan.send(PlayerEvent::FFTBins(bins))?;
}
self.adjust_volume(frame.as_mut_slice());
self.sample_in.send(frame)?;
match fetch_more {

View file

@ -1,6 +1,9 @@
use image::DynamicImage;
use crate::{ssonic::response::Song, utils::default_cover};
use crate::{
ssonic::response::Song,
utils::{default_cover, FFTResult},
};
pub struct Metadata {
pub id: String,
@ -12,6 +15,7 @@ pub struct Metadata {
pub size: u32,
pub bytes_received: u32,
pub last_byte_range_start: u32,
pub spectrogram: FFTResult,
}
impl Metadata {
@ -26,6 +30,10 @@ impl Metadata {
size: 0,
bytes_received: 0,
last_byte_range_start: 0,
spectrogram: FFTResult {
bins_l: [0.0; 20],
bins_r: [0.0; 20],
},
}
}
@ -49,4 +57,8 @@ impl Metadata {
self.bytes_received = 0;
self.size = song.size;
}
pub fn update_spectrogram(&mut self, bins: FFTResult) {
self.spectrogram = bins;
}
}

View file

@ -10,7 +10,7 @@ use image::DynamicImage;
use crate::{
audio::AudioEvent,
ssonic::{response::Song, APIEvent, MAX_CHUNK_SIZE},
utils::{default_cover, time_rem, Error},
utils::{default_cover, time_rem, Error, FFTResult},
};
use super::{errors::PlayerError, Player, PlayerEvent};
@ -53,6 +53,7 @@ impl Player {
PlayerEvent::UpdateCover(cover) => self.tui_root.update_cover(cover),
PlayerEvent::AddAudioChunk(chunk_len) => self.recv_chunk(chunk_len)?,
PlayerEvent::FetchChunk => self.fetch_audio_chunk(false)?,
PlayerEvent::FFTBins(bins) => self.tui_root.metadata.update_spectrogram(bins),
_ => unimplemented!(),
}
}
@ -90,7 +91,6 @@ impl Player {
}
fn play_next(&mut self) -> Result<(), Error> {
dbg!("playing next");
let song = match self.playlist.get_next() {
None => {
// no song exists, requeue the event

View file

@ -76,4 +76,7 @@ pub fn time_rem(start_time: SystemTime, target_dur: Duration) -> Result<Duration
Ok(target_dur.saturating_sub(time_since))
}
pub struct FFTResult {}
pub struct FFTResult {
pub bins_l: [f64; 20],
pub bins_r: [f64; 20],
}