Files
composer/src/tone.rs

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"));
}
}