Skip to content

Commit 7285e82

Browse files
committed
feat: add visualizer that's like.. kinda okay
1 parent 82df5db commit 7285e82

5 files changed

Lines changed: 195 additions & 4 deletions

File tree

‎Cargo.lock‎

Lines changed: 44 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎player/Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ fltk = "1.4.3"
1919
fltk-theme = "0.7.0"
2020
aes-gcm-siv = "0.11.1"
2121
hex = "0.4.3"
22+
spectrum-analyzer = "1.4.0"
2223

2324
[target.'cfg(windows)'.dependencies]
2425
winapi = { version = "0.3", features = ["winuser", "dwmapi"] }

‎player/src/audio.rs‎

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ use cpal::{Sample, Stream};
66
use opus::Decoder;
77
use ringbuf::{LocalRb, Rb};
88

9+
use spectrum_analyzer;
10+
use spectrum_analyzer::scaling::{divide_by_N_sqrt, scale_to_zero_to_one, scale_20_times_log10};
11+
912
type Ringbuf<T> = LocalRb<T, Vec<MaybeUninit<T>>>;
1013

1114
const PLAYER_BUFFER_SIZE: usize = 48000 * 10; // 10s of samples?
@@ -14,7 +17,8 @@ pub struct Player {
1417
decoder: Decoder,
1518
buffer: Arc<Mutex<Ringbuf<f32>>>,
1619
stream: Option<Stream>,
17-
frames_received: usize,
20+
pub frames_received: usize,
21+
visualizer_buffer: Arc<Mutex<Vec<[f32; 480 * 5]>>>, // :3
1822
}
1923
impl Player {
2024
pub fn new() -> Self {
@@ -23,13 +27,14 @@ impl Player {
2327
buffer: Arc::new(Mutex::new(Ringbuf::<f32>::new(PLAYER_BUFFER_SIZE))),
2428
stream: None,
2529
frames_received: 0,
30+
visualizer_buffer: Arc::new(Mutex::new(vec![])),
2631
}
2732
}
2833

2934
// decode opus data when received and buffer the samples
3035
pub fn receive(&mut self, frame: Vec<u8>) {
3136
// allocate and decode into here
32-
let mut pcm = vec![0.0; 960];
37+
let mut pcm = [0.0; 960];
3338
self.decoder.decode_float(&frame, &mut pcm, false).unwrap();
3439
self.frames_received += 1;
3540

@@ -41,6 +46,21 @@ impl Player {
4146
// otherwise just discard i guess
4247
println!("player buffer full... discarding :/")
4348
}
49+
50+
let chunk = self.frames_received % 5;
51+
let mut buf = self.visualizer_buffer.lock().unwrap();
52+
let bl = buf.len();
53+
54+
if chunk == 0 || bl == 0 {
55+
buf.push([0.0;480*5]);
56+
}
57+
58+
let bl = buf.len();
59+
60+
for (i, s) in pcm.iter().step_by(2).enumerate() {
61+
let summed = s + pcm[i + 1] / 2.0;
62+
buf[bl-1][chunk + i] = summed;
63+
}
4464
}
4565

4666
pub fn start(&mut self) {
@@ -127,6 +147,10 @@ impl Player {
127147
pub fn fake_frames_received(&mut self, frames: usize) {
128148
self.frames_received = frames;
129149
}
150+
pub fn get_visualizer_buffer(&mut self) -> Arc<Mutex<Vec<[f32; 480 * 5]>>> {
151+
assert!(self.frames_received % 5 == 0);
152+
self.visualizer_buffer.clone()
153+
}
130154
}
131155

132156
// callback when the audio output needs more data
@@ -150,3 +174,46 @@ fn write_audio<T: Sample>(
150174
println!("write_audio: buffer has {} left", buffer.len());
151175
}
152176
}
177+
178+
pub fn calculate_visualizer(samples: &[f32; 2400]) -> [u8; 14] {
179+
let samples_truncated = &samples[..2048];
180+
let hamming_window = spectrum_analyzer::windows::hann_window(samples_truncated);
181+
let spectrum = spectrum_analyzer::samples_fft_to_spectrum(
182+
&hamming_window,
183+
48000,
184+
spectrum_analyzer::FrequencyLimit::All,
185+
Some(&scale_to_zero_to_one),
186+
)
187+
.unwrap();
188+
189+
let mut bars_float = [0.0; 14];
190+
let min_freq = 20.0;
191+
let max_freq = 20000.0;
192+
193+
for (fr, fr_val) in spectrum.data().iter() {
194+
let fr = fr.val();
195+
if fr < 25.0 {
196+
continue;
197+
}
198+
199+
let index = (-5.25062 * fr.log(0.06) - 7.56997).round().min(13.0) as usize;
200+
//println!("{}, {}, {}", index, fr);
201+
let lmao = if fr < 200.0 {
202+
3.0
203+
} else if fr < 500.0 {
204+
2.0
205+
} else {
206+
1.0
207+
};
208+
bars_float[index] += fr_val.val() * lmao;
209+
}
210+
//println!("{:?}", bars_float);
211+
let bars: [u8; 14] = bars_float
212+
.iter()
213+
.map(|&num| (num as f32 / 2.0).round().min(8.0) as u8)
214+
.collect::<Vec<u8>>()
215+
.try_into()
216+
.unwrap();
217+
//println!("{:?}", bars);
218+
bars
219+
}

‎player/src/gui.rs‎

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
use std::cell::RefCell;
2+
use std::rc::Rc;
3+
14
use fltk::app::Sender;
25
use fltk::browser::*;
36
use fltk::button::*;
@@ -12,6 +15,8 @@ use fltk::image::PngImage;
1215
use fltk::input::*;
1316
use fltk::prelude::*;
1417
use fltk::valuator::*;
18+
use fltk::widget;
19+
use fltk::widget_extends;
1520
use fltk::window::*;
1621

1722
use crate::main;
@@ -26,6 +31,7 @@ pub struct MainWindow {
2631
pub lbl_time: Frame,
2732
pub lbl_title: Frame,
2833
pub lbl_data1: Frame,
34+
pub visualizer: Visualizer,
2935
}
3036
impl MainWindow {
3137
pub fn make_window(s: Sender<UIEvent>) -> Self {
@@ -148,6 +154,10 @@ impl MainWindow {
148154
lbl_time.set_align(Align::Left | Align::Inside);
149155
//main_win.add(&lbl_time);
150156

157+
let mut visualizer = Visualizer::new(19, 60);
158+
visualizer.update_values([0, 1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3]);
159+
display.add(&*visualizer);
160+
151161
display.end();
152162
main_win.add(&display);
153163

@@ -165,7 +175,6 @@ impl MainWindow {
165175
btn_queue.emit(s.clone(), UIEvent::BtnQueue);
166176
main_win.add(&btn_queue);
167177

168-
169178
main_win.end();
170179

171180
Self {
@@ -177,6 +186,7 @@ impl MainWindow {
177186
lbl_time,
178187
lbl_title,
179188
lbl_data1,
189+
visualizer
180190
}
181191
}
182192

@@ -245,7 +255,7 @@ impl QueueWindow {
245255

246256
let mut queue_browser =
247257
SelectBrowser::new(10, 24, main_grp.width() - 20, main_grp.height() - 40, "");
248-
258+
249259
main_grp.add(&queue_browser);
250260

251261
let mut btns_grp = Group::new(10, main_grp.height() - 15, 10, 10, "");
@@ -291,6 +301,56 @@ fn create_horizontal_gradient_frame(
291301
frame
292302
}
293303

304+
pub struct Visualizer {
305+
inner: widget::Widget,
306+
values: Rc<RefCell<[u8; 14]>>, // 14 bars (height 0 to 8)
307+
}
308+
309+
widget_extends!(Visualizer, widget::Widget, inner);
310+
311+
impl Visualizer {
312+
pub fn new(x: i32, y: i32) -> Self {
313+
let mut inner = widget::Widget::default().with_size(41, 20).with_pos(x, y);
314+
inner.set_frame(FrameType::FlatBox);
315+
316+
let values = [5; 14];
317+
let values = Rc::from(RefCell::from(values));
318+
let v = values.clone();
319+
320+
inner.draw(move |i| {
321+
draw::draw_box(i.frame(), i.x(), i.y(), i.w(), i.h(), i.color());
322+
draw::set_draw_color(Color::Black);
323+
draw::draw_xyline(i.x(), i.y() + 16, i.x() + (14 * 3) - 2);
324+
for bar in 0..14 {
325+
let value = v.borrow()[bar] as i32;
326+
for val in 0..value {
327+
let x = i.x() + bar as i32 * 3;
328+
let y = i.y() + (7 - val) * 2;
329+
draw::draw_xyline(x, y, x + 1);
330+
}
331+
}
332+
});
333+
334+
Self { inner, values }
335+
}
336+
pub fn update_values(&mut self, values: [u8; 14]) {
337+
{
338+
let prev = self.values.borrow().clone();
339+
let mut sv = self.values.borrow_mut();
340+
for i in 0..14 {
341+
if values[i] > prev[i] {
342+
sv[i] = ((prev[i] as usize + 2*values[i] as usize) / 3 as usize) as u8;
343+
} else if sv[i] > 1 {
344+
sv[i] = sv[i] - 1;
345+
} else {
346+
sv[i] = 0;
347+
}
348+
}
349+
}
350+
self.redraw();
351+
}
352+
}
353+
294354
use winapi::shared::windef::HWND;
295355

296356
use winapi::um::winuser::{

‎player/src/main.rs‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,17 @@ impl Connection {
178178
}
179179

180180
p.receive(frame.data);
181+
if p.frames_received % 5 == 0 {
182+
// visualization buffer ready
183+
let samples = p.get_visualizer_buffer();
184+
let mut samples = samples.lock().unwrap();
185+
if samples.len() < 20 {
186+
continue;
187+
}
188+
let bars = audio::calculate_visualizer(&samples.remove(0));
189+
tx.send(AudioStatus::Visualizer(bars)).unwrap();
190+
191+
}
181192
}
182193
AudioData::Start => {
183194
wants_play = true;
@@ -286,6 +297,9 @@ impl Connection {
286297
AudioStatus::Finished => {
287298
self.ui_status_default();
288299
}
300+
AudioStatus::Visualizer(bars) => {
301+
self.ui_sender.send(UIEvent::Update(UIUpdateEvent::Visualizer(bars)))
302+
}
289303
}
290304
},
291305

@@ -532,6 +546,9 @@ async fn main() -> std::io::Result<()> {
532546
let progress = elapsed as f64 / total as f64;
533547
gui.seek_bar.set_value(progress);
534548
}
549+
UIUpdateEvent::Visualizer(bars) => {
550+
gui.visualizer.update_values(bars);
551+
}
535552
UIUpdateEvent::Status(val) => {
536553
gui.status_field.set_label(&val);
537554
}
@@ -653,6 +670,7 @@ pub enum UIUpdateEvent {
653670
UpdateUserList(HashMap<String, String>),
654671
UpdateQueue(Option<Track>, VecDeque<Track>),
655672
Status(String),
673+
Visualizer([u8; 14]),
656674
}
657675

658676
struct UIState {
@@ -665,6 +683,7 @@ enum AudioStatus {
665683
Buffering,
666684
DoneBuffering,
667685
Finished,
686+
Visualizer([u8; 14]),
668687
}
669688

670689
#[derive(Debug, PartialEq)]

0 commit comments

Comments
 (0)