eryon_nrt/transform/
kinds.rs1use crate::{MusicError, PitchMod, Triad, Triads};
6
7#[derive(
30 Clone,
31 Copy,
32 Debug,
33 Default,
34 Eq,
35 Hash,
36 Ord,
37 PartialEq,
38 PartialOrd,
39 strum::AsRefStr,
40 strum::Display,
41 strum::EnumCount,
42 strum::EnumIs,
43 strum::EnumIter,
44 strum::EnumString,
45 strum::VariantArray,
46 strum::VariantNames,
47)]
48#[cfg_attr(
49 feature = "serde",
50 derive(serde_derive::Deserialize, serde_derive::Serialize),
51 serde(rename_all = "lowercase")
52)]
53#[strum(serialize_all = "lowercase")]
54pub enum LPR {
55 #[default]
57 #[cfg_attr(feature = "serde", serde(alias = "L", alias = "l", alias = "lead"))]
58 Leading,
59 #[cfg_attr(feature = "serde", serde(alias = "P", alias = "p", alias = "par"))]
61 Parallel,
62 #[cfg_attr(feature = "serde", serde(alias = "R", alias = "r", alias = "rel"))]
64 Relative,
65}
66
67impl LPR {
68 pub fn leading() -> Self {
69 LPR::Leading
70 }
71
72 pub fn parallel() -> Self {
73 LPR::Parallel
74 }
75
76 pub fn relative() -> Self {
77 LPR::Relative
78 }
79 pub fn apply(&self, triad: &Triad) -> Triad {
80 self.try_apply(triad).unwrap()
81 }
82 pub fn try_apply(&self, triad: &Triad) -> Result<Triad, MusicError> {
84 let [x, y, z] = triad.notes;
85
86 let notes: [usize; 3];
87 let class: Triads;
88 match triad.class() {
89 Triads::Major => match self {
90 LPR::Leading => {
91 notes = [y, z, (x as isize - 1).pmod() as usize];
92 class = Triads::Minor;
93 }
94 LPR::Parallel => {
95 notes = [x, (y as isize - 1).pmod() as usize, z];
96 class = Triads::Minor;
97 }
98 LPR::Relative => {
99 notes = [(z + 2).pmod(), x, y];
100 class = Triads::Minor;
101 }
102 },
103 Triads::Minor => match self {
104 LPR::Leading => {
105 notes = [(z + 1).pmod(), x, y];
106 class = Triads::Major;
107 }
108 LPR::Parallel => {
109 notes = [x, (y + 1).pmod(), z];
110 class = Triads::Major;
111 }
112 LPR::Relative => {
113 notes = [y, z, (x as isize - 2).pmod() as usize];
114 class = Triads::Major;
115 }
116 },
117 _ => return Err(MusicError::InvalidTriadClass),
118 };
119
120 Ok(Triad {
121 notes,
122 class,
123 octave: triad.octave,
124 })
125 }
126}
127
128impl From<char> for LPR {
129 fn from(value: char) -> Self {
130 match value.to_ascii_lowercase() {
131 'l' => LPR::Leading,
132 'p' => LPR::Parallel,
133 'r' => LPR::Relative,
134 _ => panic!("Invalid LPR transformation; character must be 'L', 'P', or 'R'"),
135 }
136 }
137}
138
139impl From<usize> for LPR {
140 fn from(value: usize) -> Self {
141 use strum::EnumCount;
142 match value % Self::COUNT {
143 0 => LPR::Leading,
144 1 => LPR::Parallel,
145 2 => LPR::Relative,
146 _ => unreachable!(),
147 }
148 }
149}
150
151impl From<LPR> for usize {
152 fn from(value: LPR) -> Self {
153 match value {
154 LPR::Leading => 0,
155 LPR::Parallel => 1,
156 LPR::Relative => 2,
157 }
158 }
159}