1use keysignature::KeySignatureTrait;
2pub use keysignature::{KeySignature, pitch_name_to_sharps, pitch_to_sharps, sharps_to_pitch};
3
4use crate::{
5 base::Music21ObjectTrait,
6 chord::Chord,
7 defaults::IntegerType,
8 error::{Error, Result},
9 pitch::Pitch,
10 prebase::ProtoM21ObjectTrait,
11 scale::diatonicscale::DiatonicScale,
12};
13use std::str::FromStr;
14
15pub mod keysignature;
17
18#[derive(Clone, Debug)]
19pub struct Key {
21 tonic_pitch: Pitch,
22 mode: String,
23 sharps: IntegerType,
24}
25
26impl Key {
27 pub(crate) fn new(tonic_pitch: Pitch, mode: &str, sharps: IntegerType) -> Self {
28 Self {
29 tonic_pitch,
30 mode: mode.to_string(),
31 sharps,
32 }
33 }
34
35 pub fn from_tonic_mode<'a, M>(tonic: &str, mode: M) -> Result<Self>
40 where
41 M: Into<Option<&'a str>>,
42 {
43 let tonic_pitch = Pitch::new(
44 Some(tonic.to_string()),
45 None,
46 None,
47 Option::<IntegerType>::None,
48 Option::<IntegerType>::None,
49 None,
50 None,
51 None,
52 None,
53 )?;
54
55 let mode = mode.into();
56 let resolved_mode = match mode {
57 Some(mode) => mode.to_lowercase(),
58 None => {
59 if tonic.chars().all(|ch| !ch.is_ascii_uppercase()) {
60 "minor".to_string()
61 } else {
62 "major".to_string()
63 }
64 }
65 };
66
67 let sharps = pitch_to_sharps(&tonic_pitch, Some(&resolved_mode))?;
68 Ok(Self::new(tonic_pitch, &resolved_mode, sharps))
69 }
70
71 pub fn from_tonic(tonic: &str) -> Result<Self> {
73 Self::from_tonic_mode(tonic, None::<&str>)
74 }
75
76 pub fn tonic(&self) -> Pitch {
78 self.tonic_pitch.clone()
79 }
80
81 pub fn tonic_pitch(&self) -> &Pitch {
83 &self.tonic_pitch
84 }
85
86 pub fn mode(&self) -> &str {
88 &self.mode
89 }
90
91 pub fn sharps(&self) -> IntegerType {
93 self.sharps
94 }
95
96 pub fn key_signature(&self) -> KeySignature {
98 KeySignature::new(self.sharps)
99 }
100
101 pub fn scale(&self) -> DiatonicScale {
103 DiatonicScale::new(self.tonic_pitch.clone(), self.sharps, &self.mode)
104 }
105
106 pub fn pitch_from_degree(&self, degree: usize) -> Result<Pitch> {
108 self.scale().pitch_from_degree(degree)
109 }
110
111 pub fn pitches(&self) -> Result<Vec<Pitch>> {
113 self.scale().pitches()
114 }
115
116 pub fn triad_from_degree(&self, degree: usize) -> Result<Chord> {
118 self.scale().triad_from_degree(degree)
119 }
120
121 pub fn seventh_chord_from_degree(&self, degree: usize) -> Result<Chord> {
123 self.scale().seventh_chord_from_degree(degree)
124 }
125
126 pub fn harmonized_triads(&self) -> Result<Vec<Chord>> {
128 (1..=7)
129 .map(|degree| self.triad_from_degree(degree))
130 .collect()
131 }
132
133 pub fn harmonized_sevenths(&self) -> Result<Vec<Chord>> {
135 (1..=7)
136 .map(|degree| self.seventh_chord_from_degree(degree))
137 .collect()
138 }
139
140 pub fn relative(&self) -> Result<Self> {
142 match self.mode.as_str() {
143 "major" => self.key_signature().try_as_key(Some("minor"), None),
144 "minor" => self.key_signature().try_as_key(Some("major"), None),
145 _ => Ok(self.clone()),
146 }
147 }
148
149 pub fn parallel(&self) -> Result<Self> {
151 match self.mode.as_str() {
152 "major" => Self::from_tonic_mode(&self.tonic_pitch.name(), "minor"),
153 "minor" => Self::from_tonic_mode(&self.tonic_pitch.name(), "major"),
154 _ => Ok(self.clone()),
155 }
156 }
157}
158
159impl KeySignatureTrait for Key {}
160impl Music21ObjectTrait for Key {}
161impl ProtoM21ObjectTrait for Key {}
162
163impl FromStr for Key {
164 type Err = Error;
165
166 fn from_str(value: &str) -> Result<Self> {
167 parse_key(value)
168 }
169}
170
171impl TryFrom<&str> for Key {
172 type Error = Error;
173
174 fn try_from(value: &str) -> Result<Self> {
175 value.parse()
176 }
177}
178
179impl TryFrom<String> for Key {
180 type Error = Error;
181
182 fn try_from(value: String) -> Result<Self> {
183 value.parse()
184 }
185}
186
187fn parse_key(value: &str) -> Result<Key> {
188 let trimmed = value.trim();
189 if trimmed.is_empty() {
190 return Err(Error::Analysis("key cannot be empty".to_string()));
191 }
192
193 let parts = trimmed.split_whitespace().collect::<Vec<_>>();
194 match parts.as_slice() {
195 [tonic] => {
196 if let Some((tonic, mode)) = split_compact_key_token(tonic) {
197 Key::from_tonic_mode(tonic, Some(mode.as_str()))
198 } else {
199 Key::from_tonic(tonic)
200 }
201 }
202 [tonic, mode] => {
203 let mode = canonical_key_mode(mode);
204 Key::from_tonic_mode(tonic, Some(mode.as_str()))
205 }
206 _ => Err(Error::Analysis(format!(
207 "invalid key {value:?}; use a tonic and optional mode, such as \"C\", \"C major\", or \"Am\""
208 ))),
209 }
210}
211
212fn split_compact_key_token(token: &str) -> Option<(&str, String)> {
213 let lower = token.to_ascii_lowercase();
214 for suffix in ["major", "minor", "maj", "min", "m"] {
215 if lower.ends_with(suffix) && lower.len() > suffix.len() {
216 let tonic_end = token.len() - suffix.len();
217 return Some((&token[..tonic_end], canonical_key_mode(suffix)));
218 }
219 }
220 None
221}
222
223fn canonical_key_mode(mode: &str) -> String {
224 match mode.to_ascii_lowercase().as_str() {
225 "maj" | "major" => "major".to_string(),
226 "m" | "min" | "minor" => "minor".to_string(),
227 other => other.to_string(),
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234 use crate::key::keysignature::pitch_name_to_sharps;
235
236 #[test]
237 fn key_from_tonic_mode() {
238 let c_major = Key::from_tonic_mode("C", Some("major")).unwrap();
239 assert_eq!(c_major.sharps(), 0);
240 let g_major = Key::from_tonic_mode("G", Some("major")).unwrap();
241 assert_eq!(g_major.sharps(), 1);
242 let a_minor = Key::from_tonic_mode("A", Some("minor")).unwrap();
243 assert_eq!(a_minor.sharps(), 0);
244 let e_phrygian = Key::from_tonic_mode("E", Some("phrygian")).unwrap();
245 assert_eq!(e_phrygian.sharps(), 0);
246 }
247
248 #[test]
249 fn key_from_string_accepts_common_notation() {
250 let c_major: Key = "C major".parse().unwrap();
251 assert_eq!(c_major.tonic().name(), "C");
252 assert_eq!(c_major.mode(), "major");
253
254 let a_minor: Key = "Am".parse().unwrap();
255 assert_eq!(a_minor.tonic().name(), "A");
256 assert_eq!(a_minor.mode(), "minor");
257
258 let b_flat_minor: Key = "Bb minor".parse().unwrap();
259 assert_eq!(b_flat_minor.tonic().name(), "B-");
260 assert_eq!(b_flat_minor.mode(), "minor");
261 }
262
263 #[test]
264 fn key_scale_degree_and_chords() {
265 let d_major = Key::from_tonic_mode("D", Some("major")).unwrap();
266 assert_eq!(
267 d_major.pitch_from_degree(7).unwrap().name_with_octave(),
268 "C#5"
269 );
270 assert_eq!(
271 d_major.triad_from_degree(1).unwrap().pitched_common_name(),
272 "D-major triad"
273 );
274 assert_eq!(
275 d_major
276 .seventh_chord_from_degree(5)
277 .unwrap()
278 .pitched_common_name(),
279 "A-dominant seventh chord"
280 );
281 }
282
283 #[test]
284 fn key_harmonized_triads() {
285 let c_major = Key::from_tonic_mode("C", Some("major")).unwrap();
286 let triads = c_major.harmonized_triads().unwrap();
287 assert_eq!(triads.len(), 7);
288 assert_eq!(triads[0].pitched_common_name(), "C-major triad");
289 assert_eq!(triads[4].pitched_common_name(), "G-major triad");
290 }
291
292 #[test]
293 fn key_relative_and_parallel() {
294 let c_major = Key::from_tonic_mode("C", Some("major")).unwrap();
295 let relative = c_major.relative().unwrap();
296 assert_eq!(relative.mode(), "minor");
297 assert_eq!(relative.tonic().name(), "A");
298
299 let parallel = c_major.parallel().unwrap();
300 assert_eq!(parallel.mode(), "minor");
301 assert_eq!(parallel.tonic().name(), "C");
302 }
303
304 #[test]
305 fn pitch_name_to_sharps_modes() {
306 assert_eq!(pitch_name_to_sharps("C", Some("major")).unwrap(), 0);
307 assert_eq!(pitch_name_to_sharps("E", Some("minor")).unwrap(), 1);
308 assert_eq!(pitch_name_to_sharps("D", Some("dorian")).unwrap(), 0);
309 assert_eq!(pitch_name_to_sharps("A", Some("mixolydian")).unwrap(), 2);
310 }
311}