eryon_nrt/triad/
class.rs

1/*
2    Appellation: classes <module>
3    Contrib: @FL03
4*/
5use crate::PitchMod;
6use itertools::Itertools;
7
8// Expanded triad types in Neo-Riemannian theory
9#[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    /// a functional constructor for the major triad type
79    pub fn major() -> Self {
80        Triads::Major
81    }
82    /// a functional constructor for the minor triad type
83    pub fn minor() -> Self {
84        Triads::Minor
85    }
86    /// a functional constructor for the augmented triad type
87    pub fn augmented() -> Self {
88        Triads::Augmented
89    }
90    /// a functional constructor for the diminished triad type
91    pub fn diminished() -> Self {
92        Triads::Diminished
93    }
94    /// get the relative triad type
95    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    /// returns the intervals corresponding to the triad type
104    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    /// returns the interval from the root to the third chord factor; defined by the class
122    pub fn root_to_third(self) -> usize {
123        self.intervals()[0]
124    }
125    /// returns the interval from the third to the fifth chord factor; defined by the class
126    pub fn third_to_fifth(self) -> usize {
127        self.intervals()[1]
128    }
129    /// returns the interval from the root to the fifth chord factor; defined by the class
130    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        // compute the interval between the root and third
136        let rt = third - root;
137        // compute the interval between the third and fifth
138        let tf = fifth - third;
139        // compute the interval between the root and fifth
140        let rf = fifth - root;
141
142        rt == a && tf == b && rf == c
143    }
144    /// validate a chord's composition satisfies the requirements of the current class
145    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}