refactored utils into its own waveform struct, shorten main

This commit is contained in:
ao kami 2025-07-14 20:05:00 +02:00
parent 2e7dbe74e3
commit 8cdd6b06e2
3 changed files with 125 additions and 96 deletions

View file

@ -1,17 +1,18 @@
mod pw;
mod utils;
mod waveform;
fn main() -> iced::Result {
iced::application(Spectrogram::new, Spectrogram::update, Spectrogram::view)
.subscription(Spectrogram::subscription)
iced::application(RumbleWrecker::new, RumbleWrecker::update, RumbleWrecker::view)
.subscription(RumbleWrecker::subscription)
// .title("Rumble Wrecker")
.run()
}
struct Spectrogram {
struct RumbleWrecker {
refresh_rate: u64,
stream_left: std::sync::mpsc::Receiver<Vec<f32>>,
stream_right: std::sync::mpsc::Receiver<Vec<f32>>,
waveform_img: Rgba,
waveform: waveform::Waveform,
}
struct Rgba {
@ -25,7 +26,7 @@ enum Message {
Tick,
}
impl Spectrogram {
impl RumbleWrecker {
fn new() -> Self {
let (tx_left, rx_left) = std::sync::mpsc::channel::<Vec<f32>>();
let (tx_right, rx_right) = std::sync::mpsc::channel::<Vec<f32>>();
@ -36,12 +37,12 @@ impl Spectrogram {
let w = 1024u32;
let h = 256u32;
let p = vec![255; (w*h*4) as usize];
Self {
refresh_rate: 40,
stream_left: rx_left,
stream_right: rx_right,
waveform_img: Rgba { width: w, height:h, pixels: p }
waveform: waveform::Waveform::new(w, h),
}
}
@ -58,23 +59,21 @@ impl Spectrogram {
}
if !left_samples.is_empty() && !right_samples.is_empty() {
utils::update_waveform(&mut self.waveform_img, (&left_samples, &right_samples), 0.5);
self.waveform.update((&left_samples, &right_samples), 0.5);
}
}
}
}
fn view(&self) -> iced::Element<'_, Message> {
let img_handle = iced::widget::image::Handle::from_rgba(
self.waveform_img.width,
self.waveform_img.height,
self.waveform_img.pixels.clone()
);
let waveform = self.waveform.view();
iced::widget::center_x(iced::widget::image(img_handle)).into()
// refresh rate slider
iced::widget::Column::new().push(waveform).into()
}
fn subscription(&self) -> iced::Subscription<Message> {
iced::time::every(std::time::Duration::from_millis(40)).map(|_| Message::Tick)
iced::time::every(std::time::Duration::from_millis(self.refresh_rate)).map(|_| Message::Tick)
}
}

View file

@ -1,80 +0,0 @@
pub fn update_waveform( rgba: &mut crate::Rgba, samples: (&Vec<f32>, &Vec<f32>), resolution: f32 ) {
let (left_samples, right_samples) = samples;
let mut last_rows = ((rgba.height/2) as usize, (rgba.height/2) as usize);
let span = (rgba.width as f32 * resolution) as usize;
let moved_start = rgba.width as usize - span;
if resolution < 1.0 {
let mut previous_left_row = (rgba.height as usize, 0usize);
let mut previous_right_row = (rgba.height as usize, 0usize);
for row in 0..rgba.height as usize {
// Get min/max left/right values for last column looking in each row
let last_pixel_idx = (((row+1)*rgba.width as usize)-1)*4;
let last_row_pixel = &rgba.pixels[last_pixel_idx..last_pixel_idx+4];
// LEFT
if last_row_pixel[2] == 0 {
if row < previous_left_row.0 { previous_left_row.0 = row; }
if row > previous_left_row.1 { previous_left_row.1 = row; }
}
// RIGHT
if last_row_pixel[0] == 0 {
if row < previous_right_row.0 { previous_right_row.0 = row; }
if row > previous_right_row.1 { previous_right_row.1 = row; }
}
let row_offset = row * rgba.width as usize*4;
// Translate image to the left
// |--^#####^xx| => |#####^xx^xx|
let moved_slice : Vec<u8> = rgba.pixels[row_offset+span*4..row_offset+rgba.width as usize*4].into();
rgba.pixels[row_offset..row_offset+moved_start*4].copy_from_slice(&moved_slice);
// |#####^xx^xx| => |#####^xx^ |
let blank = vec![255; span*4];
rgba.pixels[row_offset+moved_start*4..row_offset+rgba.width as usize*4].copy_from_slice(&blank);
}
// Set function scoped last rows
if previous_left_row.0 != rgba.height as usize && previous_left_row.1 != 0 {
last_rows.0 = if (previous_left_row.0 as f32 - 128f32).abs() < (previous_left_row.1 as f32 - 128f32).abs() {previous_left_row.0} else {previous_left_row.1};
}
if previous_right_row.0 != rgba.height as usize && previous_right_row.1 != 0 {
last_rows.1 = if (previous_right_row.0 as f32 - 128f32).abs() < (previous_right_row.1 as f32 - 128f32).abs() {previous_right_row.0} else {previous_right_row.1};
}
}
else {
rgba.pixels.fill(255);
}
let left_column_width : usize = left_samples.len() / span;
let right_column_width : usize = right_samples.len() / span;
for x in 0..span {
// LEFT CHANNEL
let column_values : &[f32] = &left_samples[x*left_column_width..(x+1)*left_column_width];
let column_average : f32 = (column_values.iter().cloned().reduce(|acc, v| acc+v).unwrap_or(0f32) / column_values.len() as f32).clamp(-0.99, 0.99);
let row = (128f32 - (column_average * 128f32)).round() as usize;
// Draw vertical lines from last row
let range : Vec<usize> = if row <= last_rows.0 {(row..=last_rows.0).collect()} else {(last_rows.0..=row).rev().collect()};
for row in range {
let coord = (row * rgba.width as usize * 4) + ((moved_start + x) * 4);
rgba.pixels[coord+0] = 0;
rgba.pixels[coord+1] = 0;
}
last_rows.0 = row;
// RIGHT CHANNEL
let column_values : &[f32] = &right_samples[x*right_column_width..(x+1)*right_column_width];
let column_average : f32 = (column_values.iter().cloned().reduce(|acc, v| acc+v).unwrap_or(0f32) / column_values.len() as f32).clamp(-0.99, 0.99);
let row = (128f32 - (column_average * 128f32)).round() as usize;
// Draw vertical lines from last row
let range : Vec<usize> = if row <= last_rows.1 {(row..=last_rows.1).collect()} else {(last_rows.1..=row).rev().collect()};
for row in range {
let coord = (row * rgba.width as usize * 4) + ((moved_start + x) * 4);
rgba.pixels[coord+1] = 0;
rgba.pixels[coord+2] = 0;
}
last_rows.1 = row;
}
}

110
src/waveform.rs Normal file
View file

