Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added persistent configuration settings with config.yaml #11

Merged
merged 5 commits into from Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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);
}