1use crate::PitchMod;
6use itertools::Itertools;
7
8#[derive(
10 Clone,
11 Copy,
12 Debug,
13 Default,
14 Eq,
15 Hash,
16 Ord,
17 PartialEq,
18 PartialOrd,
19 strum::AsRefStr,
20 strum::Display,
21 strum::EnumIs,
22 strum::EnumIter,
23 strum::EnumString,
24 strum::VariantArray,
25 strum::VariantNames,
26)]
27#[cfg_attr(
28 feature = "serde",
29 derive(serde_derive::Deserialize, serde_derive::Serialize),
30 serde(rename_all = "lowercase")
31)]
32#[strum(serialize_all = "lowercase")]
33pub enum Triads {
34 #[default]
35 Major,
36 Minor,
37 Augmented,
38 Diminished,
39}
40
41impl Triads {
42 pub fn try_from_notes(a: usize, b: usize, c: usize) -> crate::Result<Self> {
43 let (a, b, c) = (a as isize, b as isize, c as isize);
44 let rt = (b - a).pmod();
45 let tf = (c - b).pmod();
46 let rf = (c - a).pmod();
47 if matches!(rt, 3 | 4) && matches!(tf, 3 | 4) && matches!(rf, 6 | 7 | 8) && rt + tf == rf {
48 let class = match [rt, tf, rf] {
49 [4, 3, 7] => Triads::Major,
50 [3, 4, 7] => Triads::Minor,
51 [4, 4, 8] => Triads::Augmented,
52 [3, 3, 6] => Triads::Diminished,
53 _ => unreachable!(),
54 };
55 return Ok(class);
56 }
57 Err(crate::MusicError::InvalidIntervals(format!(
58 "{}-{}-{}",
59 rt, tf, rf
60 )))
61 }
62 pub fn try_from_arr(arr: [usize; 3]) -> crate::Result<Self> {
63 let mut res = Err(crate::MusicError::InvalidIntervals(
64 "the given chord does not match any triad class".to_string(),
65 ));
66 for (&a, &b, &c) in arr
67 .iter()
68 .circular_tuple_windows()
69 .chain(arr.iter().rev().circular_tuple_windows())
70 {
71 if let Ok(class) = Triads::try_from_notes(a, b, c) {
72 res = Ok(class);
73 break;
74 }
75 }
76 res
77 }
78 pub fn major() -> Self {
80 Triads::Major
81 }
82 pub fn minor() -> Self {
84 Triads::Minor
85 }
86 pub fn augmented() -> Self {
88 Triads::Augmented
89 }
90 pub fn diminished() -> Self {
92 Triads::Diminished
93 }
94 pub fn relative(self) -> Self {
96 match self {
97 Triads::Major => Triads::Minor,
98 Triads::Minor => Triads::Major,
99 Triads::Augmented => Triads::Diminished,
100 Triads::Diminished => Triads::Augmented,
101 }
102 }
103 pub fn intervals(self) -> [usize; 3] {
105 match self {
106 Triads::Major => [4, 3, 7],
107 Triads::Minor => [3, 4, 7],
108 Triads::Augmented => [4, 4, 8],
109 Triads::Diminished => [3, 3, 6],
110 }
111 }
112 pub fn thirds(&self) -> (usize, usize) {
113 use Triads::*;
114 match self {
115 Augmented => (4, 4),
116 Diminished => (3, 3),
117 Major => (4, 3),
118 Minor => (3, 4),
119 }
120 }
121 pub fn root_to_third(self) -> usize {
123 self.intervals()[0]
124 }
125 pub fn third_to_fifth(self) -> usize {
127 self.intervals()[1]
128 }
129 pub fn root_to_fifth(self) -> usize {
131 self.intervals()[2]
132 }
133 pub fn is_valid(&self, root: usize, third: usize, fifth: usize) -> bool {
134 let [a, b, c] = self.intervals();
135 let rt = third - root;
137 let tf = fifth - third;
139 let rf = fifth - root;
141
142 rt == a && tf == b && rf == c
143 }
144 pub fn validate(&self, notes: &[usize; 3]) -> bool {
146 let [a, b, c] = self.intervals();
147 let r = notes[0] as isize;
148 let t = notes[1] as isize;
149 let f = notes[2] as isize;
150 (t - r).pmod() as usize == a && (f - t).pmod() as usize == b && (f - r).pmod() as usize == c
151 }
152}
153
154impl From<usize> for Triads {
155 fn from(value: usize) -> Self {
156 match value {
157 0 => Triads::Major,
158 1 => Triads::Minor,
159 2 => Triads::Augmented,
160 3 => Triads::Diminished,
161 _ => Triads::Major,
162 }
163 }
164}