Skip to content

Commit

Permalink
Merge pull request #11 from gursi26/config
Browse files Browse the repository at this point in the history
Added persistent configuration settings with config.yaml
  • Loading branch information
gursi26 committed Mar 13, 2024
2 parents 3e8d266 + d22febc commit 3bdf3e3
Show file tree
Hide file tree
Showing 6 changed files with 301 additions and 72 deletions.
5 changes: 4 additions & 1 deletion Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "fftviz"
version = "0.2.2"
version = "0.3.0"
edition = "2021"
authors = ["Gursimar Singh <gursi26.dev@gmail.com>"]
license = "MIT"
Expand All @@ -20,7 +20,10 @@ microfft = "0.5.1"
rayon = "1.9.0"
rodio = "0.17.3"
serde = { version = "1.0.197", features = ["derive"] }
dirs = "5.0.1"
serde_yaml = "0.9.32"
spectrum-analyzer = "1.5.0"
stopwatch = "0.0.7"

# [profile.release]
# lto = true
Expand Down
112 changes: 52 additions & 60 deletions src/args.rs
Expand Up @@ -10,105 +10,104 @@ pub struct CLIArgs {
#[arg()]
file_path: String,

/// Temporal resolution for FFT calculation (rendering always occurs at 60 fps with interpolation)
#[arg(long = "fft-fps", default_value_t = 12)]
fft_fps: u32,

/// Smoothing factor for spatial interpolation between bars
#[clap(long = "smoothness", default_value_t = 1)]
smoothness: u32,
#[clap(long = "smoothness", default_value = None)]
pub smoothness: Option<u32>,

/// Number of individual frequencies detected by the FFT
#[arg(long = "freq-resolution", default_value_t = 90)]
freq_resolution: u32,
#[arg(long = "freq-resolution", default_value = None)]
pub freq_resolution: Option<u32>,

/// Maximum frequency detected by FFT
#[arg(long = "min-freq", default_value_t = 0.0)]
min_freq: f32,
#[arg(long = "min-freq", default_value = None)]
pub min_freq: Option<f32>,

/// Minimum frequency detected by FFT
#[arg(long = "max-freq", default_value_t = 5000.0)]
max_freq: f32,
#[arg(long = "max-freq", default_value = None)]
pub max_freq: Option<f32>,

/// Volume
#[arg(long = "volume", default_value_t = 0.7)]
volume: f32,
#[arg(long = "volume", default_value = None)]
pub volume: Option<u32>,

/// Window width
#[arg(long = "width", default_value_t = 1000.0)]
window_width: f32,
#[arg(long = "width", default_value = None)]
pub window_width: Option<f32>,

/// Window height
#[arg(long = "height", default_value_t = 700.0)]
window_height: f32,
#[arg(long = "height", default_value = None)]
pub window_height: Option<f32>,

/// Border size for each bar
#[arg(long = "border-size", default_value_t = 1)]
border_size: u32,
#[arg(long = "border-size", default_value = None)]
pub border_size: Option<i32>,

/// Border color for each bar (in hex)
#[arg(long = "border-color", default_value_t = String::from("000000"))]
border_color: String,
#[arg(long = "border-color", default_value = None)]
pub border_color: Option<String>,

/// Color for each bar (in hex)
#[arg(long = "bar-color", default_value_t = String::from("FF0000"))]
bar_color: String,
#[arg(long = "bar-color", default_value = None)]
pub bar_color: Option<String>,

/// Use if you want track name to be printed
#[arg(long = "track-name", action = ArgAction::SetTrue)]
track_name: bool,
pub track_name: Option<bool>,

/// Use if you want the gui to be open when launched
#[arg(long = "display-gui", action = ArgAction::SetTrue)]
display_gui: bool,
pub display_gui: Option<bool>,

/// Color for currently playing text (in hex)
#[arg(long = "text-color", default_value_t = String::from("FFFFFF"))]
text_color: String,
#[arg(long = "text-color", default_value = None)]
pub text_color: Option<String>,

/// Font size of currently playing label
#[arg(long = "font-size", default_value_t = 25)]
font_size: u32,
#[arg(long = "font-size", default_value = None)]
pub font_size: Option<i32>,

// Background color (in hex)
#[arg(long = "background-color", default_value_t = String::from("000000"))]
background_color: String,
#[arg(long = "background-color", default_value = None)]
pub background_color: Option<String>,
}

