#[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: S) -> Tone { Tone::new(s.to_string()) } impl Tone { fn new(s: String) -> Tone { let re = regex::Regex::new(r"^(((?^[a-g])(?[+-]?)|(?[A-G]M[0-6])|(?[A-G]m[0-6]))(?[1-5]?)((?T)|(?t)|(?f))?|(?[0-9]+)|(?[^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::().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::().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")); } }