use regex::Regex;
pub(crate) fn check_f32(value: &str) -> f32 {
let num: f32 = value
.parse::<f32>()
.unwrap_or_else(|_| panic!("ERROR: `{}` is not a valid f32 number", value));
num
}
pub(crate) fn check_f32_between(value: &str, min: f32, max: f32) -> Option<f32> {
let num = check_f32(value);
match num {
num if (min..=max).contains(&num) => Some(num),
_ => None,
}
}
pub(crate) fn check_u8(value: &str) -> u8 {
let num: u8 = value
.parse::<u8>()
.unwrap_or_else(|_| panic!("ERROR: `{}` is not a valid u8 number", value));
num
}
pub(crate) fn check_u8_between(value: &str, min: u8, max: u8) -> Option<u8> {
let num = check_u8(value);
if num >= min && num <= max {
Some(num)
} else {
None
}
}
pub(crate) fn check_i8(value: &str) -> i8 {
let num: i8 = value
.parse::<i8>()
.unwrap_or_else(|_| panic!("ERROR: `{}` is not a valid i8 number", value));
num
}
pub(crate) fn check_i8_between(value: &str, min: i8, max: i8) -> Option<i8> {
let num = check_i8(value);
if num >= min && num <= max {
Some(num)
} else {
None
}
}
pub(crate) fn check_i16(value: &str) -> i16 {
let num: i16 = value
.parse::<i16>()
.unwrap_or_else(|_| panic!("ERROR: `{}` is not a valid i16 number", value));
num
}
pub(crate) fn check_i16_between(value: &str, min: i16, max: i16) -> Option<i16> {
let num = check_i16(value);
if num >= min && num <= max {
Some(num)
} else {
None
}
}
pub(crate) fn check_u16(value: &str) -> u16 {
let num: u16 = value
.parse::<u16>()
.unwrap_or_else(|_| panic!("ERROR: `{}` is not a valid u16 number", value));
num
}
pub(crate) fn check_u16_between(value: &str, min: u16, max: u16) -> Option<u16> {
let num = check_u16(value);
if num >= min && num <= max {
Some(num)
} else {
None
}
}
pub(crate) fn check_u32(value: &str) -> u32 {
let num: u32 = value
.parse::<u32>()
.unwrap_or_else(|_| panic!("ERROR: `{}` is not a valid u32 number", value));
num
}
pub(crate) fn check_u32_between(value: &str, min: u32, max: u32) -> Option<u32> {
let num = check_u32(value);
if num >= min && num <= max {
Some(num)
} else {
None
}
}
pub(crate) fn check_midi_note(value: &str) -> Option<u8> {
let note_regex = Regex::new(r"([a-gA-G][bB#]?)(-?\d+)").unwrap();
if let Some(captures) = note_regex.captures(value) {
let note_semitones = match captures[1].to_lowercase().as_str() {
"c" => 0,
"c#" => 1,
"db" => 1,
"d" => 2,
"d#" => 3,
"eb" => 3,
"e" => 4,
"f" => 5,
"f#" => 6,
"gb" => 6,
"g" => 7,
"g#" => 8,
"ab" => 8,
"a" => 9,
"a#" => 10,
"bb" => 10,
"b" => 11,
_ => panic!("ERROR: `{}` is not a valid note", &captures[1]),
};
let octave = captures[2]
.parse::<i8>()
.unwrap_or_else(|_| panic!("ERROR: `{}` is not a valid i8 number", &captures[2]));
let midi_note = u8::try_from(12 + note_semitones + octave * 12)
.unwrap_or_else(|_| panic!("ERROR: `{}` is outside of MIDI note range", value));
Some(midi_note)
} else {
check_u8_between(value, 0, 127)
}
}
#[cfg(test)]
mod tests_parse {
use super::check_midi_note;
#[test]
fn test_parse_valid_midi_notes() {
assert_eq!(check_midi_note("0"), Some(0));
assert_eq!(check_midi_note("125"), Some(125));
assert_eq!(check_midi_note("C4"), Some(60));
assert_eq!(check_midi_note("Bb7"), Some(106));
assert_eq!(check_midi_note("g#0"), Some(20));
assert_eq!(check_midi_note("c#-1"), Some(1));
}
#[test]
fn test_parse_invalid_midi_notes() {
assert_eq!(check_midi_note("128"), None);
assert!(std::panic::catch_unwind(|| check_midi_note("-1")).is_err());
assert!(std::panic::catch_unwind(|| check_midi_note("c")).is_err());
assert!(std::panic::catch_unwind(|| check_midi_note("de#2")).is_err());
assert!(std::panic::catch_unwind(|| check_midi_note("d#-9")).is_err());
assert!(std::panic::catch_unwind(|| check_midi_note("q#2")).is_err());
}
}