1use crate::{
2 defaults::IntegerType,
3 error::{Error, Result},
4 interval::Interval,
5 pitch::{Accidental, Pitch},
6 scale::{FIFTHS_ORDER_SHARP, Scale, ScaleType},
7};
8
9use super::Key;
10
11use crate::interval::constants::{
12 PERFECT_FIFTH_DOWN, PERFECT_FIFTH_UP, PERFECT_FIFTH_UP as PERFECT_FIFTH, PERFECT_FOURTH_UP,
13 PERFECT_FOURTH_UP as PERFECT_FOURTH,
14};
15
16const MODE_SHARPS_ALTER: [(&str, IntegerType); 9] = [
17 ("major", 0),
18 ("ionian", 0),
19 ("minor", -3),
20 ("aeolian", -3),
21 ("dorian", -2),
22 ("phrygian", -4),
23 ("lydian", 1),
24 ("mixolydian", -1),
25 ("locrian", -5),
26];
27
28fn canonical_mode_for_offset(offset: IntegerType) -> Option<&'static str> {
29 match offset {
30 0 => Some("ionian"),
31 -1 => Some("mixolydian"),
32 -2 => Some("dorian"),
33 -3 => Some("aeolian"),
34 -4 => Some("phrygian"),
35 -5 => Some("locrian"),
36 1 => Some("lydian"),
37 _ => None,
38 }
39}
40
41pub fn mode_sharps_alter(mode: &str) -> Option<IntegerType> {
43 MODE_SHARPS_ALTER
44 .iter()
45 .find_map(|(name, value)| (*name == mode.to_lowercase()).then_some(*value))
46}
47
48pub fn sharps_to_pitch(sharp_count: IntegerType) -> Result<Pitch> {
50 if sharp_count == 0 {
51 return Pitch::from_name("C");
52 }
53
54 let mut pitch = Pitch::from_name("C")?;
55 pitch.octave_setter(None);
56
57 let interval = if sharp_count > 0 {
58 &*PERFECT_FIFTH
59 } else {
60 &*PERFECT_FIFTH_DOWN
61 };
62
63 for _ in 0..sharp_count.abs() {
64 pitch = interval.transpose_pitch(&pitch)?;
65 pitch.octave_setter(None);
66 }
67 Ok(pitch)
68}
69
70pub fn pitch_to_sharps(pitch_value: &Pitch, mode: Option<&str>) -> Result<IntegerType> {
72 let step_index = FIFTHS_ORDER_SHARP
73 .iter()
74 .position(|step| *step == pitch_value.step())
75 .ok_or_else(|| Error::StepName("cannot map step to circle of fifths".to_string()))?;
76
77 let mut sharps = step_index as IntegerType - 1;
78 if !pitch_value.accidental().is_twelve_tone() {
79 return Err(Error::Key(
80 "Cannot determine sharps for quarter-tone keys! silly!".to_string(),
81 ));
82 }
83 sharps += 7 * pitch_value.accidental().alter() as IntegerType;
84
85 if let Some(offset) = mode.and_then(mode_sharps_alter) {
89 sharps += offset;
90 }
91
92 Ok(sharps)
93}
94
95pub fn pitch_name_to_sharps(pitch_name: &str, mode: Option<&str>) -> Result<IntegerType> {
97 let pitch = Pitch::from_name(pitch_name)?;
98 pitch_to_sharps(&pitch, mode)
99}
100
101#[derive(Clone, Debug)]
102#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
103#[must_use]
107pub struct KeySignature {
108 sharps: Option<IntegerType>,
109 altered: Option<Vec<Pitch>>,
110 accidentals_apply_only_to_octave: bool,
111}
112
113impl KeySignature {
114 pub fn new(sharps: IntegerType) -> Self {
116 Self {
117 sharps: Some(sharps),
118 altered: None,
119 accidentals_apply_only_to_octave: false,
120 }
121 }
122
123 pub fn from_altered_pitches(pitches: Vec<Pitch>) -> Self {
128 Self {
129 sharps: None,
130 altered: Some(pitches),
131 accidentals_apply_only_to_octave: false,
132 }
133 }
134
135 pub fn sharps(&self) -> Option<IntegerType> {
138 self.sharps
139 }
140
141 pub fn set_sharps(&mut self, sharps: IntegerType) {
144 self.sharps = Some(sharps);
145 self.altered = None;
146 }
147
148 pub fn set_altered_pitches(&mut self, pitches: Vec<Pitch>) {
150 self.sharps = None;
151 self.altered = Some(pitches);
152 }
153
154 pub fn is_non_traditional(&self) -> bool {
161 self.sharps.is_none()
162 }
163
164 pub fn set_non_traditional(&mut self, non_traditional: bool) {
171 if non_traditional == self.sharps.is_none() {
172 return;
173 }
174 if non_traditional {
175 self.sharps = None;
176 self.altered = Some(Vec::new());
177 } else {
178 self.sharps = Some(0);
179 self.altered = None;
180 }
181 }
182
183 pub fn accidentals_apply_only_to_octave(&self) -> bool {
187 self.accidentals_apply_only_to_octave
188 }
189
190 pub fn set_accidentals_apply_only_to_octave(&mut self, value: bool) {
192 self.accidentals_apply_only_to_octave = value;
193 }
194
195 fn sharps_or_error(&self) -> Result<IntegerType> {
196 self.sharps.ok_or_else(|| {
197 Error::Key(
198 "a non-traditional key signature has no count of sharps or flats".to_string(),
199 )
200 })
201 }
202
203 pub fn description(&self) -> String {
206 match self.sharps {
207 None => {
208 let names: Vec<String> = self
209 .altered
210 .as_deref()
211 .unwrap_or_default()
212 .iter()
213 .map(Pitch::to_string)
214 .collect();
215 format!("pitches: [{}]", names.join(", "))
216 }
217 Some(0) => "no sharps or flats".to_string(),
218 Some(1) => "1 sharp".to_string(),
219 Some(-1) => "1 flat".to_string(),
220 Some(n) if n > 1 => format!("{n} sharps"),
221 Some(n) => format!("{} flats", -n),
222 }
223 }
224
225 pub fn altered_pitches(&self) -> Result<Vec<Pitch>> {
229 if let Some(altered) = &self.altered {
230 return Ok(altered.clone());
231 }
232 let sharps = self.sharps_or_error()?;
233 let (start, interval) = if sharps > 0 {
234 ("B", &*PERFECT_FIFTH)
235 } else {
236 ("F", &*PERFECT_FOURTH)
237 };
238 let mut current = Pitch::from_name(start)?;
239 let mut altered = Vec::with_capacity(sharps.unsigned_abs() as usize);
240 for _ in 0..sharps.abs() {
241 current = interval.transpose_pitch(¤t)?;
242 current.octave_setter(None);
243 altered.push(current.clone());
244 }
245 Ok(altered)
246 }
247
248 pub fn accidental_by_step(&self, step: char) -> Result<Option<Accidental>> {
250 let step = crate::stepname::StepName::try_from(step)?;
251 Ok(self
252 .altered_pitches()?
253 .into_iter()
254 .rev()
255 .find(|pitch| pitch.step() == step)
256 .and_then(|pitch| pitch.explicit_accidental().cloned()))
257 }
258
259 pub fn transpose(&self, interval: &Interval) -> Result<Self> {
262 let tonic = self.try_as_key(Some("major"), None)?.tonic();
263 let mut transposed = interval.transpose_pitch(&tonic)?;
264 if interval.implicit_diatonic && pitch_to_sharps(&transposed, None)?.abs() > 6 {
265 transposed = transposed.get_enharmonic()?;
266 }
267 Ok(Self::new(pitch_to_sharps(&transposed, None)?))
268 }
269
270 pub fn transpose_pitch_from_c(&self, pitch: &Pitch) -> Result<Pitch> {
275 let sharps = self.sharps_or_error()?;
276 if sharps == 0 {
277 return Ok(pitch.clone());
278 }
279 let step = if sharps < 0 {
280 &*PERFECT_FOURTH_UP
281 } else {
282 &*PERFECT_FIFTH_UP
283 };
284 let mut transposed = pitch.clone();
285 for _ in 0..sharps.unsigned_abs() {
286 transposed = step.transpose_pitch_with_options(&transposed, false, None)?;
287 }
288 transposed.octave_setter(pitch.octave());
289 Ok(transposed)
290 }
291
292 pub fn scale(&self, mode: &str) -> Result<Scale> {
294 let scale_type = match mode {
295 "major" => ScaleType::Major,
296 "minor" => ScaleType::Minor,
297 other => {
298 return Err(Error::Key(format!(
299 "no mapping to a scale exists for this mode yet: {other}"
300 )));
301 }
302 };
303 Ok(Scale::new(
304 scale_type,
305 self.try_as_key(Some(mode), None)?.tonic(),
306 ))
307 }
308
309 pub fn as_key(&self, mode: &str) -> Key {
311 self.try_as_key(Some(mode), None).unwrap_or_else(|_| {
312 Key::new(Pitch::from_name("C").expect("C is valid pitch"), "major", 0)
313 })
314 }
315
316 pub fn try_as_key(&self, mode: Option<&str>, tonic: Option<&str>) -> Result<Key> {
318 let our_sharps = self.sharps_or_error()?;
319
320 let resolved_mode = if mode.is_none() && tonic.is_none() {
321 "major".to_string()
322 } else if mode.is_none() && tonic.is_some() {
323 let tonic_name = tonic.expect("checked is_some above");
324 let major_sharps = pitch_name_to_sharps(tonic_name, None)?;
325 canonical_mode_for_offset(our_sharps - major_sharps)
326 .ok_or_else(|| {
327 Error::Key(format!(
328 "Could not solve for mode from sharps={our_sharps}, tonic={tonic_name}"
329 ))
330 })?
331 .to_string()
332 } else {
333 mode.expect("checked is_some above").to_lowercase()
334 };
335
336 let sharp_alteration_from_major = mode_sharps_alter(&resolved_mode)
337 .ok_or_else(|| Error::Key(format!("Mode {resolved_mode} is unknown")))?;
338
339 let tonic_pitch = sharps_to_pitch(our_sharps - sharp_alteration_from_major)?;
340 Ok(Key::new(tonic_pitch, &resolved_mode, our_sharps))
341 }
342}
343
344impl std::fmt::Display for KeySignature {
345 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
347 write!(f, "KeySignature of {}", self.description())
348 }
349}
350
351#[cfg(test)]
352mod non_traditional_tests {
353 use super::*;
354
355 #[test]
358 fn a_signature_can_be_made_non_traditional_and_back() {
359 let mut signature = KeySignature::new(3);
360 assert!(!signature.is_non_traditional());
361
362 signature.set_non_traditional(true);
363 assert!(signature.is_non_traditional());
364 assert_eq!(signature.sharps(), None);
365
366 signature.set_non_traditional(false);
367 assert!(!signature.is_non_traditional());
368 assert_eq!(signature.sharps(), Some(0));
369
370 let mut three = KeySignature::new(3);
372 three.set_non_traditional(false);
373 assert_eq!(three.sharps(), Some(3));
374 }
375
376 fn pitches(names: &[&str]) -> Vec<Pitch> {
377 names
378 .iter()
379 .map(|name| Pitch::from_name(*name).unwrap())
380 .collect()
381 }
382
383 #[test]
384 fn a_pitch_list_makes_a_non_traditional_signature() {
385 let unusual = KeySignature::from_altered_pitches(pitches(&["E-", "G#"]));
386 assert!(unusual.is_non_traditional());
387 assert_eq!(unusual.sharps(), None);
388 assert_eq!(unusual.to_string(), "KeySignature of pitches: [E-, G#]");
389 assert_eq!(
390 unusual.accidental_by_step('G').unwrap().unwrap().name(),
391 "sharp"
392 );
393 assert_eq!(unusual.accidental_by_step('A').unwrap(), None);
394 assert!(unusual.try_as_key(Some("major"), None).is_err());
395 assert!(
396 unusual
397 .transpose_pitch_from_c(&Pitch::from_name("C4").unwrap())
398 .is_err()
399 );
400 }
401
402 #[test]
403 fn setting_pitches_and_sharps_switches_kinds() {
404 let mut signature = KeySignature::new(2);
405 assert_eq!(signature.to_string(), "KeySignature of 2 sharps");
406 signature.set_altered_pitches(pitches(&["F#4"]));
407 assert!(signature.is_non_traditional());
408 assert_eq!(signature.to_string(), "KeySignature of pitches: [F#4]");
409 assert!(!signature.accidentals_apply_only_to_octave());
410 signature.set_accidentals_apply_only_to_octave(true);
411 assert!(signature.accidentals_apply_only_to_octave());
412 signature.set_sharps(-1);
413 assert!(!signature.is_non_traditional());
414 assert_eq!(signature.to_string(), "KeySignature of 1 flat");
415 assert_eq!(
416 KeySignature::new(0).to_string(),
417 "KeySignature of no sharps or flats"
418 );
419 assert_eq!(KeySignature::new(-4).description(), "4 flats");
420 }
421
422 #[test]
423 fn traditional_altered_pitches_carry_explicit_accidentals() {
424 let three_sharps = KeySignature::new(3);
425 let altered = three_sharps.altered_pitches().unwrap();
426 assert!(altered.iter().all(Pitch::has_accidental));
427 assert_eq!(
428 three_sharps
429 .accidental_by_step('F')
430 .unwrap()
431 .unwrap()
432 .name(),
433 "sharp"
434 );
435 }
436}
437
438#[cfg(test)]
439mod tests {
440
441 #[test]
442 fn a_signature_names_a_key_in_a_mode_or_from_a_tonic() {
443 let two_sharps = KeySignature::new(2);
444 assert_eq!(two_sharps.as_key("major").tonic().name(), "D");
445 assert_eq!(two_sharps.as_key("minor").tonic().name(), "B");
446 let from_tonic = two_sharps.try_as_key(None, Some("b")).unwrap();
447 assert_eq!(
448 (from_tonic.tonic().name(), from_tonic.mode()),
449 ("B".to_string(), "aeolian")
450 );
451 assert_eq!(
452 two_sharps.try_as_key(None, None).unwrap().tonic().name(),
453 "D"
454 );
455 assert_eq!(
457 two_sharps
458 .try_as_key(Some("major"), Some("C"))
459 .unwrap()
460 .tonic()
461 .name(),
462 "D"
463 );
464 let altered = KeySignature::from_altered_pitches(vec![Pitch::from_name("F#").unwrap()]);
465 assert!(altered.try_as_key(Some("major"), None).is_err());
466 assert_eq!(altered.as_key("major").tonic().name(), "C");
467 assert_eq!(
468 pitch_to_sharps(&Pitch::from_name("D").unwrap(), Some("major")).unwrap(),
469 2
470 );
471 assert_eq!(
472 pitch_to_sharps(&Pitch::from_name("D").unwrap(), Some("minor")).unwrap(),
473 -1
474 );
475 assert_eq!(
477 pitch_to_sharps(&Pitch::from_name("D").unwrap(), Some("nonsense")).unwrap(),
478 2
479 );
480 }
481
482 #[test]
483 fn transpose_pitch_from_c_matches_music21() {
484 let cases: [(i32, [&str; 5]); 7] = [
485 (0, ["C4", "E4", "B-3", "F#5", "G"]),
486 (1, ["G4", "B4", "F3", "C#5", "D"]),
487 (2, ["D4", "F#4", "C3", "G#5", "A"]),
488 (-1, ["F4", "A4", "E-3", "B5", "C"]),
489 (-3, ["E-4", "G4", "D-3", "A5", "B-"]),
490 (7, ["C#4", "E#4", "B3", "F##5", "G#"]),
491 (-7, ["C-4", "E-4", "B--3", "F5", "G-"]),
492 ];
493 for (sharps, expected) in cases {
494 let signature = KeySignature::new(sharps);
495 let actual = ["C4", "E4", "B-3", "F#5", "G"].map(|name| {
496 signature
497 .transpose_pitch_from_c(&Pitch::from_name(name).unwrap())
498 .unwrap()
499 .name_with_octave()
500 });
501 assert_eq!(actual, expected, "{sharps} sharps");
502 }
503 }
504 use super::*;
505
506 #[test]
507 fn altered_pitches_and_transpositions_match_music21() {
508 type Row = (
509 i32,
510 &'static [&'static str],
511 [Option<&'static str>; 3],
512 [i32; 3],
513 &'static str,
514 &'static str,
515 );
516 let cases: [Row; 15] = [
517 (
518 -7,
519 &["B-", "E-", "A-", "D-", "G-", "C-", "F-"],
520 [Some("flat"), Some("flat"), Some("flat")],
521 [-6, -8, -12],
522 "C-",
523 "A-",
524 ),
525 (
526 -6,
527 &["B-", "E-", "A-", "D-", "G-", "C-"],
528 [None, Some("flat"), Some("flat")],
529 [-5, -7, -11],
530 "G-",
531 "E-",
532 ),
533 (
534 -5,
535 &["B-", "E-", "A-", "D-", "G-"],
536 [None, Some("flat"), None],
537 [-4, -6, -10],
538 "D-",
539 "B-",
540 ),
541 (
542 -4,
543 &["B-", "E-", "A-", "D-"],
544 [None, Some("flat"), None],
545 [-3, -5, -9],
546 "A-",
547 "F",
548 ),
549 (
550 -3,
551 &["B-", "E-", "A-"],
552 [None, Some("flat"), None],
553 [-2, -4, -8],
554 "E-",
555 "C",
556 ),
557 (
558 -2,
559 &["B-", "E-"],
560 [None, Some("flat"), None],
561 [-1, -3, -7],
562 "B-",
563 "G",
564 ),
565 (
566 -1,
567 &["B-"],
568 [None, Some("flat"), None],
569 [0, -2, -6],
570 "F",
571 "D",
572 ),
573 (0, &[], [None, None, None], [1, -1, -5], "C", "A"),
574 (
575 1,
576 &["F#"],
577 [Some("sharp"), None, None],
578 [2, 0, -4],
579 "G",
580 "E",
581 ),
582 (
583 2,
584 &["F#", "C#"],
585 [Some("sharp"), None, Some("sharp")],
586 [3, 1, -3],
587 "D",
588 "B",
589 ),
590 (
591 3,
592 &["F#", "C#", "G#"],
593 [Some("sharp"), None, Some("sharp")],
594 [4, 2, -2],
595 "A",
596 "F#",
597 ),
598 (
599 4,
600 &["F#", "C#", "G#", "D#"],
601 [Some("sharp"), None, Some("sharp")],
602 [5, 3, -1],
603 "E",
604 "C#",
605 ),
606 (
607 5,
608 &["F#", "C#", "G#", "D#", "A#"],
609 [Some("sharp"), None, Some("sharp")],
610 [6, 4, 0],
611 "B",
612 "G#",
613 ),
614 (
615 6,
616 &["F#", "C#", "G#", "D#", "A#", "E#"],
617 [Some("sharp"), None, Some("sharp")],
618 [7, 5, 1],
619 "F#",
620 "D#",
621 ),
622 (
623 7,
624 &["F#", "C#", "G#", "D#", "A#", "E#", "B#"],
625 [Some("sharp"), Some("sharp"), Some("sharp")],
626 [8, 6, 2],
627 "C#",
628 "A#",
629 ),
630 ];
631 for (sharps, altered, by_step, transposed, major, minor) in cases {
632 let signature = KeySignature::new(sharps);
633 let names = signature
634 .altered_pitches()
635 .unwrap()
636 .iter()
637 .map(Pitch::name)
638 .collect::<Vec<_>>();
639 assert_eq!(names, altered, "{sharps} altered");
640 for (step, expected) in ['F', 'B', 'C'].into_iter().zip(by_step) {
641 let accidental = signature.accidental_by_step(step).unwrap();
642 assert_eq!(
643 accidental.as_ref().map(Accidental::name),
644 expected,
645 "{sharps} {step}"
646 );
647 }
648 for (name, expected) in ["P5", "-P5", "m2"].into_iter().zip(transposed) {
649 let moved = signature
650 .transpose(&Interval::from_name(name).unwrap())
651 .unwrap();
652 assert_eq!(moved.sharps(), Some(expected), "{sharps} by {name}");
653 }
654 assert_eq!(signature.scale("major").unwrap().tonic().name(), major);
655 assert_eq!(signature.scale("minor").unwrap().tonic().name(), minor);
656 }
657 assert!(KeySignature::new(0).scale("dorian").is_err());
658 assert!(KeySignature::new(0).accidental_by_step('H').is_err());
659 }
660 #[test]
661 fn keysignature_as_key_major_minor() {
662 let ks = KeySignature::new(2);
663 assert_eq!(ks.as_key("major").tonic().name(), "D");
664 assert_eq!(ks.as_key("minor").tonic().name(), "B");
665 }
666
667 #[test]
668 fn keysignature_mode_inference_from_tonic() {
669 let ks = KeySignature::new(0);
670 let key = ks.try_as_key(None, Some("D")).unwrap();
671 assert_eq!(key.mode(), "dorian");
672 assert_eq!(key.tonic().name(), "D");
673 }
674
675 #[test]
676 fn sharps_to_pitch_roundtrip() {
677 let f_sharp = sharps_to_pitch(6).unwrap();
678 assert_eq!(f_sharp.name(), "F#");
679 let b_flat = sharps_to_pitch(-2).unwrap();
680 assert_eq!(b_flat.name(), "B-");
681 assert_eq!(sharps_to_pitch(-7).unwrap().name(), "C-");
682 assert_eq!(sharps_to_pitch(7).unwrap().name(), "C#");
683 assert_eq!(KeySignature::new(-7).as_key("major").tonic().name(), "C-");
684 }
685}