use std::str::FromStr;
use crate::chart_classes::TimingChangeType;
#[inline(always)]
pub fn to_millis(number: f32) -> f32 {
number * 1000f32
}
#[inline(always)]
pub fn to_seconds(number: f32) -> f32 {
number / 1000f32
}
pub fn remove_comments(string: &str) -> String {
let mut result = String::with_capacity(string.len());
for line in string.lines() {
let (content, _) = line.split_once("//").unwrap_or((line, ""));
if content.chars().any(|c| !c.is_whitespace()) {
result.push_str(content);
result.push('\n');
}
}
result.pop();
result
}
#[inline]
pub fn parse_key_value(raw_str: &str) -> (&str, &str) {
let colon_pos = raw_str.find(':').unwrap();
(raw_str[..colon_pos].trim(), raw_str[colon_pos + 1..].trim())
}
#[inline(always)]
pub fn trim_split_iter<'a, I>(split_iter: I) -> Vec<&'a str>
where
I: Iterator<Item = &'a str>,
{
split_iter.map(|s| s.trim()).collect()
}
pub fn merge_bpm_and_stops(
bpms_beats: Vec<f32>,
bpms_values: Vec<f32>,
stops_beats: Vec<f32>,
stops_values: Vec<f32>,
) -> (Vec<f32>, Vec<f32>, Vec<TimingChangeType>) {
let mut beats = Vec::new();
let mut values = Vec::new();
let mut types = Vec::new();
match (bpms_beats.is_empty(), stops_beats.is_empty()) {
(true, true) => (), (true, false) => {
for i in 0..stops_beats.len() {
beats.push(stops_beats[i]);
values.push(stops_values[i]);
types.push(TimingChangeType::Stop);
}
},
(false, true) => {
for i in 0..bpms_beats.len() {
beats.push(bpms_beats[i]);
values.push(bpms_values[i]);
types.push(TimingChangeType::Bpm);
}
},
(false, false) => {
let mut merged: Vec<((f32, f32), TimingChangeType)> =
Vec::with_capacity(bpms_beats.len() + stops_beats.len());
for i in 0..bpms_beats.len() {
merged.push(((bpms_beats[i], bpms_values[i]), TimingChangeType::Bpm));
}
for i in 0..stops_beats.len() {
merged.push(((stops_beats[i], stops_values[i]), TimingChangeType::Stop));
}
merged.sort_unstable_by(|a, b| {
a.0.0
.partial_cmp(&b.0.0)
.expect("Comparison between NaN values when merging bpm and stops")
});
for ((beat, value), change_type) in merged {
beats.push(beat);
values.push(value);
types.push(change_type);
}
}
}
(beats, values, types)
}
pub trait StrDefaultExtension {
fn or_default_empty(&self, default: &str) -> String;
}
impl StrDefaultExtension for str {
fn or_default_empty(&self, default: &str) -> String {
let trimmed = self.trim();
if trimmed.is_empty() {
default.to_string()
} else {
trimmed.to_string()
}
}
}
pub trait StrNumericDefaultExtension {
fn or_default_empty_as<D: Default + ToString + FromStr>(&self, default: D) -> D;
}
impl StrNumericDefaultExtension for str {
fn or_default_empty_as<D: Default + ToString + FromStr>(&self, default: D) -> D {
let s = self.trim();
if s.is_empty() {
return default;
}
s.parse().unwrap_or_else(|_| {
default.to_string().parse().unwrap_or_else(|_| {
panic!(
"Failed to parse '{}' or default '{}' as requested numeric type",
s,
default.to_string()
)
})
})
}
}
#[inline(always)]
pub fn approx_eq(a: f32, b: f32, margin: f32) -> bool {
(a - b).abs() <= margin
}
#[inline(always)]
pub fn thresholded_ceil(value: f32, threshold: f32) -> f32 {
let fractional = value.fract();
if fractional >= threshold {
value.floor() + 1.0
} else {
value
}
}
#[inline]
pub fn add_key_value_template(template: &mut String, key: &str, sep: &str, value: &str, end: &str) {
template.reserve(key.len() + value.len() + 3);
template.push_str(key);
template.push_str(sep);
template.push_str(value);
template.push_str(end);
}