180 lines
6.2 KiB
Rust
180 lines
6.2 KiB
Rust
#[derive(Debug, PartialEq, Clone)]
|
|
pub struct Tone(i32);
|
|
|
|
// new parses [a-g][+-]?[1-5]? to tone, sharp or flat, down 2 octave..up 1 octave
|
|
pub fn new<S: ToString>(s: S) -> Tone {
|
|
Tone::new(s.to_string())
|
|
}
|
|
|
|
impl Tone {
|
|
fn new(s: String) -> Tone {
|
|
let re = regex::Regex::new(r"^(((?<letter>^[a-g])(?<sharpness>[+-]?)|(?<major>[A-G]M[0-6])|(?<minor>[A-G]m[0-6]))(?<octave>[1-5]?)|(?<numeric>[0-9]+)|(?<rest>[^a-gA-G0-9]))$").unwrap();
|
|
let captures = re
|
|
.captures(&s)
|
|
.expect(format!("tone '{}' does not match regex", s).as_ref());
|
|
Tone(match captures.name("numeric") {
|
|
Some(number) => number.as_str().parse::<i32>().unwrap(),
|
|
None => match captures.name("rest") {
|
|
Some(_) => 0,
|
|
_ => {
|
|
let mut result = match captures.name("letter") {
|
|
Some(letter) => Tone::char_to_middle_i32(letter.as_str()),
|
|
None => match captures.name("major") {
|
|
Some(major) => Tone::char_to_middle_i32(&major.as_str()[..1]) + match &major.as_str()[2..] {
|
|
"0" => 0,
|
|
"1" => 2,
|
|
"2" => 4,
|
|
"3" => 5,
|
|
"4" => 7,
|
|
"5" => 9,
|
|
_ => 11,
|
|
},
|
|
None => {
|
|
let minor = captures.name("minor").unwrap();
|
|
Tone::char_to_middle_i32(&minor.as_str()[..1]) + match &minor.as_str()[2..] {
|
|
"0" => 0,
|
|
"1" => 2,
|
|
"2" => 3,
|
|
"3" => 5,
|
|
"4" => 7,
|
|
"5" => 8,
|
|
_ => 10,
|
|
}
|
|
},
|
|
},
|
|
} as i32;
|
|
|
|
result += match captures.name("sharpness") {
|
|
Some(sharpness) => match sharpness.as_str() {
|
|
"+" => 1,
|
|
"-" => -1,
|
|
_ => 0,
|
|
},
|
|
None => 0,
|
|
} as i32;
|
|
|
|
result += match captures.name("octave") {
|
|
Some(octave) => match octave.as_str() {
|
|
"" => 0,
|
|
_ => (octave.as_str().parse::<i32>().unwrap() - 3) * 12,
|
|
},
|
|
None => 0,
|
|
} as i32;
|
|
|
|
result
|
|
}
|
|
},
|
|
})
|
|
}
|
|
|
|
fn char_to_middle_i32(c: &str) -> i32 {
|
|
match c.to_lowercase().as_str() {
|
|
"a" => 57,
|
|
"b" => 59,
|
|
"c" => 60,
|
|
"d" => 62,
|
|
"e" => 64,
|
|
"f" => 65,
|
|
_ => 67,
|
|
}
|
|
}
|
|
|
|
pub fn i32(&self) -> i32 {
|
|
self.0
|
|
}
|
|
|
|
pub fn string(&self) -> String {
|
|
let v = self.i32();
|
|
let modifier = if v > 0 && v < 57 { "-" } else if v >= 69 { "+" } else { "" };
|
|
modifier.to_string() + match v {
|
|
45|57|69 => "a",
|
|
46|58|70 => "a+",
|
|
47|59|71 => "b",
|
|
48|60|72 => "c",
|
|
49|61|73 => "c+",
|
|
50|62|74 => "d",
|
|
51|63|75 => "d+",
|
|
52|64|76 => "e",
|
|
53|65|77 => "f",
|
|
54|66|78 => "f+",
|
|
55|67|79 => "g",
|
|
56|68|80 => "g+",
|
|
0 => " ",
|
|
_ => "?",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
#[test]
|
|
fn test_tone_new() {
|
|
eprintln!("numeric");
|
|
assert_eq!(super::new("60").i32(), 60);
|
|
|
|
eprintln!("rests");
|
|
assert_eq!(super::new("-").i32(), 0);
|
|
assert_eq!(super::new(".").i32(), 0);
|
|
|
|
eprintln!("alpha");
|
|
assert_eq!(super::new("c").i32(), 60);
|
|
assert_eq!(super::new("e").i32(), 64);
|
|
assert_eq!(super::new("g").i32(), 67);
|
|
|
|
eprintln!("alpha mod");
|
|
assert_eq!(super::new("c+").i32(), 60 + 1);
|
|
assert_eq!(super::new("c-").i32(), 60 - 1);
|
|
|
|
eprintln!("alpha octave");
|
|
assert_eq!(super::new("c3").i32(), 60);
|
|
assert_eq!(super::new("c4").i32(), 60 + 12);
|
|
|
|
eprintln!("alpha mod octave");
|
|
assert_eq!(super::new("c+4").i32(), 60 + 12 + 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_tone_c_major() {
|
|
assert_eq!(super::new("CM0"), super::new("c"));
|
|
assert_eq!(super::new("CM1"), super::new("d"));
|
|
assert_eq!(super::new("CM2"), super::new("e"));
|
|
assert_eq!(super::new("CM3"), super::new("f"));
|
|
assert_eq!(super::new("CM4"), super::new("g"));
|
|
assert_eq!(super::new("CM5"), super::new("a4"));
|
|
assert_eq!(super::new("CM6"), super::new("b4"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_tone_c_minor() {
|
|
assert_eq!(super::new("Cm0"), super::new("c"));
|
|
assert_eq!(super::new("Cm1"), super::new("d"));
|
|
assert_eq!(super::new("Cm2"), super::new("e-"));
|
|
assert_eq!(super::new("Cm3"), super::new("f"));
|
|
assert_eq!(super::new("Cm4"), super::new("g"));
|
|
assert_eq!(super::new("Cm5"), super::new("a-4"));
|
|
assert_eq!(super::new("Cm6"), super::new("b-4"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_tone_a_major() {
|
|
assert_eq!(super::new("AM0"), super::new("a"));
|
|
assert_eq!(super::new("AM1"), super::new("b"));
|
|
assert_eq!(super::new("AM2"), super::new("c+"));
|
|
assert_eq!(super::new("AM3"), super::new("d"));
|
|
assert_eq!(super::new("AM4"), super::new("e"));
|
|
assert_eq!(super::new("AM5"), super::new("f+"));
|
|
assert_eq!(super::new("AM6"), super::new("g+"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_tone_a_minor() {
|
|
assert_eq!(super::new("Am0"), super::new("a"));
|
|
assert_eq!(super::new("Am1"), super::new("b"));
|
|
assert_eq!(super::new("Am2"), super::new("c"));
|
|
assert_eq!(super::new("Am3"), super::new("d"));
|
|
assert_eq!(super::new("Am4"), super::new("e"));
|
|
assert_eq!(super::new("Am5"), super::new("f"));
|
|
assert_eq!(super::new("Am6"), super::new("g"));
|
|
}
|
|
}
|