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