eryon_nrt/
triad.rs

1/*
2    Appellation: traid <module>
3    Contrib: @FL03
4*/
5#[doc(inline)]
6pub use self::{class::*, factors::*};
7
8pub(crate) mod class;
9pub(crate) mod factors;
10
11use crate::error::MusicError;
12use crate::traits::{IntoNote, IntoOctave, PitchMod};
13use crate::transform::{LPR, TriadNavigator};
14use crate::types::{Note, Octave};
15
16use num_traits::{Float, FromPrimitive};
17
18/// A triad is a particular chord composed of three notes that satify particular intervallic
19/// constrains with each other. Here, the triad materializes the facet of a hyperedge within a
20/// cluster of triads persisted in the Tonnetz. The triad is a fundamental entity in the
21/// substrate used to represent the _headspace_ of a plant. Each plant relies on these objects
22/// to transverse the surface of the tonnetz so that it may gaurantee the completion of a task.
23#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
24#[cfg_attr(
25    feature = "serde",
26    derive(serde_derive::Deserialize, serde_derive::Serialize)
27)]
28pub struct Triad {
29    /// The type of triad (Major, Minor, Augmented, Diminished)
30    pub(crate) class: Triads,
31    /// the set of three pitch classes defining the triad
32    pub(crate) notes: [usize; 3],
33    /// The octave of the triad
34    pub(crate) octave: Octave,
35}
36
37impl Triad {
38    pub fn new(notes: [usize; 3], class: Triads) -> Self {
39        if !class.validate(&notes) {
40            panic!("Invalid triad pitches for class {notes:?}");
41        }
42        Self {
43            class,
44            notes,
45            octave: Octave(4),
46        }
47    }
48    /// Create a new triad from a root pitch and class
49    pub fn from_root<N>(root: N, class: Triads) -> Self
50    where
51        N: IntoNote,
52    {
53        // all IntoNote implementations should* already compute pmod
54        let note = root.into_note();
55        let root = note.class();
56        let [a, .., c] = class.intervals();
57        let third = (root + a).pmod();
58        let fifth = (root + c).pmod();
59        Self {
60            class,
61            notes: [root, third, fifth],
62            octave: note.octave(),
63        }
64    }
65    /// creates a new augmented triad from the given root
66    pub fn augmented<N>(root: N) -> Self
67    where
68        N: IntoNote,
69    {
70        Self::from_root(root, Triads::Augmented)
71    }
72    /// creates a new diminished triad from the given root
73    pub fn diminished<N>(root: N) -> Self
74    where
75        N: IntoNote,
76    {
77        Self::from_root(root, Triads::Diminished)
78    }
79    /// Create a new major triad from the given root
80    pub fn major<N>(root: N) -> Self
81    where
82        N: IntoNote,
83    {
84        Self::from_root(root, Triads::Major)
85    }
86    /// creates a new minor triad from the given root
87    pub fn minor<N>(root: N) -> Self
88    where
89        N: IntoNote,
90    {
91        Self::from_root(root, Triads::Minor)
92    }
93    /// returns a copy of the class of the triad
94    pub const fn class(&self) -> Triads {
95        self.class
96    }
97    /// returns the number of common tones between two triads
98    pub fn common_tones(&self, other: &Self) -> Vec<usize> {
99        self.notes()
100            .iter()
101            .filter(|&&n| other.contains(&n))
102            .copied()
103            .collect::<Vec<_>>()
104    }
105    /// returns copy of the notes currently composing the triad
106    pub const fn notes(&self) -> [usize; 3] {
107        self.notes
108    }
109    /// returns a mutable reference to the notes of the triad
110    pub fn notes_mut(&mut self) -> &mut [usize; 3] {
111        &mut self.notes
112    }
113    /// returns a copy of the octave of the triad
114    pub const fn octave(&self) -> Octave {
115        self.octave
116    }
117    /// returns a mutable reference to the octave of the triad
118    pub fn octave_mut(&mut self) -> &mut Octave {
119        &mut self.octave
120    }
121    /// returns a copy of the root pitch of the triad
122    pub fn root(&self) -> Note {
123        Note {
124            class: self[Factors::Root],
125            octave: self.octave(),
126        }
127    }
128    /// returns a copy of the third chord factor within the triad
129    pub fn third(&self) -> Note {
130        Note {
131            class: self[Factors::Third],
132            octave: self.octave(),
133        }
134    }
135    /// returns a copy of the fifth chord factor within the triad
136    pub fn fifth(&self) -> Note {
137        Note {
138            class: self[Factors::Fifth],
139            octave: self.octave(),
140        }
141    }
142    /// check if the triad contains a given pitch class
143    pub fn contains<Q>(&self, pitch: &Q) -> bool
144    where
145        Q: core::borrow::Borrow<usize>,
146    {
147        self.notes().contains(pitch.borrow())
148    }
149    /// returns true if the current instance is an augmented triad
150    pub fn is_augmented(&self) -> bool {
151        self.class().is_augmented()
152    }
153    /// returns true if the current instance is a diminished triad
154    pub fn is_diminished(&self) -> bool {
155        self.class().is_diminished()
156    }
157    /// returns true if the current instance is a major triad
158    pub fn is_major(&self) -> bool {
159        self.class().is_major()
160    }
161    /// returns true if the current instance is a minor triad
162    pub fn is_minor(&self) -> bool {
163        self.class().is_minor()
164    }
165    /// returns true if the pitches within the triad match its classification
166    pub fn is_valid(&self) -> bool {
167        self.class().validate(&self.notes())
168    }
169    /// apply the leading transformation to the triad
170    pub fn leading(&self) -> Self {
171        self.transform(LPR::Leading)
172    }
173    /// apply the parallel transformation to the triad
174    pub fn parallel(&self) -> Self {
175        self.transform(LPR::Parallel)
176    }
177    /// apply the relative transformation to the triad
178    pub fn relative(&self) -> Self {
179        self.transform(LPR::Relative)
180    }
181    /// computes the centroid of the triad
182    pub fn centroid<T>(&self) -> Option<[T; 2]>
183    where
184        T: Float + FromPrimitive,
185    {
186        let y = T::from_isize(*self.octave)?;
187        let x = T::from_usize(self.notes().iter().sum())? / T::from_usize(self.notes().len())?;
188        Some([x, y])
189    }
190    // return the barycentric coordinates of the given note w.r.t the current triad
191    pub fn barycentric<T>(&self, p: impl IntoNote) -> [T; 3]
192    where
193        T: Float + FromPrimitive,
194    {
195        let note = p.into_note();
196        let px = T::from_usize(note.class().pmod()).unwrap();
197        let py = T::from_isize(*note.octave()).unwrap();
198        let y = T::from_isize(*self.octave).unwrap();
199        let [v0, v1, v2] = self.notes.map(|n| T::from_usize(n).unwrap());
200
201        let d00 = v0 * v0 + y * y;
202        let d01 = v0 * v1 + y * y;
203        let d11 = v1 * v1 + y * y;
204        let d20 = v2 * px + y * py;
205        let d21 = v2 * v1 + y * y;
206
207        let denom = d00 * d11 - d01 * d01;
208        let a = (d11 * d20 - d01 * d21) / denom;
209        let b = (d00 * d21 - d01 * d20) / denom;
210        let c = T::one() - a - b;
211        [a, b, c]
212    }
213    /// creates an instance of the transformer for the current triad
214    pub fn path_finder(&self) -> TriadNavigator<'_> {
215        TriadNavigator::new(self)
216    }
217    /// apply a single [LPR] transformation to a triad
218    pub fn transform(&self, transform: LPR) -> Self {
219        transform.apply(self)
220    }
221    /// apply a single transformation to a triad in-place, mutating the current instance
222    pub fn transform_inplace(&mut self, transform: LPR) {
223        *self = self.transform(transform);
224    }
225    /// apply a series of transformations to a triad
226    pub fn walk<I>(&self, path: I) -> Self
227    where
228        I: IntoIterator<Item = LPR>,
229    {
230        path.into_iter()
231            .fold(*self, |triad, transform| transform.apply(&triad))
232    }
233    /// apply a chain of transformations to a triad in-place
234    pub fn walk_inplace<I>(&mut self, path: I)
235    where
236        I: IntoIterator<Item = LPR>,
237    {
238        *self = self.walk(path);
239    }
240    /// try to apply a single transformation to a triad
241    pub fn try_transform(&self, transform: LPR) -> Result<Self, MusicError> {
242        transform.try_apply(self)
243    }
244    /// set the octave of the triad
245    pub fn set_octave<O>(&mut self, octave: O)
246    where
247        O: IntoOctave,
248    {
249        self.octave = octave.into_octave();
250    }
251    /// consumes the current instance to create another with the given octave
252    pub fn with_octave<O>(self, octave: O) -> Self
253    where
254        O: IntoOctave,
255    {
256        Self {
257            octave: octave.into_octave(),
258            ..self
259        }
260    }
261}
262
263impl Default for Triad {
264    fn default() -> Self {
265        Triad::major(Note::from_pitch(0))
266    }
267}
268
269impl core::fmt::Display for Triad {
270    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
271        write!(f, "{}({:?})", self.class, self.notes)
272    }
273}
274
275impl core::convert::AsRef<[usize; 3]> for Triad {
276    fn as_ref(&self) -> &[usize; 3] {
277        &self.notes
278    }
279}
280
281impl core::convert::AsMut<[usize; 3]> for Triad {
282    fn as_mut(&mut self) -> &mut [usize; 3] {
283        &mut self.notes
284    }
285}
286
287impl core::ops::Index<usize> for Triad {
288    type Output = usize;
289    fn index(&self, index: usize) -> &Self::Output {
290        &self.notes[index % 3]
291    }
292}
293
294impl core::ops::IndexMut<usize> for Triad {
295    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
296        &mut self.notes[index % 3]
297    }
298}
299
300impl core::ops::Index<Factors> for Triad {
301    type Output = usize;
302    fn index(&self, index: Factors) -> &Self::Output {
303        &self.notes[index as usize]
304    }
305}
306
307impl core::ops::IndexMut<Factors> for Triad {
308    fn index_mut(&mut self, index: Factors) -> &mut Self::Output {
309        &mut self.notes[index as usize]
310    }
311}
312
313impl core::ops::Mul<LPR> for Triad {
314    type Output = Self;
315
316    fn mul(self, rhs: LPR) -> Self::Output {
317        rhs.apply(&self)
318    }
319}
320
321impl core::ops::MulAssign<LPR> for Triad {
322    fn mul_assign(&mut self, rhs: LPR) {
323        *self = rhs.apply(self);
324    }
325}
326
327impl core::iter::IntoIterator for Triad {
328    type Item = usize;
329
330    type IntoIter = core::array::IntoIter<Self::Item, 3>;
331
332    fn into_iter(self) -> Self::IntoIter {
333        self.notes.into_iter()
334    }
335}
336
337impl<'a> core::iter::IntoIterator for &'a Triad {
338    type Item = &'a usize;
339
340    type IntoIter = core::slice::Iter<'a, usize>;
341
342    fn into_iter(self) -> Self::IntoIter {
343        self.notes.iter()
344    }
345}
346
347impl<'a> core::iter::IntoIterator for &'a mut Triad {
348    type Item = &'a mut usize;
349
350    type IntoIter = core::slice::IterMut<'a, usize>;
351
352    fn into_iter(self) -> Self::IntoIter {
353        self.notes.iter_mut()
354    }
355}