1use crate::traits::TriadType;
6use num_traits::{FromPrimitive, ToPrimitive};
7use rstmt::PitchMod;
8
9#[derive(
12 Clone,
13 Copy,
14 Debug,
15 Default,
16 Eq,
17 Hash,
18 Ord,
19 PartialOrd,
20 strum::AsRefStr,
21 strum::Display,
22 strum::EnumIs,
23 strum::EnumIter,
24 strum::EnumString,
25 strum::VariantArray,
26 strum::VariantNames,
27)]
28#[cfg_attr(
29 feature = "serde",
30 derive(serde::Deserialize, serde::Serialize),
31 serde(untagged, rename_all = "lowercase")
32)]
33#[strum(serialize_all = "lowercase")]
34pub enum Triads {
35 #[default]
36 Major = 0,
37 Minor = 1,
38 Augmented = 2,
39 Diminished = 3,
40}
41
42impl Triads {
43 pub fn from_class<C>(class: C) -> Self
44 where
45 C: TriadType,
46 {
47 if class.is_major() {
48 Self::Major
49 } else if class.is_minor() {
50 Self::Minor
51 } else if class.is_augmented() {
52 Self::Augmented
53 } else if class.is_diminished() {
54 Self::Diminished
55 } else {
56 panic!("invalid triad class")
57 }
58 }
59 pub fn is<T: TriadType>(&self, class: T) -> bool {
60 match self {
61 Triads::Major => class.is_major(),
62 Triads::Minor => class.is_minor(),
63 Triads::Augmented => class.is_augmented(),
64 Triads::Diminished => class.is_diminished(),
65 }
66 }
67 pub const fn major() -> Self {
69 Self::Major
70 }
71 pub const fn minor() -> Self {
73 Self::Minor
74 }
75 pub const fn augmented() -> Self {
77 Self::Augmented
78 }
79 pub const fn diminished() -> Self {
81 Self::Diminished
82 }
83 pub fn try_from_notes(a: isize, b: isize, c: isize) -> crate::Result<Self> {
85 Self::try_from_arr([a, b, c])
86 }
87 #[cfg(feature = "alloc")]
88 pub fn try_from_arr(notes: [isize; 3]) -> crate::Result<Self> {
90 use itertools::Itertools;
91 let intervals = notes
92 .iter()
93 .combinations(2)
94 .map(|v| (v[1] - v[0]).pmod())
95 .collect::<Vec<_>>();
96 match intervals[..] {
97 [4, 7, 3] => Ok(Self::Major),
98 [3, 7, 4] => Ok(Self::Minor),
99 [4, 8, 4] => Ok(Self::Augmented),
100 [3, 6, 3] => Ok(Self::Diminished),
101 _ => Err(crate::TriadError::InvalidTriad),
102 }
103 }
104 pub const fn relative(&self) -> Self {
108 match self {
109 Triads::Major => Triads::Minor,
110 Triads::Minor => Triads::Major,
111 Triads::Augmented => Triads::Diminished,
112 Triads::Diminished => Triads::Augmented,
113 }
114 }
115 pub const fn intervals(&self) -> [usize; 3] {
118 match self {
119 Triads::Major => [4, 7, 3],
120 Triads::Minor => [3, 7, 4],
121 Triads::Augmented => [4, 8, 4],
122 Triads::Diminished => [3, 6, 3],
123 }
124 }
125 pub const fn thirds(&self) -> (usize, usize) {
127 (self.root(), self.third())
128 }
129 pub const fn root(&self) -> usize {
131 match self {
132 Triads::Major => 4,
133 Triads::Minor => 3,
134 Triads::Augmented => 4,
135 Triads::Diminished => 3,
136 }
137 }
138 pub const fn root_ref(&self) -> &usize {
140 match self {
141 Triads::Major => &4,
142 Triads::Minor => &3,
143 Triads::Augmented => &4,
144 Triads::Diminished => &3,
145 }
146 }
147 pub const fn third(&self) -> usize {
149 match self {
150 Triads::Major => 3,
151 Triads::Minor => 4,
152 Triads::Augmented => 4,
153 Triads::Diminished => 3,
154 }
155 }
156 pub const fn third_ref(&self) -> &usize {
158 match self {
159 Triads::Major => &3,
160 Triads::Minor => &4,
161 Triads::Augmented => &4,
162 Triads::Diminished => &3,
163 }
164 }
165 pub const fn fifth(&self) -> usize {
167 match self {
168 Triads::Major => 7,
169 Triads::Minor => 7,
170 Triads::Augmented => 8,
171 Triads::Diminished => 6,
172 }
173 }
174 pub const fn fifth_ref(&self) -> &usize {
176 match self {
177 Triads::Major => &7,
178 Triads::Minor => &7,
179 Triads::Augmented => &8,
180 Triads::Diminished => &6,
181 }
182 }
183 pub fn is_valid(&self, root: usize, third: usize, fifth: usize) -> bool {
185 let [rt, rf, tf] = self.intervals();
186 (third - root).pmod() == rt && (fifth - root).pmod() == rf && (fifth - third).pmod() == tf
187 }
188 pub fn validate<T>(&self, &[r, t, f]: &[T; 3]) -> bool
190 where
191 T: Copy
192 + PartialEq
193 + FromPrimitive
194 + ToPrimitive
195 + PitchMod<Output = T>
196 + core::ops::Sub<Output = T>,
197 {
198 let a = T::from_usize(self.root()).unwrap();
199 let b = T::from_usize(self.fifth()).unwrap();
200 let c = T::from_usize(self.third()).unwrap();
201 (t - r).pmod() == a && (f - t).pmod() == c && (f - r).pmod() == b
202 }
203}
204
205macro_rules! impl_from_triad_class {
206 ($($T:ty),* $(,)?) => {
207 $(
208 impl From<$T> for Triads {
209 fn from(value: $T) -> Self {
210 match value % 4 {
211 0 => Triads::Major,
212 1 => Triads::Minor,
213 2 => Triads::Augmented,
214 3 => Triads::Diminished,
215 _ => unreachable! { "invalid modulo operation" },
216 }
217 }
218 }
219
220 impl From<Triads> for $T {
221 fn from(value: Triads) -> Self {
222 value as $T
223 }
224 }
225 )*
226 };
227}
228
229impl_from_triad_class! { u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize }
230
231impl core::ops::Index<super::Factors> for Triads {
232 type Output = usize;
233
234 fn index(&self, index: super::Factors) -> &Self::Output {
235 match index {
236 super::Factors::Root => self.root_ref(),
237 super::Factors::Third => self.third_ref(),
238 super::Factors::Fifth => self.fifth_ref(),
239 }
240 }
241}
242
243macro_rules! interval_to_class {
244 (@impl $T:ident) => {
245 impl From<rstmt_core::$T> for Triads {
252 fn from(_value: rstmt_core::$T) -> Self {
253 Triads::$T
254 }
255 }
256
257 impl TryFrom<Triads> for rstmt_core::$T {
258 type Error = $crate::TriadError;
259
260 fn try_from(value: Triads) -> Result<Self, Self::Error> {
261 if matches!(value, Triads::$T) {
262 Ok(Self)
263 } else {
264 Err($crate::TriadError::IncompatibleTriadClasses)
265 }
266 }
267 }
268 };
269 ($($T:ident),* $(,)?) => {
270 $(interval_to_class! { @impl $T })*
271 };
272}
273
274interval_to_class! { Major, Minor, Augmented, Diminished }
275
276impl<C: crate::TriadType> PartialEq<C> for Triads {
277 fn eq(&self, other: &C) -> bool {
278 match self {
279 Triads::Major => other.is_major(),
280 Triads::Minor => other.is_minor(),
281 Triads::Augmented => other.is_augmented(),
282 Triads::Diminished => other.is_diminished(),
283 }
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290
291 #[test]
292 fn test_class_creation() -> crate::Result<()> {
293 let class = Triads::try_from_arr([0, 4, 7])?;
294 assert!(class.is_major());
295 let class = Triads::try_from_arr([0, 3, 7])?;
296 assert!(class.is_minor());
297 let class = Triads::try_from_arr([0, 4, 8])?;
298 assert!(class.is_augmented());
299 let class = Triads::try_from_arr([0, 3, 6])?;
300 assert!(class.is_diminished());
301
302 assert!(Triads::try_from_arr([0, 7, 4]).is_err());
303 assert!(Triads::try_from_arr([0, 5, 9]).is_err());
304
305 Ok(())
306 }
307}