pub fn cli_args_to_fft_args(cli_args: CLIArgs) -> FFTArgs {
pub fn cli_args_to_fft_args(mut cli_args: CLIArgs, use_default: bool) -> FFTArgs {
if !Path::new(&cli_args.file_path).is_file() {
println!("File \"{}\" not found!", cli_args.file_path);
std::process::exit(1);
}

bar_smoothness_constraint(cli_args.smoothness);
fft_fps_constraint(cli_args.fft_fps);
freq_resolution_constraint(cli_args.freq_resolution);
// Merges cli args with args in config.yaml.
// Precendence: Cli args > config.yaml args > default values
merge_config_with_cli_args(&mut cli_args, use_default);

bar_smoothness_constraint(cli_args.smoothness.unwrap());
freq_resolution_constraint(cli_args.freq_resolution.unwrap());

FFTArgs {
file_path: Path::new(&cli_args.file_path).to_path_buf(),
border_size: cli_args.border_size as i32,
border_color: Color::hex(cli_args.border_color).unwrap(),
bar_color: Color::hex(cli_args.bar_color).unwrap(),
track_name: cli_args.track_name,
text_color: Color::hex(cli_args.text_color).unwrap(),
font_size: cli_args.font_size as i32,
background_color: Color::hex(cli_args.background_color).unwrap(),
smoothness: cli_args.smoothness,
fft_fps: cli_args.fft_fps,
freq_resolution: cli_args.freq_resolution,
window_height: cli_args.window_height,
window_width: cli_args.window_width,
min_freq: cli_args.min_freq,
max_freq: cli_args.max_freq,
display_gui: cli_args.display_gui,
volume: cli_args.volume,
border_size: cli_args.border_size.unwrap(),
border_color: Color::hex(cli_args.border_color.unwrap()).unwrap(),
bar_color: Color::hex(cli_args.bar_color.unwrap()).unwrap(),
track_name: cli_args.track_name.unwrap(),
text_color: Color::hex(cli_args.text_color.unwrap()).unwrap(),
font_size: cli_args.font_size.unwrap(),
background_color: Color::hex(cli_args.background_color.unwrap()).unwrap(),
smoothness: cli_args.smoothness.unwrap(),
freq_resolution: cli_args.freq_resolution.unwrap(),
window_height: cli_args.window_height.unwrap(),
window_width: cli_args.window_width.unwrap(),
min_freq: cli_args.min_freq.unwrap(),
max_freq: cli_args.max_freq.unwrap(),
display_gui: cli_args.display_gui.unwrap(),
volume: cli_args.volume.unwrap(),
paused: false,
fft_fps: FFT_FPS
}
}

pub fn parse_cli_args() -> FFTArgs {
cli_args_to_fft_args(args::CLIArgs::parse())
cli_args_to_fft_args(args::CLIArgs::parse(), false)
}
// Value constraints
pub fn bar_smoothness_constraint(v: u32) {
Expand All @@ -118,13 +117,6 @@ pub fn bar_smoothness_constraint(v: u32) {
}
}

pub fn fft_fps_constraint(v: u32) {
if v < 6 || v > 60 || RENDERING_FPS % v != 0 {
println!("fft-fps must be between 6 and 60 inclusive and divide RENDERING_FPS = {}.", RENDERING_FPS);
std::process::exit(1);
}
}

fn freq_resolution_constraint(v: u32) {
if v < 10 || v > 300 {
println!("freq-resolution must be between 10 and 300 inclusive.");
Expand Down
198 changes: 198 additions & 0 deletions src/config.rs
@@ -0,0 +1,198 @@
use dirs::home_dir;
use std::{fs::remove_file, io::{stdout, Read, Write}, path::PathBuf};
use serde::{Deserialize, Serialize};
use serde_yaml::{self};
use std::fs::{read_to_string, create_dir, create_dir_all, File, OpenOptions};
use std::io::BufWriter;
use bevy::prelude::Color;

use crate::{CLIArgs, FFTArgs};

#[derive(Serialize, Deserialize, Debug)]
pub struct ConfigFFTArgs {
pub border_size: Option<i32>,
pub border_color: Option<String>,
pub bar_color: Option<String>,
pub display_track_name: Option<bool>,
pub text_color: Option<String>,
pub font_size: Option<i32>,
pub background_color: Option<String>,
pub smoothness: Option<u32>,
pub freq_resolution: Option<u32>,
pub window_width: Option<f32>,
pub window_height: Option<f32>,
pub min_freq: Option<f32>,
pub max_freq: Option<f32>,
pub display_gui: Option<bool>,
pub volume: Option<u32>,
}

impl Default for ConfigFFTArgs {
fn default() -> Self {
ConfigFFTArgs {
border_size: Some(1),
border_color: Some(String::from("000000")),
bar_color: Some(String::from("FF0000")),
display_track_name: Some(false),
text_color: Some(String::from("FFFFFF")),
font_size: Some(25),
background_color: Some(String::from("000000")),
smoothness: Some(1),
freq_resolution: Some(90),
window_width: Some(1000.0),
window_height: Some(700.0),
min_freq: Some(0.0),
max_freq: Some(5000.0),
display_gui: Some(false),
volume: Some(50),
}
}
}

pub fn config_path() -> PathBuf {
let mut config_path = home_dir().unwrap();
config_path.push(".config");
config_path.push("fftviz");
config_path.push("config.yaml");
config_path
}

pub fn config_exists() -> bool {
let cfg_path = config_path();
cfg_path.exists()
}

pub fn read_config() -> ConfigFFTArgs {
let config_file = File::open(config_path()).expect("Could not find file lmao");
let user_config_yaml =
serde_yaml::from_reader(config_file).expect("Could not read values lmao");
user_config_yaml
}

macro_rules! update_cli_arg {
($cli_arg: expr, $config_arg: expr, $default_config_arg: expr) => {
if let None = *$cli_arg {
if let None = $config_arg {
*$cli_arg = $default_config_arg;
} else {
*$cli_arg = $config_arg;
}
}
};
}

macro_rules! overwrite_non_default_args {
($user_config_arg: expr, $fft_arg: expr) => {
*$user_config_arg = Some($fft_arg);
};
}

pub fn convert_color_to_hex(c: &Color) -> String {
let c_vec = c.rgb_to_vec3();
let (r, g, b) = ((c_vec.x * 255.0) as u32, (c_vec.y * 255.0) as u32, (c_vec.z * 255.0) as u32);
format!("{}{}{}", format!("{:02X}", r), format!("{:02X}", g), format!("{:02X}", b))
}

pub fn write_fftargs_to_config(args: &FFTArgs) {
let mut default_args = ConfigFFTArgs::default();
overwrite_non_default_args!(&mut default_args.background_color, convert_color_to_hex(&args.background_color));
overwrite_non_default_args!(&mut default_args.border_color, convert_color_to_hex(&args.border_color));
overwrite_non_default_args!(&mut default_args.bar_color, convert_color_to_hex(&args.bar_color));
overwrite_non_default_args!(&mut default_args.text_color, convert_color_to_hex(&args.text_color));
overwrite_non_default_args!(&mut default_args.border_size, args.border_size);
overwrite_non_default_args!(&mut default_args.display_track_name, args.track_name);
overwrite_non_default_args!(&mut default_args.font_size, args.font_size);
overwrite_non_default_args!(&mut default_args.smoothness, args.smoothness);
overwrite_non_default_args!(&mut default_args.freq_resolution, args.freq_resolution);
overwrite_non_default_args!(&mut default_args.window_width, args.window_width);
overwrite_non_default_args!(&mut default_args.window_height, args.window_height);
overwrite_non_default_args!(&mut default_args.min_freq, args.min_freq);
overwrite_non_default_args!(&mut default_args.max_freq, args.max_freq);
overwrite_non_default_args!(&mut default_args.volume, args.volume);

let cfg_path = config_path();
create_dir_all(cfg_path.as_path().parent().unwrap());

let mut config_file = OpenOptions::new()
.write(true)
.create(true)
.open(&cfg_path)
.expect("Could not open file.");
serde_yaml::to_writer(config_file, &default_args).unwrap();

let mut cfg_yaml: Vec<String> = read_to_string(&cfg_path)
.unwrap() // panic on possible file-reading errors
.lines() // split the string into an iterator of string slices
.map(String::from) // make each slice into a string
.collect(); // gather them together into a vector

cfg_yaml.retain(|x| x.contains(":"));

remove_file(&cfg_path);

let f = File::create(&cfg_path).expect("Unable to create file");
let mut f = BufWriter::new(f);
f.write_all(cfg_yaml.join("\n").as_bytes()).expect("Unable to write data");
}

pub fn reset_config_file() {
let default_user_config = ConfigFFTArgs::default();

let cfg_path = config_path();
create_dir_all(cfg_path.as_path().parent().unwrap());

let mut config_file = OpenOptions::new()
.write(true)
.create(true)
.open(cfg_path)
.expect("Could not open file.");
serde_yaml::to_writer(config_file, &default_user_config).unwrap();
}

pub fn merge_config_with_cli_args(args: &mut CLIArgs, use_default: bool) {
let default_user_config = ConfigFFTArgs::default();
if !config_exists() {
update_cli_arg!(&mut args.background_color, None::<String>, default_user_config.background_color);
update_cli_arg!(&mut args.bar_color, None::<String>, default_user_config.bar_color);
update_cli_arg!(&mut args.border_color, None::<String>, default_user_config.border_color);
update_cli_arg!(&mut args.border_size, None::<i32>, default_user_config.border_size);
update_cli_arg!(&mut args.font_size, None::<i32>, default_user_config.font_size);
update_cli_arg!(&mut args.freq_resolution, None::<u32>, default_user_config.freq_resolution);
update_cli_arg!(&mut args.max_freq, None::<f32>, default_user_config.max_freq);
update_cli_arg!(&mut args.min_freq, None::<f32>, default_user_config.min_freq);
update_cli_arg!(&mut args.smoothness, None::<u32>, default_user_config.smoothness);
update_cli_arg!(&mut args.text_color, None::<String>, default_user_config.text_color);
update_cli_arg!(&mut args.volume, None::<u32>, default_user_config.volume);
update_cli_arg!(&mut args.window_width, None::<f32>, default_user_config.window_width);
update_cli_arg!(&mut args.window_height, None::<f32>, default_user_config.window_height);
return;
}

let user_config_yaml: ConfigFFTArgs;
if use_default {
user_config_yaml = ConfigFFTArgs::default();
} else {
user_config_yaml = read_config();
}

if let Some(x) = user_config_yaml.display_track_name {
args.track_name = Some(x);
}
if let Some(x) = user_config_yaml.display_gui {
args.display_gui = Some(x);
}

update_cli_arg!(&mut args.background_color, user_config_yaml.background_color, default_user_config.background_color);
update_cli_arg!(&mut args.bar_color, user_config_yaml.bar_color, default_user_config.bar_color);
update_cli_arg!(&mut args.border_color, user_config_yaml.border_color, default_user_config.border_color);
update_cli_arg!(&mut args.border_size, user_config_yaml.border_size, default_user_config.border_size);
update_cli_arg!(&mut args.font_size, user_config_yaml.font_size, default_user_config.font_size);
update_cli_arg!(&mut args.freq_resolution, user_config_yaml.freq_resolution, default_user_config.freq_resolution);
update_cli_arg!(&mut args.max_freq, user_config_yaml.max_freq, default_user_config.max_freq);
update_cli_arg!(&mut args.min_freq, user_config_yaml.min_freq, default_user_config.min_freq);
update_cli_arg!(&mut args.smoothness, user_config_yaml.smoothness, default_user_config.smoothness);
update_cli_arg!(&mut args.text_color, user_config_yaml.text_color, default_user_config.text_color);
update_cli_arg!(&mut args.volume, user_config_yaml.volume, default_user_config.volume);
update_cli_arg!(&mut args.window_width, user_config_yaml.window_width, default_user_config.window_width);
update_cli_arg!(&mut args.window_height, user_config_yaml.window_height, default_user_config.window_height);
}

0 comments on commit 3bdf3e3

Please sign in to comment.