1use crate::{
2 defaults::{IntegerType, UnsignedIntegerType},
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
48const MOST_SHARPS: IntegerType = 1000;
52
53fn fifths_walked(sharps: IntegerType) -> Result<UnsignedIntegerType> {
56 let walked = sharps.unsigned_abs();
57 if walked > MOST_SHARPS.unsigned_abs() {
58 return Err(Error::Key(format!(
59 "a signature of {sharps} sharps is more than {MOST_SHARPS} either way"
60 )));
61 }
62 Ok(walked)
63}
64
65pub fn sharps_to_pitch(sharp_count: IntegerType) -> Result<Pitch> {
67 if sharp_count == 0 {
68 return Pitch::from_name("C");
69 }
70
71 let walked = fifths_walked(sharp_count)?;
72 let mut pitch = Pitch::from_name("C")?;
73 pitch.octave_setter(None);
74
75 let interval = if sharp_count > 0 {
76 &*PERFECT_FIFTH
77 } else {
78 &*PERFECT_FIFTH_DOWN
79 };
80
81 for _ in 0..walked {
82 pitch = interval.transpose_pitch(&pitch)?;
83 pitch.octave_setter(None);
84 }
85 Ok(pitch)
86}
87
88pub fn pitch_to_sharps(pitch_value: &Pitch, mode: Option<&str>) -> Result<IntegerType> {
90 let step_index = FIFTHS_ORDER_SHARP
91 .iter()
92 .position(|step| *step == pitch_value.step())
93 .ok_or_else(|| Error::StepName("cannot map step to circle of fifths".to_string()))?;
94
95 let mut sharps = step_index as IntegerType - 1;
96 if !pitch_value.accidental().is_twelve_tone() {
97 return Err(Error::Key(
98 "Cannot determine sharps for quarter-tone keys! silly!".to_string(),
99 ));
100 }
101 sharps += 7 * pitch_value.accidental().alter() as IntegerType;
102
103 if let Some(offset) = mode.and_then(mode_sharps_alter) {
107 sharps += offset;
108 }
109
110 Ok(sharps)
111}
112
113pub fn pitch_name_to_sharps(pitch_name: &str, mode: Option<&str>) -> Result<IntegerType> {
115 let pitch = Pitch::from_name(pitch_name)?;
116 pitch_to_sharps(&pitch, mode)
117}
118
119#[derive(Clone, Debug)]
120#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
121#[cfg_attr(feature = "serde", serde(from = "SavedKeySignature"))]
122#[must_use]
129pub struct KeySignature {
130 sharps: IntegerType,
131 #[cfg_attr(feature = "serde", serde(default))]
133 non_traditional: bool,
134 altered: Option<Vec<Pitch>>,
137 accidentals_apply_only_to_octave: bool,
138 #[cfg_attr(feature = "serde", serde(default))]
139 color: Option<String>,
140}
141
142#[cfg(feature = "serde")]
145#[derive(serde::Deserialize)]
146struct SavedKeySignature {
147 sharps: Option<IntegerType>,
148 #[serde(default)]
149 non_traditional: bool,
150 altered: Option<Vec<Pitch>>,
151 accidentals_apply_only_to_octave: bool,
152 #[serde(default)]
153 color: Option<String>,
154}
155
156#[cfg(feature = "serde")]
157impl From<SavedKeySignature> for KeySignature {
158 fn from(saved: SavedKeySignature) -> Self {
159 Self {
160 sharps: saved.sharps.unwrap_or(0),
161 non_traditional: saved.non_traditional || saved.sharps.is_none(),
162 altered: saved.altered,
163 accidentals_apply_only_to_octave: saved.accidentals_apply_only_to_octave,
164 color: saved.color,
165 }
166 }
167}
168
169impl PartialEq for KeySignature {
170 fn eq(&self, other: &Self) -> bool {
172 self.sharps == other.sharps && self.non_traditional == other.non_traditional
173 }
174}
175
176impl KeySignature {
177 pub fn new(sharps: IntegerType) -> Self {
179 Self {
180 sharps,
181 non_traditional: false,
182 altered: None,
183 accidentals_apply_only_to_octave: false,
184 color: None,
185 }
186 }
187
188 pub fn color(&self) -> Option<&str> {
190 self.color.as_deref()
191 }
192
193 pub fn set_color(&mut self, color: Option<String>) {
195 self.color = color;
196 }
197
198 pub fn from_altered_pitches(pitches: Vec<Pitch>) -> Self {
202 let mut signature = Self::new(0);
203 signature.non_traditional = true;
204 signature.altered = Some(pitches);
205 signature
206 }
207
208 pub fn sharps(&self) -> IntegerType {
210 self.sharps
211 }
212
213 pub fn set_sharps(&mut self, sharps: IntegerType) {
216 self.sharps = sharps;
217 }
218
219 pub fn set_altered_pitches(&mut self, pitches: Vec<Pitch>) {
223 self.altered = Some(pitches);
224 }
225
226 pub fn is_non_traditional(&self) -> bool {
229 self.non_traditional
230 }
231
232 pub fn set_non_traditional(&mut self, non_traditional: bool) {
235 self.non_traditional = non_traditional;
236 }
237
238 pub fn accidentals_apply_only_to_octave(&self) -> bool {
242 self.accidentals_apply_only_to_octave
243 }
244
245 pub fn set_accidentals_apply_only_to_octave(&mut self, value: bool) {
247 self.accidentals_apply_only_to_octave = value;
248 }
249
250 pub fn description(&self) -> String {
253 if self.non_traditional {
254 let names: Vec<String> = self
255 .altered
256 .as_deref()
257 .unwrap_or_default()
258 .iter()
259 .map(Pitch::to_string)
260 .collect();
261 return format!("pitches: [{}]", names.join(", "));
262 }
263 match self.sharps {
264 0 => "no sharps or flats".to_string(),
265 1 => "1 sharp".to_string(),
266 -1 => "1 flat".to_string(),
267 n if n > 1 => format!("{n} sharps"),
268 n => format!("{} flats", -n),
269 }
270 }
271
272 pub fn altered_pitches(&self) -> Result<Vec<Pitch>> {
276 if let Some(altered) = &self.altered {
277 return Ok(altered.clone());
278 }
279 if self.non_traditional {
280 return Ok(Vec::new());
281 }
282 let sharps = self.sharps;
283 let (start, interval) = if sharps > 0 {
284 ("B", &*PERFECT_FIFTH)
285 } else {
286 ("F", &*PERFECT_FOURTH)
287 };
288 let walked = fifths_walked(sharps)?;
289 let mut current = Pitch::from_name(start)?;
290 let mut altered = Vec::with_capacity(walked as usize);
291 for _ in 0..walked {
292 current = interval.transpose_pitch(¤t)?;
293 current.octave_setter(None);
294 altered.push(current.clone());
295 }
296 Ok(altered)
297 }
298
299 pub fn accidental_by_step(&self, step: char) -> Result<Option<Accidental>> {
301 let step = crate::stepname::StepName::try_from(step)?;
302 Ok(self
303 .altered_pitches()?
304 .into_iter()
305 .rev()
306 .find(|pitch| pitch.step() == step)
307 .and_then(|pitch| pitch.written_accidental().cloned()))
308 }
309
310 pub fn transpose(&self, interval: &Interval) -> Result<Self> {
313 let tonic = self.try_as_key(Some("major"), None)?.tonic();
314 let mut transposed = interval.transpose_pitch(&tonic)?;
315 if interval.implicit_diatonic && pitch_to_sharps(&transposed, None)?.abs() > 6 {
316 transposed = transposed.get_enharmonic()?;
317 }
318 let mut moved = self.clone();
321 moved.sharps = pitch_to_sharps(&transposed, None)?;
322 Ok(moved)
323 }
324
325 pub fn transpose_pitch_from_c(&self, pitch: &Pitch) -> Result<Pitch> {
330 let sharps = self.sharps;
331 if sharps == 0 {
332 return Ok(pitch.clone());
333 }
334 let step = if sharps < 0 {
335 &*PERFECT_FOURTH_UP
336 } else {
337 &*PERFECT_FIFTH_UP
338 };
339 let walked = fifths_walked(sharps)?;
340 let mut transposed = pitch.clone();
341 for _ in 0..walked {
342 transposed = step.transpose_pitch_with_options(&transposed, false, None)?;
343 }
344 transposed.octave_setter(pitch.octave());
345 Ok(transposed)
346 }
347
348 pub fn scale(&self, mode: &str) -> Result<Scale> {
350 let scale_type = match mode {
351 "major" => ScaleType::Major,
352 "minor" => ScaleType::Minor,
353 other => {
354 return Err(Error::Key(format!(
355 "no mapping to a scale exists for this mode yet: {other}"
356 )));
357 }
358 };
359 Ok(Scale::new(
360 scale_type,
361 self.try_as_key(Some(mode), None)?.tonic(),
362 ))
363 }
364
365 pub fn as_key(&self, mode: &str) -> Key {
367 self.try_as_key(Some(mode), None).unwrap_or_else(|_| {
368 Key::new(Pitch::from_name("C").expect("C is valid pitch"), "major", 0)
369 })
370 }
371
372 pub fn try_as_key(&self, mode: Option<&str>, tonic: Option<&str>) -> Result<Key> {
374 let our_sharps = self.sharps;
375
376 let resolved_mode = if mode.is_none() && tonic.is_none() {
377 "major".to_string()
378 } else if mode.is_none() && tonic.is_some() {
379 let tonic_name = tonic.expect("checked is_some above");
380 let major_sharps = pitch_name_to_sharps(tonic_name, None)?;
381 canonical_mode_for_offset(our_sharps.saturating_sub(major_sharps))
382 .ok_or_else(|| {
383 Error::Key(format!(
384 "Could not solve for mode from sharps={our_sharps}, tonic={tonic_name}"
385 ))
386 })?
387 .to_string()
388 } else {
389 mode.expect("checked is_some above").to_lowercase()
390 };
391
392 let sharp_alteration_from_major = mode_sharps_alter(&resolved_mode)
393 .ok_or_else(|| Error::Key(format!("Mode {resolved_mode} is unknown")))?;
394
395 let tonic_pitch = sharps_to_pitch(our_sharps.saturating_sub(sharp_alteration_from_major))?;
396 Ok(Key::new(tonic_pitch, &resolved_mode, our_sharps))
397 }
398}
399
400impl std::fmt::Display for KeySignature {
401 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
403 write!(f, "KeySignature of {}", self.description())
404 }
405}
406
407#[cfg(test)]
408mod non_traditional_tests {
409 use super::*;
410
411 #[test]
413 fn a_signature_can_be_made_non_traditional_and_back() {
414 let mut signature = KeySignature::new(3);
415 assert!(!signature.is_non_traditional());
416
417 signature.set_non_traditional(true);
418 assert!(signature.is_non_traditional());
419 assert_eq!(signature.sharps(), 3);
420
421 signature.set_non_traditional(false);
422 assert!(!signature.is_non_traditional());
423 assert_eq!(signature.sharps(), 3);
424 }
425
426 fn pitches(names: &[&str]) -> Vec<Pitch> {
427 names
428 .iter()
429 .map(|name| Pitch::from_name(*name).unwrap())
430 .collect()
431 }
432
433 #[test]
436 fn a_pitch_list_makes_a_non_traditional_signature() {
437 let unusual = KeySignature::from_altered_pitches(pitches(&["Eb", "G#"]));
438 assert!(unusual.is_non_traditional());
439 assert_eq!(unusual.sharps(), 0);
440 assert_eq!(unusual.to_string(), "KeySignature of pitches: [Eb, G#]");
441 assert_eq!(
442 unusual.accidental_by_step('G').unwrap().unwrap().name(),
443 "sharp"
444 );
445 assert_eq!(unusual.accidental_by_step('A').unwrap(), None);
446 assert_eq!(
448 unusual
449 .try_as_key(Some("major"), None)
450 .unwrap()
451 .tonic()
452 .name(),
453 "C"
454 );
455 assert_eq!(
456 unusual
457 .transpose_pitch_from_c(&Pitch::from_name("C4").unwrap())
458 .unwrap()
459 .name_with_octave(),
460 "C4"
461 );
462 }
463
464 #[test]
466 fn sharps_flag_and_pitches_are_set_apart() {
467 let mut signature = KeySignature::new(2);
469 signature.set_altered_pitches(pitches(&["Eb"]));
470 assert!(!signature.is_non_traditional());
471 assert_eq!(signature.sharps(), 2);
472 assert_eq!(signature.to_string(), "KeySignature of 2 sharps");
473 assert_eq!(signature.altered_pitches().unwrap(), pitches(&["Eb"]));
474 signature.set_sharps(3);
475 assert_eq!(signature.altered_pitches().unwrap(), pitches(&["Eb"]));
476
477 let mut flagged = KeySignature::new(2);
479 flagged.set_non_traditional(true);
480 assert_eq!(flagged.sharps(), 2);
481 assert_eq!(flagged.to_string(), "KeySignature of pitches: []");
482 assert_eq!(flagged.try_as_key(None, None).unwrap().tonic().name(), "D");
483 flagged.set_sharps(3);
484 assert!(flagged.is_non_traditional());
485 flagged.set_non_traditional(false);
486 assert_eq!(flagged.to_string(), "KeySignature of 3 sharps");
487
488 assert!(!signature.accidentals_apply_only_to_octave());
489 signature.set_accidentals_apply_only_to_octave(true);
490 assert!(signature.accidentals_apply_only_to_octave());
491 assert_eq!(
492 KeySignature::new(0).to_string(),
493 "KeySignature of no sharps or flats"
494 );
495 assert_eq!(KeySignature::new(-4).description(), "4 flats");
496 }
497
498 #[test]
501 fn transposing_moves_only_the_count() {
502 let mut unusual = KeySignature::new(2);
503 unusual.set_non_traditional(true);
504 unusual.set_altered_pitches(pitches(&["B-", "F#"]));
505 let moved = unusual
506 .transpose(&Interval::from_name("M2").unwrap())
507 .unwrap();
508 assert_eq!(moved.sharps(), 4);
509 assert!(moved.is_non_traditional());
510 assert_eq!(moved.altered_pitches().unwrap(), pitches(&["B-", "F#"]));
511 }
512
513 #[test]
516 fn the_flag_counts_in_equality() {
517 let mut flagged = KeySignature::new(2);
518 flagged.set_non_traditional(true);
519 assert_ne!(flagged, KeySignature::new(2));
520 assert_eq!(KeySignature::new(2), KeySignature::new(2));
521 }
522
523 #[test]
524 fn traditional_altered_pitches_carry_explicit_accidentals() {
525 let three_sharps = KeySignature::new(3);
526 let altered = three_sharps.altered_pitches().unwrap();
527 assert!(
528 altered
529 .iter()
530 .all(|pitch| pitch.written_accidental().is_some())
531 );
532 assert_eq!(
533 three_sharps
534 .accidental_by_step('F')
535 .unwrap()
536 .unwrap()
537 .name(),
538 "sharp"
539 );
540 }
541}
542
543#[cfg(test)]
544mod tests {
545
546 #[test]
547 fn a_signature_names_a_key_in_a_mode_or_from_a_tonic() {
548 let two_sharps = KeySignature::new(2);
549 assert_eq!(two_sharps.as_key("major").tonic().name(), "D");
550 assert_eq!(two_sharps.as_key("minor").tonic().name(), "B");
551 let from_tonic = two_sharps.try_as_key(None, Some("b")).unwrap();
552 assert_eq!(
553 (from_tonic.tonic().name(), from_tonic.mode()),
554 ("B".to_string(), "aeolian")
555 );
556 assert_eq!(
557 two_sharps.try_as_key(None, None).unwrap().tonic().name(),
558 "D"
559 );
560 assert_eq!(
562 two_sharps
563 .try_as_key(Some("major"), Some("C"))
564 .unwrap()
565 .tonic()
566 .name(),
567 "D"
568 );
569 let altered = KeySignature::from_altered_pitches(vec![Pitch::from_name("F#").unwrap()]);
570 assert_eq!(altered.as_key("major").tonic().name(), "C");
571 assert_eq!(
572 pitch_to_sharps(&Pitch::from_name("D").unwrap(), Some("major")).unwrap(),
573 2
574 );
575 assert_eq!(
576 pitch_to_sharps(&Pitch::from_name("D").unwrap(), Some("minor")).unwrap(),
577 -1
578 );
579 assert_eq!(
581 pitch_to_sharps(&Pitch::from_name("D").unwrap(), Some("nonsense")).unwrap(),
582 2
583 );
584 }
585
586 #[test]
587 fn transpose_pitch_from_c_matches_music21() {
588 let cases: [(i32, [&str; 5]); 7] = [
589 (0, ["C4", "E4", "Bb3", "F#5", "G"]),
590 (1, ["G4", "B4", "F3", "C#5", "D"]),
591 (2, ["D4", "F#4", "C3", "G#5", "A"]),
592 (-1, ["F4", "A4", "Eb3", "B5", "C"]),
593 (-3, ["Eb4", "G4", "Db3", "A5", "Bb"]),
594 (7, ["C#4", "E#4", "B3", "F##5", "G#"]),
595 (-7, ["Cb4", "Eb4", "Bbb3", "F5", "Gb"]),
596 ];
597 for (sharps, expected) in cases {
598 let signature = KeySignature::new(sharps);
599 let actual = ["C4", "E4", "Bb3", "F#5", "G"].map(|name| {
600 signature
601 .transpose_pitch_from_c(&Pitch::from_name(name).unwrap())
602 .unwrap()
603 .name_with_octave()
604 });
605 assert_eq!(actual, expected, "{sharps} sharps");
606 }
607 }
608 use super::*;
609
610 #[test]
611 fn altered_pitches_and_transpositions_match_music21() {
612 type Row = (
613 i32,
614 &'static [&'static str],
615 [Option<&'static str>; 3],
616 [i32; 3],
617 &'static str,
618 &'static str,
619 );
620 let cases: [Row; 15] = [
621 (
622 -7,
623 &["Bb", "Eb", "Ab", "Db", "Gb", "Cb", "Fb"],
624 [Some("flat"), Some("flat"), Some("flat")],
625 [-6, -8, -12],
626 "Cb",
627 "Ab",
628 ),
629 (
630 -6,
631 &["Bb", "Eb", "Ab", "Db", "Gb", "Cb"],
632 [None, Some("flat"), Some("flat")],
633 [-5, -7, -11],
634 "Gb",
635 "Eb",
636 ),
637 (
638 -5,
639 &["Bb", "Eb", "Ab", "Db", "Gb"],
640 [None, Some("flat"), None],
641 [-4, -6, -10],
642 "Db",
643 "Bb",
644 ),
645 (
646 -4,
647 &["Bb", "Eb", "Ab", "Db"],
648 [None, Some("flat"), None],
649 [-3, -5, -9],
650 "Ab",
651 "F",
652 ),
653 (
654 -3,
655 &["Bb", "Eb", "Ab"],
656 [None, Some("flat"), None],
657 [-2, -4, -8],
658 "Eb",
659 "C",
660 ),
661 (
662 -2,
663 &["Bb", "Eb"],
664 [None, Some("flat"), None],
665 [-1, -3, -7],
666 "Bb",
667 "G",
668 ),
669 (
670 -1,
671 &["Bb"],
672 [None, Some("flat"), None],
673 [0, -2, -6],
674 "F",
675 "D",
676 ),
677 (0, &[], [None, None, None], [1, -1, -5], "C", "A"),
678 (
679 1,
680 &["F#"],
681 [Some("sharp"), None, None],
682 [2, 0, -4],
683 "G",
684 "E",
685 ),
686 (
687 2,
688 &["F#", "C#"],
689 [Some("sharp"), None, Some("sharp")],
690 [3, 1, -3],
691 "D",
692 "B",
693 ),
694 (
695 3,
696 &["F#", "C#", "G#"],
697 [Some("sharp"), None, Some("sharp")],
698 [4, 2, -2],
699 "A",
700 "F#",
701 ),
702 (
703 4,
704 &["F#", "C#", "G#", "D#"],
705 [Some("sharp"), None, Some("sharp")],
706 [5, 3, -1],
707 "E",
708 "C#",
709 ),
710 (
711 5,
712 &["F#", "C#", "G#", "D#", "A#"],
713 [Some("sharp"), None, Some("sharp")],
714 [6, 4, 0],
715 "B",
716 "G#",
717 ),
718 (
719 6,
720 &["F#", "C#", "G#", "D#", "A#", "E#"],
721 [Some("sharp"), None, Some("sharp")],
722 [7, 5, 1],
723 "F#",
724 "D#",
725 ),
726 (
727 7,
728 &["F#", "C#", "G#", "D#", "A#", "E#", "B#"],
729 [Some("sharp"), Some("sharp"), Some("sharp")],
730 [8, 6, 2],
731 "C#",
732 "A#",
733 ),
734 ];
735 for (sharps, altered, by_step, transposed, major, minor) in cases {
736 let signature = KeySignature::new(sharps);
737 let names = signature
738 .altered_pitches()
739 .unwrap()
740 .iter()
741 .map(Pitch::name)
742 .collect::<Vec<_>>();
743 assert_eq!(names, altered, "{sharps} altered");
744 for (step, expected) in ['F', 'B', 'C'].into_iter().zip(by_step) {
745 let accidental = signature.accidental_by_step(step).unwrap();
746 assert_eq!(
747 accidental.as_ref().map(Accidental::name),
748 expected,
749 "{sharps} {step}"
750 );
751 }
752 for (name, expected) in ["P5", "-P5", "m2"].into_iter().zip(transposed) {
753 let moved = signature
754 .transpose(&Interval::from_name(name).unwrap())
755 .unwrap();
756 assert_eq!(moved.sharps(), expected, "{sharps} by {name}");
757 }
758 assert_eq!(signature.scale("major").unwrap().tonic().name(), major);
759 assert_eq!(signature.scale("minor").unwrap().tonic().name(), minor);
760 }
761 assert!(KeySignature::new(0).scale("dorian").is_err());
762 assert!(KeySignature::new(0).accidental_by_step('H').is_err());
763 }
764 #[test]
765 fn keysignature_as_key_major_minor() {
766 let ks = KeySignature::new(2);
767 assert_eq!(ks.as_key("major").tonic().name(), "D");
768 assert_eq!(ks.as_key("minor").tonic().name(), "B");
769 }
770
771 #[test]
772 fn keysignature_mode_inference_from_tonic() {
773 let ks = KeySignature::new(0);
774 let key = ks.try_as_key(None, Some("D")).unwrap();
775 assert_eq!(key.mode(), "dorian");
776 assert_eq!(key.tonic().name(), "D");
777 }
778
779 #[test]
780 fn sharps_to_pitch_roundtrip() {
781 let f_sharp = sharps_to_pitch(6).unwrap();
782 assert_eq!(f_sharp.name(), "F#");
783 let b_flat = sharps_to_pitch(-2).unwrap();
784 assert_eq!(b_flat.name(), "Bb");
785 assert_eq!(sharps_to_pitch(-7).unwrap().name(), "Cb");
786 assert_eq!(sharps_to_pitch(7).unwrap().name(), "C#");
787 assert_eq!(KeySignature::new(-7).as_key("major").tonic().name(), "Cb");
788 }
789
790 #[test]
791 fn a_count_of_sharps_past_the_limit_is_refused() {
792 for sharps in [MOST_SHARPS + 1, IntegerType::MAX, IntegerType::MIN] {
793 let signature = KeySignature::new(sharps);
794 assert!(sharps_to_pitch(sharps).is_err(), "{sharps}");
795 assert!(signature.altered_pitches().is_err(), "{sharps}");
796 assert!(
797 signature.try_as_key(Some("minor"), None).is_err(),
798 "{sharps}"
799 );
800 let c = Pitch::from_name("C4").unwrap();
801 assert!(signature.transpose_pitch_from_c(&c).is_err(), "{sharps}");
802 }
803 assert!(sharps_to_pitch(MOST_SHARPS).is_ok());
804 }
805}