FFT computation and value updating
This commit is contained in:
parent
1c3dc2abd7
commit
3108c16566
7 changed files with 146 additions and 17 deletions
57
Cargo.lock
generated
57
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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],
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue