Skip to content

Commit

Permalink
Merge pull request #1 from gursi26/bevy-renderer
Browse files Browse the repository at this point in the history
Rewrite renderer in bevy
  • Loading branch information
gursi26 committed Mar 5, 2024
2 parents 405059d + db6dbbb commit bf29634
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 79 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "fftviz"
version = "0.1.3"
version = "0.1.5"
edition = "2021"
authors = ["Gursimar Singh <gursi26.dev@gmail.com>"]
license = "MIT"
Expand All @@ -12,9 +12,9 @@ keywords = ["cli"]
categories = ["command-line-utilities"]

[dependencies]
bevy = "0.13.0"
bincode = "1.3.3"
clap = { version = "4.5.0", features = ["derive"] }
raylib = "3.7.0"
rayon = "1.9.0"
rodio = "0.17.3"
serde = { version = "1.0.197", features = ["derive"] }
Expand Down
Binary file removed assets/demo.png
Binary file not shown.
Binary file added assets/fonts/Roboto-Regular.ttf
Binary file not shown.
4 changes: 2 additions & 2 deletions src/fft.rs
Expand Up @@ -21,10 +21,10 @@ pub struct FFT {
}


pub fn time_interpolate(v1: &Vec<f32>, v2: &Vec<f32>) -> Vec<f32> {
pub fn time_interpolate(v1: &Vec<f32>, v2: &Vec<f32>, alpha: f32) -> Vec<f32> {
v1.iter()
.zip(v2.iter())
.map(|(x, y)| (x.clone() + y.clone()) / 2.0)
.map(|(x, y)| x.clone() * (1.0 - alpha) + y.clone() * alpha)
.collect::<Vec<f32>>()
}

Expand Down
269 changes: 199 additions & 70 deletions src/main.rs
@@ -1,16 +1,23 @@
mod utils;
mod fft;
mod utils;

use utils::*;
use fft::*;
use utils::*;

use bevy::{
prelude::*,
sprite::{MaterialMesh2dBundle, Mesh2dHandle},
};
use clap::{ArgAction, Parser};
use raylib::prelude::*;
use rodio::{source::Source, Decoder, OutputStream};
use std::ffi::OsString;
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;
use std::time::Duration;
use std::ffi::OsString;
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
use bevy::render::mesh::VertexAttributeValues;
use bevy::sprite::Anchor;


// Constants
Expand All @@ -24,7 +31,7 @@ const FREQ_WINDOW_LOW: f32 = 0.0;
const FREQ_WINDOW_HIGH: f32 = 5000.0;
const FFT_WINDOW: i32 =
((256 as u64 / 107 as u64) * FREQUENCY_RESOLUTION as u64).next_power_of_two() as i32;
const BAR_INTERPOLATION_FACTOR: u32 = 2;
const BAR_INTERPOLATION_FACTOR: u32 = 1;
const RESCALING_THRESHOLDS: &[f32] = &[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9];
const RESCALING_FACTOR: &[f32] = &[2.0, 1.7, 1.3, 1.2, 1.1, 1.0, 0.9, 0.8, 0.7];

Expand Down Expand Up @@ -65,15 +72,159 @@ struct CLIArgs {
background_color: String,
}

#[derive(Resource)]
struct FFTArgs {
file_path: String,
file_path: PathBuf,
border_size: i32,
border_color: Color,
bar_color: Color,
border_color: String,
bar_color: String,
disable_title: bool,
text_color: Color,
text_color: String,
font_size: i32,
background_color: Color
background_color: String,
}

#[derive(Resource)]
struct FFTQueue {
fft: Vec<Vec<f32>>,
curr_bars: Vec<Handle<Mesh>>,
c: usize,
i: usize,
}

fn update_bars(
mut window: Query<&mut Window>,
mut meshes: ResMut<Assets<Mesh>>,
mut fft_queue: ResMut<FFTQueue>,
args: Res<FFTArgs>,
) {
let h = window.single_mut().physical_height();
let mut update_i = false;
let interval = RENDERING_FPS / FFT_FPS;

let curr_fft = match fft_queue.c as u32 % interval {
0 => {
if fft_queue.i > fft_queue.fft.len() {
std::process::exit(0);
}
update_i = true;
fft_queue.fft[fft_queue.i].clone()
}
rem => time_interpolate(
&(fft_queue.fft[fft_queue.i - 1]),
&(fft_queue.fft[fft_queue.i]),
rem as f32 / interval as f32,
),
};

for (handle, new_value) in fft_queue.curr_bars.chunks(2).zip(curr_fft.iter()) {
let (handle1, handle2) = (handle[0].clone(), handle[1].clone());

let rect = meshes.get_mut(handle1).unwrap();
let dims = rect.attribute_mut(Mesh::ATTRIBUTE_POSITION).unwrap();
match dims {
VertexAttributeValues::Float32x3(x) => {
x[0][1] = new_value.clone() * (h / 2) as f32;
x[1][1] = new_value.clone() * (h / 2) as f32;
x[2][1] = -new_value.clone() * (h / 2) as f32;
x[3][1] = -new_value.clone() * (h / 2) as f32;
}
_ => {}
}

let rect = meshes.get_mut(handle2).unwrap();
let dims = rect.attribute_mut(Mesh::ATTRIBUTE_POSITION).unwrap();
match dims {
VertexAttributeValues::Float32x3(x) => {
x[0][1] = new_value.clone() * (h / 2) as f32 - args.border_size as f32;
x[1][1] = new_value.clone() * (h / 2) as f32 - args.border_size as f32;
x[2][1] = -new_value.clone() * (h / 2) as f32 + args.border_size as f32;
x[3][1] = -new_value.clone() * (h / 2) as f32 + args.border_size as f32;
}
_ => {}
}
}

if update_i {
fft_queue.i += 1;
}
fft_queue.c += 1;
}

fn startup(
mut commands: Commands,
mut window: Query<&mut Window>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
mut fft_queue: ResMut<FFTQueue>,
args: Res<FFTArgs>,
asset_server: Res<AssetServer>,
) {
commands.spawn(Camera2dBundle {
camera: Camera {
clear_color: ClearColorConfig::Custom(
Color::hex(args.background_color.clone()).unwrap(),
),
..Default::default()
},
..Default::default()
});

let w = window.single_mut().physical_width();
let h = window.single_mut().physical_height();

if !args.disable_title {
let font = asset_server.load("fonts/Roboto-Regular.ttf");
let text_style = TextStyle {
font: font.clone(),
font_size: args.font_size as f32,
color: Color::hex(args.text_color.clone()).unwrap(),
};

commands.spawn(
Text2dBundle {
text: Text::from_section(format!("Playing: \"{}\"", args.file_path.file_name().unwrap().to_str().unwrap()), text_style.clone()),
transform: Transform::from_xyz(-(w as f32 / 2.0) + 10.0, (h as f32 / 2.0) - 10.0, 0.0),
text_anchor: Anchor::TopLeft,
..default()
},
);
};

let num_bars = fft_queue.fft[0].len();
let bar_size = w as f32 / num_bars as f32;
let mut handle_vec = Vec::new();

for i in 0..num_bars {
let handle1 = meshes.add(Rectangle::new(bar_size, 0.0));
handle_vec.push(handle1.clone());

commands.spawn(MaterialMesh2dBundle {
mesh: Mesh2dHandle(handle1),
material: materials.add(Color::hex(args.border_color.clone()).unwrap()),
transform: Transform::from_xyz(
bar_size * i as f32 + (bar_size / 2.0) - (w / 2) as f32,
0.0,
0.0,
),
..default()
});

let handle2 = meshes.add(Rectangle::new(bar_size - args.border_size as f32, 0.0));
handle_vec.push(handle2.clone());

commands.spawn(MaterialMesh2dBundle {
mesh: Mesh2dHandle(handle2),
material: materials.add(Color::hex(args.bar_color.clone()).unwrap()),
transform: Transform::from_xyz(
bar_size * i as f32 + (bar_size / 2.0) - (w / 2) as f32,
0.0,
0.0,
),
..default()
});
}
fft_queue.curr_bars = handle_vec;
}

fn main() {
Expand All @@ -98,70 +249,48 @@ fn main() {
*c = reversed;
}

let mut fft = fft_vec.into_iter().peekable();
let mut i = 0;

let (mut rl, thread) = raylib::init().title("fftviz").build();
rl.set_target_fps(RENDERING_FPS);
rl.set_window_size(SCREEN_WIDTH, SCREEN_HEIGHT);
fft_vec
.par_iter_mut()
.for_each(|x| space_interpolate(x, BAR_INTERPOLATION_FACTOR));

let mut binding = App::new();
let app = binding
.add_plugins((DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "fftviz".into(),
name: Some("fftviz".into()),
resolution: (SCREEN_WIDTH as f32, SCREEN_HEIGHT as f32).into(),
resizable: false,
position: WindowPosition::Centered(MonitorSelection::Current),
prevent_default_event_handling: false,
enabled_buttons: bevy::window::EnabledButtons {
maximize: false,
..Default::default()
},
visible: true,
..default()
}),
..default()
}),))
.insert_resource(FFTQueue {
fft: fft_vec,
curr_bars: Vec::new(),
i: 0,
c: 0,
})
.insert_resource(args)
.add_systems(Startup, startup)
.add_systems(
Update,
(update_bars.run_if(bevy::time::common_conditions::on_timer(
Duration::from_secs_f64(1.0 / RENDERING_FPS as f64),
)),),
);

let (_stream, stream_handle) = OutputStream::try_default().unwrap();
let file = BufReader::new(File::open(&p).unwrap());
let source = Decoder::new(file).unwrap();
stream_handle.play_raw(source.convert_samples()).unwrap();

let num_frame_gen = RENDERING_FPS / FFT_FPS;
let mut fft_chunk: Vec<f32> = Vec::new();

while !rl.window_should_close() && fft.peek().is_some() && !rl.is_key_down(KeyboardKey::KEY_Q) {
let mut d = rl.begin_drawing(&thread);
d.clear_background(args.background_color);

if i as u32 % num_frame_gen == 0 {
fft_chunk = fft.next().unwrap();
} else {
let next_chunk = fft.peek().unwrap();
fft_chunk = time_interpolate(&fft_chunk, next_chunk);
}

let mut new_chunk = fft_chunk.clone();
space_interpolate(&mut new_chunk, BAR_INTERPOLATION_FACTOR);

let (h, w) = (d.get_screen_height(), d.get_screen_width());
let bar_start_idxs = (0..((w as i32) + 1))
.step_by(w as usize / new_chunk.len() as usize)
.collect::<Vec<i32>>();

for j in 0..(bar_start_idxs.len() - 1 as usize) {
let curr_fft_value =
(new_chunk[j.clamp(0, new_chunk.len() - 1)] * h as f32 * 0.5) as i32;
let (start_x_id, end_x_id) = (bar_start_idxs[j], bar_start_idxs[j + 1]);
d.draw_rectangle(
start_x_id,
(h / 2) - curr_fft_value,
end_x_id - start_x_id,
curr_fft_value * 2,
args.border_color,
);

d.draw_rectangle(
start_x_id + args.border_size,
(h / 2) - curr_fft_value + args.border_size,
end_x_id - start_x_id - (args.border_size * 2),
curr_fft_value * 2 - (args.border_size * 2),
args.bar_color,
);
}

if !args.disable_title {
d.draw_text(
&format!("Playing: {:?}", p.file_stem().unwrap().to_str().unwrap())[..],
10,
10,
args.font_size,
args.text_color,
);
}
i += 1;
}
app.run();
}
10 changes: 5 additions & 5 deletions src/utils.rs
Expand Up @@ -7,13 +7,13 @@ pub fn cli_args_to_fft_args(cli_args: CLIArgs) -> FFTArgs {
std::process::exit(1);
}
FFTArgs {
file_path: cli_args.file_path,
file_path: Path::new(&cli_args.file_path).to_path_buf(),
border_size: cli_args.border_size as i32,
border_color: Color::from_hex(&cli_args.border_color[..]).unwrap(),
bar_color: Color::from_hex(&cli_args.bar_color[..]).unwrap(),
border_color: cli_args.border_color,
bar_color: cli_args.bar_color,
disable_title: cli_args.disable_title,
text_color: Color::from_hex(&cli_args.text_color[..]).unwrap(),
text_color: cli_args.text_color,
font_size: cli_args.font_size as i32,
background_color: Color::from_hex(&cli_args.background_color[..]).unwrap()
background_color: cli_args.background_color
}
}

0 comments on commit bf29634

Please sign in to comment.