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 #[cfg_attr(feature = "serde", serde(default))]
112 color: Option<String>,
113}
114
115impl KeySignature {
116 pub fn new(sharps: IntegerType) -> Self {
118 Self {
119 sharps: Some(sharps),
120 altered: None,
121 accidentals_apply_only_to_octave: false,
122 color: None,
123 }
124 }
125
126 pub fn color(&self) -> Option<&str> {
128 self.color.as_deref()
129 }
130
131 pub fn set_color(&mut self, color: Option<String>) {
133 self.color = color;
134 }
135
136 pub fn from_altered_pitches(pitches: Vec<Pitch>) -> Self {
141 Self {
142 sharps: None,
143 altered: Some(pitches),
144 accidentals_apply_only_to_octave: false,
145 color: None,
146 }
147 }
148
149 pub fn sharps(&self) -> Option<IntegerType> {
152 self.sharps
153 }
154
155 pub fn set_sharps(&mut self, sharps: IntegerType) {
158 self.sharps = Some(sharps);
159 self.altered = None;
160 }
161
162 pub fn set_altered_pitches(&mut self, pitches: Vec<Pitch>) {
164 self.sharps = None;
165 self.altered = Some(pitches);
166 }
167
168 pub fn is_non_traditional(&self) -> bool {
175 self.sharps.is_none()
176 }
177
178 pub fn set_non_traditional(&mut self, non_traditional: bool) {
185 if non_traditional == self.sharps.is_none() {
186 return;
187 }
188 if non_traditional {
189 self.sharps = None;
190 self.altered = Some(Vec::new());
191 } else {
192 self.sharps = Some(0);
193 self.altered = None;
194 }
195 }
196
197 pub fn accidentals_apply_only_to_octave(&self) -> bool {
201 self.accidentals_apply_only_to_octave
202 }
203
204 pub fn set_accidentals_apply_only_to_octave(&mut self, value: bool) {
206 self.accidentals_apply_only_to_octave = value;
207 }
208
209 fn sharps_or_error(&self) -> Result<IntegerType> {
210 self.sharps.ok_or_else(|| {
211 Error::Key(
212 "a non-traditional key signature has no count of sharps or flats".to_string(),
213 )
214 })
215 }
216
217 pub fn description(&self) -> String {
220 match self.sharps {
221 None => {
222 let names: Vec<String> = self
223 .altered
224 .as_deref()
225 .unwrap_or_default()
226 .iter()
227 .map(Pitch::to_string)
228 .collect();
229 format!("pitches: [{}]", names.join(", "))
230 }
231 Some(0) => "no sharps or flats".to_string(),
232 Some(1) => "1 sharp".to_string(),
233 Some(-1) => "1 flat".to_string(),
234 Some(n) if n > 1 => format!("{n} sharps"),
235 Some(n) => format!("{} flats", -n),
236 }
237 }
238
239 pub fn altered_pitches(&self) -> Result<Vec<Pitch>> {
243 if let Some(altered) = &self.altered {
244 return Ok(altered.clone());
245 }
246 let sharps = self.sharps_or_error()?;
247 let (start, interval) = if sharps > 0 {
248 ("B", &*PERFECT_FIFTH)
249 } else {
250 ("F", &*PERFECT_FOURTH)
251 };
252 let mut current = Pitch::from_name(start)?;
253 let mut altered = Vec::with_capacity(sharps.unsigned_abs() as usize);
254 for _ in 0..sharps.abs() {
255 current = interval.transpose_pitch(¤t)?;
256 current.octave_setter(None);
257 altered.push(current.clone());
258 }
259 Ok(altered)
260 }
261
262 pub fn accidental_by_step(&self, step: char) -> Result<Option<Accidental>> {
264 let step = crate::stepname::StepName::try_from(step)?;
265 Ok(self
266 .altered_pitches()?
267 .into_iter()
268 .rev()
269 .find(|pitch| pitch.step() == step)
270 .and_then(|pitch| pitch.written_accidental().cloned()))
271 }
272
273 pub fn transpose(&self, interval: &Interval) -> Result<Self> {
276 let tonic = self.try_as_key(Some("major"), None)?.tonic();
277 let mut transposed = interval.transpose_pitch(&tonic)?;
278 if interval.implicit_diatonic && pitch_to_sharps(&transposed, None)?.abs() > 6 {
279 transposed = transposed.get_enharmonic()?;
280 }
281 let mut moved = Self::new(pitch_to_sharps(&transposed, None)?);
283 moved.color = self.color.clone();
284 Ok(moved)
285 }
286
287 pub fn transpose_pitch_from_c(&self, pitch: &Pitch) -> Result<Pitch> {
292 let sharps = self.sharps_or_error()?;
293 if sharps == 0 {
294 return Ok(pitch.clone());
295 }
296 let step = if sharps < 0 {
297 &*PERFECT_FOURTH_UP
298 } else {
299 &*PERFECT_FIFTH_UP
300 };
301 let mut transposed = pitch.clone();
302 for _ in 0..sharps.unsigned_abs() {
303 transposed = step.transpose_pitch_with_options(&transposed, false, None)?;
304 }
305 transposed.octave_setter(pitch.octave());
306 Ok(transposed)
307 }
308
309 pub fn scale(&self, mode: &str) -> Result<Scale> {
311 let scale_type = match mode {
312 "major" => ScaleType::Major,
313 "minor" => ScaleType::Minor,
314 other => {
315 return Err(Error::Key(format!(
316 "no mapping to a scale exists for this mode yet: {other}"
317 )));
318 }
319 };
320 Ok(Scale::new(
321 scale_type,
322 self.try_as_key(Some(mode), None)?.tonic(),
323 ))
324 }
325
326 pub fn as_key(&self, mode: &str) -> Key {
328 self.try_as_key(Some(mode), None).unwrap_or_else(|_| {
329 Key::new(Pitch::from_name("C").expect("C is valid pitch"), "major", 0)
330 })
331 }
332
333 pub fn try_as_key(&self, mode: Option<&str>, tonic: Option<&str>) -> Result<Key> {
335 let our_sharps = self.sharps_or_error()?;
336
337 let resolved_mode = if mode.is_none() && tonic.is_none() {
338 "major".to_string()
339 } else if mode.is_none() && tonic.is_some() {
340 let tonic_name = tonic.expect("checked is_some above");
341 let major_sharps = pitch_name_to_sharps(tonic_name, None)?;
342 canonical_mode_for_offset(our_sharps - major_sharps)
343 .ok_or_else(|| {
344 Error::Key(format!(
345 "Could not solve for mode from sharps={our_sharps}, tonic={tonic_name}"
346 ))
347 })?
348 .to_string()
349 } else {
350 mode.expect("checked is_some above").to_lowercase()
351 };
352
353 let sharp_alteration_from_major = mode_sharps_alter(&resolved_mode)
354 .ok_or_else(|| Error::Key(format!("Mode {resolved_mode} is unknown")))?;
355
356 let tonic_pitch = sharps_to_pitch(our_sharps - sharp_alteration_from_major)?;
357 Ok(Key::new(tonic_pitch, &resolved_mode, our_sharps))
358 }
359}
360
361impl std::fmt::Display for KeySignature {
362 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
364 write!(f, "KeySignature of {}", self.description())
365 }
366}
367
368#[cfg(test)]
369mod non_traditional_tests {
370 use super::*;
371
372 #[test]
375 fn a_signature_can_be_made_non_traditional_and_back() {
376 let mut signature = KeySignature::new(3);
377 assert!(!signature.is_non_traditional());
378
379 signature.set_non_traditional(true);
380 assert!(signature.is_non_traditional());
381 assert_eq!(signature.sharps(), None);
382
383 signature.set_non_traditional(false);
384 assert!(!signature.is_non_traditional());
385 assert_eq!(signature.sharps(), Some(0));
386
387 let mut three = KeySignature::new(3);
389 three.set_non_traditional(false);
390 assert_eq!(three.sharps(), Some(3));
391 }
392
393 fn pitches(names: &[&str]) -> Vec<Pitch> {
394 names
395 .iter()
396 .map(|name| Pitch::from_name(*name).unwrap())
397 .collect()
398 }
399
400 #[test]
401 fn a_pitch_list_makes_a_non_traditional_signature() {
402 let unusual = KeySignature::from_altered_pitches(pitches(&["Eb", "G#"]));
403 assert!(unusual.is_non_traditional());
404 assert_eq!(unusual.sharps(), None);
405 assert_eq!(unusual.to_string(), "KeySignature of pitches: [Eb, G#]");
406 assert_eq!(
407 unusual.accidental_by_step('G').unwrap().unwrap().name(),
408 "sharp"
409 );
410 assert_eq!(unusual.accidental_by_step('A').unwrap(), None);
411 assert!(unusual.try_as_key(Some("major"), None).is_err());
412 assert!(
413 unusual
414 .transpose_pitch_from_c(&Pitch::from_name("C4").unwrap())
415 .is_err()
416 );
417 }
418
419 #[test]
420 fn setting_pitches_and_sharps_switches_kinds() {
421 let mut signature = KeySignature::new(2);
422 assert_eq!(signature.to_string(), "KeySignature of 2 sharps");
423 signature.set_altered_pitches(pitches(&["F#4"]));
424 assert!(signature.is_non_traditional());
425 assert_eq!(signature.to_string(), "KeySignature of pitches: [F#4]");
426 assert!(!signature.accidentals_apply_only_to_octave());
427 signature.set_accidentals_apply_only_to_octave(true);
428 assert!(signature.accidentals_apply_only_to_octave());
429 signature.set_sharps(-1);
430 assert!(!signature.is_non_traditional());
431 assert_eq!(signature.to_string(), "KeySignature of 1 flat");
432 assert_eq!(
433 KeySignature::new(0).to_string(),
434 "KeySignature of no sharps or flats"
435 );
436 assert_eq!(KeySignature::new(-4).description(), "4 flats");
437 }
438
439 #[test]
440 fn traditional_altered_pitches_carry_explicit_accidentals() {
441 let three_sharps = KeySignature::new(3);
442 let altered = three_sharps.altered_pitches().unwrap();
443 assert!(
444 altered
445 .iter()
446 .all(|pitch| pitch.written_accidental().is_some())
447 );
448 assert_eq!(
449 three_sharps
450 .accidental_by_step('F')
451 .unwrap()
452 .unwrap()
453 .name(),
454 "sharp"
455 );
456 }
457}
458
459#[cfg(test)]
460mod tests {
461
462 #[test]
463 fn a_signature_names_a_key_in_a_mode_or_from_a_tonic() {
464 let two_sharps = KeySignature::new(2);
465 assert_eq!(two_sharps.as_key("major").tonic().name(), "D");
466 assert_eq!(two_sharps.as_key("minor").tonic().name(), "B");
467 let from_tonic = two_sharps.try_as_key(None, Some("b")).unwrap();
468 assert_eq!(
469 (from_tonic.tonic().name(), from_tonic.mode()),
470 ("B".to_string(), "aeolian")
471 );
472 assert_eq!(
473 two_sharps.try_as_key(None, None).unwrap().tonic().name(),
474 "D"
475 );
476 assert_eq!(
478 two_sharps
479 .try_as_key(Some("major"), Some("C"))
480 .unwrap()
481 .tonic()
482 .name(),
483 "D"
484 );
485 let altered = KeySignature::from_altered_pitches(vec![Pitch::from_name("F#").unwrap()]);
486 assert!(altered.try_as_key(Some("major"), None).is_err());
487 assert_eq!(altered.as_key("major").tonic().name(), "C");
488 assert_eq!(
489 pitch_to_sharps(&Pitch::from_name("D").unwrap(), Some("major")).unwrap(),
490 2
491 );
492 assert_eq!(
493 pitch_to_sharps(&Pitch::from_name("D").unwrap(), Some("minor")).unwrap(),
494 -1
495 );
496 assert_eq!(
498 pitch_to_sharps(&Pitch::from_name("D").unwrap(), Some("nonsense")).unwrap(),
499 2
500 );
501 }
502
503 #[test]
504 fn transpose_pitch_from_c_matches_music21() {
505 let cases: [(i32, [&str; 5]); 7] = [
506 (0, ["C4", "E4", "Bb3", "F#5", "G"]),
507 (1, ["G4", "B4", "F3", "C#5", "D"]),
508 (2, ["D4", "F#4", "C3", "G#5", "A"]),
509 (-1, ["F4", "A4", "Eb3", "B5", "C"]),
510 (-3, ["Eb4", "G4", "Db3", "A5", "Bb"]),
511 (7, ["C#4", "E#4", "B3", "F##5", "G#"]),
512 (-7, ["Cb4", "Eb4", "Bbb3", "F5", "Gb"]),
513 ];
514 for (sharps, expected) in cases {
515 let signature = KeySignature::new(sharps);
516 let actual = ["C4", "E4", "Bb3", "F#5", "G"].map(|name| {
517 signature
518 .transpose_pitch_from_c(&Pitch::from_name(name).unwrap())
519 .unwrap()
520 .name_with_octave()
521 });
522 assert_eq!(actual, expected, "{sharps} sharps");
523 }
524 }
525 use super::*;
526
527 #[test]
528 fn altered_pitches_and_transpositions_match_music21() {
529 type Row = (
530 i32,
531 &'static [&'static str],
532 [Option<&'static str>; 3],
533 [i32; 3],
534 &'static str,
535 &'static str,
536 );
537 let cases: [Row; 15] = [
538 (
539 -7,
540 &["Bb", "Eb", "Ab", "Db", "Gb", "Cb", "Fb"],
541 [Some("flat"), Some("flat"), Some("flat")],
542 [-6, -8, -12],
543 "Cb",
544 "Ab",
545 ),
546 (
547 -6,
548 &["Bb", "Eb", "Ab", "Db", "Gb", "Cb"],
549 [None, Some("flat"), Some("flat")],
550 [-5, -7, -11],
551 "Gb",
552 "Eb",
553 ),
554 (
555 -5,
556 &["Bb", "Eb", "Ab", "Db", "Gb"],
557 [None, Some("flat"), None],
558 [-4, -6, -10],
559 "Db",
560 "Bb",
561 ),
562 (
563 -4,
564 &["Bb", "Eb", "Ab", "Db"],
565 [None, Some("flat"), None],
566 [-3, -5, -9],
567 "Ab",
568 "F",
569 ),
570 (
571 -3,
572 &["Bb", "Eb", "Ab"],
573 [None, Some("flat"), None],
574 [-2, -4, -8],
575 "Eb",
576 "C",
577 ),
578 (
579 -2,
580 &["Bb", "Eb"],
581 [None, Some("flat"), None],
582 [-1, -3, -7],
583 "Bb",
584 "G",
585 ),
586 (
587 -1,
588 &["Bb"],
589 [None, Some("flat"), None],
590 [0, -2, -6],
591 "F",
592 "D",
593 ),
594 (0, &[], [None, None, None], [1, -1, -5], "C", "A"),
595 (
596 1,
597 &["F#"],
598 [Some("sharp"), None, None],
599 [2, 0, -4],
600 "G",
601 "E",
602 ),
603 (
604 2,
605 &["F#", "C#"],
606 [Some("sharp"), None, Some("sharp")],
607 [3, 1, -3],
608 "D",
609 "B",
610 ),
611 (
612 3,
613 &["F#", "C#", "G#"],
614 [Some("sharp"), None, Some("sharp")],
615 [4, 2, -2],
616 "A",
617 "F#",
618 ),
619 (
620 4,
621 &["F#", "C#", "G#", "D#"],
622 [Some("sharp"), None, Some("sharp")],
623 [5, 3, -1],
624 "E",
625 "C#",
626 ),
627 (
628 5,
629 &["F#", "C#", "G#", "D#", "A#"],
630 [Some("sharp"), None, Some("sharp")],
631 [6, 4, 0],
632 "B",
633 "G#",
634 ),
635 (
636 6,
637 &["F#", "C#", "G#", "D#", "A#", "E#"],
638 [Some("sharp"), None, Some("sharp")],
639 [7, 5, 1],
640 "F#",
641 "D#",
642 ),
643 (
644 7,
645 &["F#", "C#", "G#", "D#", "A#", "E#", "B#"],
646 [Some("sharp"), Some("sharp"), Some("sharp")],
647 [8, 6, 2],
648 "C#",
649 "A#",
650 ),
651 ];
652 for (sharps, altered, by_step, transposed, major, minor) in cases {
653 let signature = KeySignature::new(sharps);
654 let names = signature
655 .altered_pitches()
656 .unwrap()
657 .iter()
658 .map(Pitch::name)
659 .collect::<Vec<_>>();
660 assert_eq!(names, altered, "{sharps} altered");
661 for (step, expected) in ['F', 'B', 'C'].into_iter().zip(by_step) {
662 let accidental = signature.accidental_by_step(step).unwrap();
663 assert_eq!(
664 accidental.as_ref().map(Accidental::name),
665 expected,
666 "{sharps} {step}"
667 );
668 }
669 for (name, expected) in ["P5", "-P5", "m2"].into_iter().zip(transposed) {
670 let moved = signature
671 .transpose(&Interval::from_name(name).unwrap())
672 .unwrap();
673 assert_eq!(moved.sharps(), Some(expected), "{sharps} by {name}");
674 }
675 assert_eq!(signature.scale("major").unwrap().tonic().name(), major);
676 assert_eq!(signature.scale("minor").unwrap().tonic().name(), minor);
677 }
678 assert!(KeySignature::new(0).scale("dorian").is_err());
679 assert!(KeySignature::new(0).accidental_by_step('H').is_err());
680 }
681 #[test]
682 fn keysignature_as_key_major_minor() {
683 let ks = KeySignature::new(2);
684 assert_eq!(ks.as_key("major").tonic().name(), "D");
685 assert_eq!(ks.as_key("minor").tonic().name(), "B");
686 }
687
688 #[test]
689 fn keysignature_mode_inference_from_tonic() {
690 let ks = KeySignature::new(0);
691 let key = ks.try_as_key(None, Some("D")).unwrap();
692 assert_eq!(key.mode(), "dorian");
693 assert_eq!(key.tonic().name(), "D");
694 }
695
696 #[test]
697 fn sharps_to_pitch_roundtrip() {
698 let f_sharp = sharps_to_pitch(6).unwrap();
699 assert_eq!(f_sharp.name(), "F#");
700 let b_flat = sharps_to_pitch(-2).unwrap();
701 assert_eq!(b_flat.name(), "Bb");
702 assert_eq!(sharps_to_pitch(-7).unwrap().name(), "Cb");
703 assert_eq!(sharps_to_pitch(7).unwrap().name(), "C#");
704 assert_eq!(KeySignature::new(-7).as_key("major").tonic().name(), "Cb");
705 }
706}