@ -0,0 +1,110 @@
use crate::Rgba;
pub struct Waveform {
rgba: Rgba,
}
impl Waveform {
pub fn new(width: u32, height: u32) -> Self {
Self {
rgba: Rgba {
width: width,
height: height,
pixels: vec![255; (width*height*4) as usize],
}
}
}
pub fn update(&mut self, samples: (&Vec<f32>, &Vec<f32>), resolution: f32 ) {
let (left_samples, right_samples) = samples;
let mut last_rows = ((self.rgba.height/2) as usize, (self.rgba.height/2) as usize);
let span = (self.rgba.width as f32 * resolution) as usize;
let moved_start = self.rgba.width as usize - span;
if resolution < 1.0 {
let mut previous_left_row = (self.rgba.height as usize, 0usize);
let mut previous_right_row = (self.rgba.height as usize, 0usize);
for row in 0..self.rgba.height as usize {
// Get min/max left/right values for last column looking in each row
let last_pixel_idx = (((row+1)*self.rgba.width as usize)-1)*4;
let last_row_pixel = &self.rgba.pixels[last_pixel_idx..last_pixel_idx+4];
// LEFT
if last_row_pixel[2] == 0 {
if row < previous_left_row.0 { previous_left_row.0 = row; }
if row > previous_left_row.1 { previous_left_row.1 = row; }
}
// RIGHT
if last_row_pixel[0] == 0 {
if row < previous_right_row.0 { previous_right_row.0 = row; }
if row > previous_right_row.1 { previous_right_row.1 = row; }
}
let row_offset = row * self.rgba.width as usize*4;
// Translate image to the left
// |--^#####^xx| => |#####^xx^xx|
let moved_slice : Vec<u8> = self.rgba.pixels[row_offset+span*4..row_offset+self.rgba.width as usize*4].into();
self.rgba.pixels[row_offset..row_offset+moved_start*4].copy_from_slice(&moved_slice);
// |#####^xx^xx| => |#####^xx^ |
let blank = vec![255; span*4];
self.rgba.pixels[row_offset+moved_start*4..row_offset+self.rgba.width as usize*4].copy_from_slice(&blank);
}
// Set function scoped last rows
if previous_left_row.0 != self.rgba.height as usize && previous_left_row.1 != 0 {
last_rows.0 = if (previous_left_row.0 as f32 - 128f32).abs() < (previous_left_row.1 as f32 - 128f32).abs() {previous_left_row.0} else {previous_left_row.1};
}
if previous_right_row.0 != self.rgba.height as usize && previous_right_row.1 != 0 {
last_rows.1 = if (previous_right_row.0 as f32 - 128f32).abs() < (previous_right_row.1 as f32 - 128f32).abs() {previous_right_row.0} else {previous_right_row.1};
}
}
else {
self.rgba.pixels.fill(255);
}
let left_column_width : usize = left_samples.len() / span;
let right_column_width : usize = right_samples.len() / span;
for x in 0..span {
// LEFT CHANNEL
let column_values : &[f32] = &left_samples[x*left_column_width..(x+1)*left_column_width];
let column_average : f32 = (column_values.iter().cloned().reduce(|acc, v| acc+v).unwrap_or(0f32) / column_values.len() as f32).clamp(-0.99, 0.99);
let row = (128f32 - (column_average * 128f32)).round() as usize;
// Draw vertical lines from last row
let range : Vec<usize> = if row <= last_rows.0 {(row..=last_rows.0).collect()} else {(last_rows.0..=row).rev().collect()};
for row in range {
let coord = (row * self.rgba.width as usize * 4) + ((moved_start + x) * 4);
self.rgba.pixels[coord+0] = 0;
self.rgba.pixels[coord+1] = 0;
}
last_rows.0 = row;
// RIGHT CHANNEL
let column_values : &[f32] = &right_samples[x*right_column_width..(x+1)*right_column_width];
let column_average : f32 = (column_values.iter().cloned().reduce(|acc, v| acc+v).unwrap_or(0f32) / column_values.len() as f32).clamp(-0.99, 0.99);
let row = (128f32 - (column_average * 128f32)).round() as usize;
// Draw vertical lines from last row
let range : Vec<usize> = if row <= last_rows.1 {(row..=last_rows.1).collect()} else {(last_rows.1..=row).rev().collect()};
for row in range {
let coord = (row * self.rgba.width as usize * 4) + ((moved_start + x) * 4);
self.rgba.pixels[coord+1] = 0;
self.rgba.pixels[coord+2] = 0;
}
last_rows.1 = row;
}
}
pub fn view(&self) -> iced::Element<'_, crate::Message> {
let img_handle = iced::widget::image::Handle::from_rgba(
self.rgba.width,
self.rgba.height,
self.rgba.pixels.clone()
);
iced::widget::center_x(iced::widget::image(img_handle)).into()
}
}