242 lines
8.4 KiB
Rust
242 lines
8.4 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]?)((?<major_third>T)|(?<minor_third>t)|(?<fifth>f))?|(?<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 += match captures.name("major_third") {
|
|
Some(_) => 4, // TODO not all are good
|
|
None => 0,
|
|
} as i32;
|
|
|
|
result += match captures.name("minor_third") {
|
|
Some(_) => 3, // TODO not all are good
|
|
None => 0,
|
|
} as i32;
|
|
|
|
result += match captures.name("fifth") {
|
|
Some(_) => 7,
|
|
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_new_major_third() {
|
|
assert_eq!(super::new("cT").i32(), 60 + 4);
|
|
assert_eq!(super::new("c1T").i32(), 60 + 4 - 24);
|
|
|
|
assert_eq!(super::new("c+T").i32(), 60 + 1 + 4);
|
|
assert_eq!(super::new("c+1T").i32(), 60 + 1 + 4 - 24);
|
|
|
|
assert_eq!(super::new("CM0T").i32(), 60 + 4);
|
|
assert_eq!(super::new("CM01T").i32(), 60 + 4 - 24);
|
|
}
|
|
|
|
#[test]
|
|
fn test_tone_new_minor_third() {
|
|
assert_eq!(super::new("ct").i32(), 60 + 3);
|
|
assert_eq!(super::new("c1t").i32(), 60 + 3 - 24);
|
|
|
|
assert_eq!(super::new("c+t").i32(), 60 + 1 + 3);
|
|
assert_eq!(super::new("c+1t").i32(), 60 + 1 + 3 - 24);
|
|
|
|
assert_eq!(super::new("CM0t").i32(), 60 + 3);
|
|
assert_eq!(super::new("CM01t").i32(), 60 + 3 - 24);
|
|
}
|
|
|
|
#[test]
|
|
fn test_tone_new_fifth() {
|
|
assert_eq!(super::new("cf").i32(), 60 + 7);
|
|
assert_eq!(super::new("c1f").i32(), 60 + 7 - 24);
|
|
|
|
assert_eq!(super::new("c+f").i32(), 60 + 1 + 7);
|
|
assert_eq!(super::new("c+1f").i32(), 60 + 1 + 7 - 24);
|
|
|
|
assert_eq!(super::new("CM0f").i32(), 60 + 7);
|
|
assert_eq!(super::new("CM01f").i32(), 60 + 7 - 24);
|
|
}
|
|
|
|
#[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"));
|
|
}
|
|
}
|