1use crate::defaults::IntegerType;
10use crate::error::{Error, Result};
11use crate::notation::StemDirection;
12use crate::pitch::Pitch;
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17pub enum ClefKind {
18 Clef,
20 NoClef,
22 PercussionClef,
24 PitchClef,
26 CClef,
28 FClef,
30 GClef,
32 JianpuClef,
34 TabClef,
36 AltoClef,
38 Bass8vaClef,
40 Bass8vbClef,
42 BassClef,
44 CBaritoneClef,
46 FBaritoneClef,
48 FrenchViolinClef,
50 GSopranoClef,
52 MezzoSopranoClef,
54 SopranoClef,
56 SubBassClef,
58 TenorClef,
60 TrebleClef,
62 Treble8vaClef,
64 Treble8vbClef,
66}
67
68struct KindRow {
70 kind: ClefKind,
71 class: &'static str,
72 parents: &'static [&'static str],
74 sign: Option<&'static str>,
75 line: Option<u8>,
76 octave_change: IntegerType,
77 lowest_line: Option<IntegerType>,
78}
79
80const KINDS: [KindRow; 24] = [
82 KindRow {
83 kind: ClefKind::Clef,
84 class: "Clef",
85 parents: &[],
86 sign: None,
87 line: None,
88 octave_change: 0,
89 lowest_line: None,
90 },
91 KindRow {
92 kind: ClefKind::NoClef,
93 class: "NoClef",
94 parents: &["Clef"],
95 sign: Some("none"),
96 line: None,
97 octave_change: 0,
98 lowest_line: None,
99 },
100 KindRow {
101 kind: ClefKind::PercussionClef,
102 class: "PercussionClef",
103 parents: &["Clef"],
104 sign: Some("percussion"),
105 line: None,
106 octave_change: 0,
107 lowest_line: Some(31),
108 },
109 KindRow {
110 kind: ClefKind::PitchClef,
111 class: "PitchClef",
112 parents: &["Clef"],
113 sign: None,
114 line: None,
115 octave_change: 0,
116 lowest_line: Some(31),
117 },
118 KindRow {
119 kind: ClefKind::CClef,
120 class: "CClef",
121 parents: &["PitchClef", "Clef"],
122 sign: Some("C"),
123 line: None,
124 octave_change: 0,
125 lowest_line: Some(31),
126 },
127 KindRow {
128 kind: ClefKind::FClef,
129 class: "FClef",
130 parents: &["PitchClef", "Clef"],
131 sign: Some("F"),
132 line: None,
133 octave_change: 0,
134 lowest_line: Some(31),
135 },
136 KindRow {
137 kind: ClefKind::GClef,
138 class: "GClef",
139 parents: &["PitchClef", "Clef"],
140 sign: Some("G"),
141 line: None,
142 octave_change: 0,
143 lowest_line: Some(31),
144 },
145 KindRow {
146 kind: ClefKind::JianpuClef,
147 class: "JianpuClef",
148 parents: &["NoClef", "Clef"],
149 sign: Some("jianpu"),
150 line: None,
151 octave_change: 0,
152 lowest_line: None,
153 },
154 KindRow {
155 kind: ClefKind::TabClef,
156 class: "TabClef",
157 parents: &["PitchClef", "Clef"],
158 sign: Some("TAB"),
159 line: Some(5),
160 octave_change: 0,
161 lowest_line: Some(31),
162 },
163 KindRow {
164 kind: ClefKind::AltoClef,
165 class: "AltoClef",
166 parents: &["CClef", "PitchClef", "Clef"],
167 sign: Some("C"),
168 line: Some(3),
169 octave_change: 0,
170 lowest_line: Some(25),
171 },
172 KindRow {
173 kind: ClefKind::Bass8vaClef,
174 class: "Bass8vaClef",
175 parents: &["FClef", "PitchClef", "Clef"],
176 sign: Some("F"),
177 line: Some(4),
178 octave_change: 1,
179 lowest_line: Some(26),
180 },
181 KindRow {
182 kind: ClefKind::Bass8vbClef,
183 class: "Bass8vbClef",
184 parents: &["FClef", "PitchClef", "Clef"],
185 sign: Some("F"),
186 line: Some(4),
187 octave_change: -1,
188 lowest_line: Some(12),
189 },
190 KindRow {
191 kind: ClefKind::BassClef,
192 class: "BassClef",
193 parents: &["FClef", "PitchClef", "Clef"],
194 sign: Some("F"),
195 line: Some(4),
196 octave_change: 0,
197 lowest_line: Some(19),
198 },
199 KindRow {
200 kind: ClefKind::CBaritoneClef,
201 class: "CBaritoneClef",
202 parents: &["CClef", "PitchClef", "Clef"],
203 sign: Some("C"),
204 line: Some(5),
205 octave_change: 0,
206 lowest_line: Some(21),
207 },
208 KindRow {
209 kind: ClefKind::FBaritoneClef,
210 class: "FBaritoneClef",
211 parents: &["FClef", "PitchClef", "Clef"],
212 sign: Some("F"),
213 line: Some(3),
214 octave_change: 0,
215 lowest_line: Some(21),
216 },
217 KindRow {
218 kind: ClefKind::FrenchViolinClef,
219 class: "FrenchViolinClef",
220 parents: &["GClef", "PitchClef", "Clef"],
221 sign: Some("G"),
222 line: Some(1),
223 octave_change: 0,
224 lowest_line: Some(33),
225 },
226 KindRow {
227 kind: ClefKind::GSopranoClef,
228 class: "GSopranoClef",
229 parents: &["GClef", "PitchClef", "Clef"],
230 sign: Some("G"),
231 line: Some(3),
232 octave_change: 0,
233 lowest_line: Some(29),
234 },
235 KindRow {
236 kind: ClefKind::MezzoSopranoClef,
237 class: "MezzoSopranoClef",
238 parents: &["CClef", "PitchClef", "Clef"],
239 sign: Some("C"),
240 line: Some(2),
241 octave_change: 0,
242 lowest_line: Some(27),
243 },
244 KindRow {
245 kind: ClefKind::SopranoClef,
246 class: "SopranoClef",
247 parents: &["CClef", "PitchClef", "Clef"],
248 sign: Some("C"),
249 line: Some(1),
250 octave_change: 0,
251 lowest_line: Some(29),
252 },
253 KindRow {
254 kind: ClefKind::SubBassClef,
255 class: "SubBassClef",
256 parents: &["FClef", "PitchClef", "Clef"],
257 sign: Some("F"),
258 line: Some(5),
259 octave_change: 0,
260 lowest_line: Some(17),
261 },
262 KindRow {
263 kind: ClefKind::TenorClef,
264 class: "TenorClef",
265 parents: &["CClef", "PitchClef", "Clef"],
266 sign: Some("C"),
267 line: Some(4),
268 octave_change: 0,
269 lowest_line: Some(23),
270 },
271 KindRow {
272 kind: ClefKind::TrebleClef,
273 class: "TrebleClef",
274 parents: &["GClef", "PitchClef", "Clef"],
275 sign: Some("G"),
276 line: Some(2),
277 octave_change: 0,
278 lowest_line: Some(31),
279 },
280 KindRow {
281 kind: ClefKind::Treble8vaClef,
282 class: "Treble8vaClef",
283 parents: &["GClef", "PitchClef", "Clef"],
284 sign: Some("G"),
285 line: Some(2),
286 octave_change: 1,
287 lowest_line: Some(38),
288 },
289 KindRow {
290 kind: ClefKind::Treble8vbClef,
291 class: "Treble8vbClef",
292 parents: &["GClef", "PitchClef", "Clef"],
293 sign: Some("G"),
294 line: Some(2),
295 octave_change: -1,
296 lowest_line: Some(24),
297 },
298];
299
300const TREBLE_MIDDLE_LINE: IntegerType = 35;
303
304impl ClefKind {
305 pub const ALL: [ClefKind; 24] = [
307 ClefKind::Clef,
308 ClefKind::NoClef,
309 ClefKind::PercussionClef,
310 ClefKind::PitchClef,
311 ClefKind::CClef,
312 ClefKind::FClef,
313 ClefKind::GClef,
314 ClefKind::JianpuClef,
315 ClefKind::TabClef,
316 ClefKind::AltoClef,
317 ClefKind::Bass8vaClef,
318 ClefKind::Bass8vbClef,
319 ClefKind::BassClef,
320 ClefKind::CBaritoneClef,
321 ClefKind::FBaritoneClef,
322 ClefKind::FrenchViolinClef,
323 ClefKind::GSopranoClef,
324 ClefKind::MezzoSopranoClef,
325 ClefKind::SopranoClef,
326 ClefKind::SubBassClef,
327 ClefKind::TenorClef,
328 ClefKind::TrebleClef,
329 ClefKind::Treble8vaClef,
330 ClefKind::Treble8vbClef,
331 ];
332
333 fn row(self) -> &'static KindRow {
334 KINDS
335 .iter()
336 .find(|row| row.kind == self)
337 .expect("every kind has a row")
338 }
339
340 pub fn class_name(self) -> &'static str {
342 self.row().class
343 }
344
345 pub fn from_class_name(class: &str) -> Option<Self> {
347 KINDS
348 .iter()
349 .find(|row| row.class == class)
350 .map(|row| row.kind)
351 }
352
353 pub fn parents(self) -> &'static [&'static str] {
355 self.row().parents
356 }
357}
358
359#[derive(Clone, Debug, PartialEq)]
372#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
373#[must_use]
374pub struct Clef {
375 kind: ClefKind,
376 sign: Option<String>,
377 line: Option<u8>,
378 octave_change: IntegerType,
379 lowest_line: Option<IntegerType>,
380 #[cfg_attr(feature = "serde", serde(default))]
381 color: crate::display::Color,
382}
383
384impl Default for Clef {
385 fn default() -> Self {
387 Self::of_kind(ClefKind::Clef)
388 }
389}
390
391impl Clef {
392 pub fn of_kind(kind: ClefKind) -> Self {
394 let row = kind.row();
395 Self {
396 kind,
397 sign: row.sign.map(str::to_string),
398 line: row.line,
399 octave_change: row.octave_change,
400 lowest_line: row.lowest_line,
401 color: crate::display::Drawn(None),
402 }
403 }
404
405 pub fn color(&self) -> Option<&str> {
408 self.color.0.as_deref()
409 }
410
411 pub fn set_color(&mut self, color: Option<String>) {
413 self.color.0 = color;
414 }
415
416 pub fn treble() -> Self {
418 Self::of_kind(ClefKind::TrebleClef)
419 }
420
421 pub fn bass() -> Self {
423 Self::of_kind(ClefKind::BassClef)
424 }
425
426 pub fn kind(&self) -> ClefKind {
428 self.kind
429 }
430
431 pub fn is_a(&self, class: &str) -> bool {
434 self.kind.class_name() == class || self.kind.parents().contains(&class)
435 }
436
437 pub fn places_pitches(&self) -> bool {
440 self.is_a("PitchClef")
441 }
442
443 pub fn name(&self) -> String {
446 let class = self.kind.class_name().replace("Clef", "");
447 let mut letters = class.chars();
448 match letters.next() {
449 Some(first) => first.to_lowercase().chain(letters).collect(),
450 None => String::new(),
451 }
452 }
453
454 pub fn sign(&self) -> Option<&str> {
456 self.sign.as_deref()
457 }
458
459 pub fn set_sign(&mut self, sign: Option<String>) {
461 self.sign = sign;
462 }
463
464 pub fn line(&self) -> Option<u8> {
466 self.line
467 }
468
469 pub fn set_line(&mut self, line: Option<u8>) {
471 self.line = line;
472 }
473
474 pub fn octave_change(&self) -> IntegerType {
477 self.octave_change
478 }
479
480 pub fn set_octave_change(&mut self, octave_change: IntegerType) {
492 if self.places_pitches()
493 && let Some(lowest) = &mut self.lowest_line
494 {
495 let moved = octave_change.saturating_sub(self.octave_change);
496 *lowest = lowest.saturating_add(moved.saturating_mul(7));
497 }
498 self.octave_change = octave_change;
499 }
500
501 pub fn lowest_line(&self) -> Option<IntegerType> {
504 self.lowest_line
505 }
506
507 pub fn set_lowest_line(&mut self, lowest_line: Option<IntegerType>) {
509 self.lowest_line = lowest_line;
510 }
511
512 pub fn stem_direction_for_pitches(
535 &self,
536 pitches: &[Pitch],
537 first_last_only: bool,
538 extreme_pitch_only: bool,
539 ) -> Result<StemDirection> {
540 if self.is_a("TabClef") {
541 return Ok(StemDirection::Down);
542 }
543 if pitches.is_empty() {
544 return Err(Error::Value(
545 "getStemDirectionForPitches cannot operate on an empty list".to_string(),
546 ));
547 }
548 let numbers: Vec<IntegerType> = pitches.iter().map(Pitch::diatonic_note_number).collect();
549 let relevant: Vec<IntegerType> = if extreme_pitch_only {
550 let lowest = numbers.iter().copied().min().unwrap_or_default();
551 let highest = numbers.iter().copied().max().unwrap_or_default();
552 vec![lowest, highest]
553 } else if first_last_only && numbers.len() > 1 {
554 vec![numbers[0], numbers[numbers.len() - 1]]
555 } else {
556 numbers
557 };
558 let middle = match self.lowest_line {
559 Some(lowest) if self.places_pitches() || self.is_a("PercussionClef") => lowest + 4,
560 _ => TREBLE_MIDDLE_LINE,
561 };
562 let distance: IntegerType = relevant.iter().map(|number| number - middle).sum();
563 Ok(if distance >= 0 {
564 StemDirection::Down
565 } else {
566 StemDirection::Up
567 })
568 }
569
570 pub fn from_string(text: &str, octave_shift: IntegerType) -> Result<Self> {
581 let written = text.trim();
582 let lower = written.to_lowercase();
583 match lower.as_str() {
584 "tab" => return Ok(Self::of_kind(ClefKind::TabClef)),
585 "percussion" => return Ok(Self::of_kind(ClefKind::PercussionClef)),
586 "none" => return Ok(Self::of_kind(ClefKind::NoClef)),
587 "jianpu" => return Ok(Self::of_kind(ClefKind::JianpuClef)),
588 _ => {}
589 }
590 let mut characters = written.chars();
591 let (sign, line) = match written.chars().count() {
592 0 => {
593 return Err(Error::Clef(
594 "Entry has clef info but no clef specified".to_string(),
595 ));
596 }
597 1 => {
598 let sign = characters.next().map(|sign| sign.to_ascii_uppercase());
599 let line = match sign {
600 Some('G') => Some(2),
601 Some('F') => Some(4),
602 Some('C') => Some(3),
603 _ => None,
604 };
605 (sign, line)
606 }
607 2 => {
608 let sign = characters.next().map(|sign| sign.to_ascii_uppercase());
609 let line = characters
610 .next()
611 .and_then(|line| line.to_digit(10))
612 .and_then(|line| u8::try_from(line).ok());
613 if line.is_none() {
614 return Err(Error::Clef(format!(
615 "cannot read {written} as clef str, should be G2, F4, etc."
616 )));
617 }
618 (sign, line)
619 }
620 _ => {
621 return KINDS
622 .iter()
623 .find(|row| {
624 let class = row.class.to_lowercase();
625 class == lower || class == format!("{lower}clef")
626 })
627 .map(|row| Self::of_kind(row.kind))
628 .ok_or_else(|| Error::Clef(format!("Could not find clef {written}")));
629 }
630 };
631 if octave_shift != 0 {
632 let shifted = match (sign, line, octave_shift) {
633 (Some('G'), Some(2), -1) => Some(ClefKind::Treble8vbClef),
634 (Some('G'), Some(2), 1) => Some(ClefKind::Treble8vaClef),
635 (Some('F'), Some(4), -1) => Some(ClefKind::Bass8vbClef),
636 (Some('F'), Some(4), 1) => Some(ClefKind::Bass8vaClef),
637 _ => None,
638 };
639 if let Some(kind) = shifted {
640 return Ok(Self::of_kind(kind));
641 }
642 }
643 let (Some(sign), Some(line)) = (sign, line) else {
644 return Err(Error::Clef(format!(
645 "cannot read {written} as clef str, should be G2, F4, etc."
646 )));
647 };
648 if !(1..=5).contains(&line) {
649 return Err(Error::Clef(format!(
650 "line number (second character) must be 1-5; do not use this function for clefs on special staves such as '{written}'"
651 )));
652 }
653 let named = match (sign, line) {
654 ('G', 1) => Some(ClefKind::FrenchViolinClef),
655 ('G', 2) => Some(ClefKind::TrebleClef),
656 ('G', 3) => Some(ClefKind::GSopranoClef),
657 ('C', 1) => Some(ClefKind::SopranoClef),
658 ('C', 2) => Some(ClefKind::MezzoSopranoClef),
659 ('C', 3) => Some(ClefKind::AltoClef),
660 ('C', 4) => Some(ClefKind::TenorClef),
661 ('C', 5) => Some(ClefKind::CBaritoneClef),
662 ('F', 3) => Some(ClefKind::FBaritoneClef),
663 ('F', 4) => Some(ClefKind::BassClef),
664 ('F', 5) => Some(ClefKind::SubBassClef),
665 _ => None,
666 };
667 let mut clef = match named {
668 Some(kind) => Self::of_kind(kind),
669 None => {
670 let family = match sign {
671 'G' => ClefKind::GClef,
672 'F' => ClefKind::FClef,
673 'C' => ClefKind::CClef,
674 _ => ClefKind::PitchClef,
675 };
676 let mut clef = Self::of_kind(family);
677 if family == ClefKind::PitchClef {
678 clef.sign = Some(sign.to_string());
679 }
680 clef.line = Some(line);
681 clef
682 }
683 };
684 if octave_shift != 0 {
685 clef.set_octave_change(octave_shift);
686 }
687 Ok(clef)
688 }
689
690 pub fn best_for(pitches: &[Pitch], allow_treble_8vb: bool) -> Self {
705 let average = if pitches.is_empty() {
706 29.0
707 } else {
708 let total: IntegerType = pitches
709 .iter()
710 .map(|pitch| {
711 let height = pitch.diatonic_note_number();
712 if height > 33 {
713 height + 3
714 } else if height < 24 {
715 height - 3
716 } else {
717 height
718 }
719 })
720 .sum();
721 f64::from(total) / pitches.len() as f64
722 };
723 let kind = if average > 49.0 {
724 ClefKind::Treble8vaClef
725 } else if (allow_treble_8vb && average > 32.0) || (!allow_treble_8vb && average > 28.0) {
726 ClefKind::TrebleClef
727 } else if allow_treble_8vb && average > 26.0 {
728 ClefKind::Treble8vbClef
729 } else if average > 10.0 {
730 ClefKind::BassClef
731 } else {
732 ClefKind::Bass8vbClef
733 };
734 Self::of_kind(kind)
735 }
736}
737
738#[cfg(test)]
739mod tests {
740 use super::*;
741
742 fn pitches(names: &[&str]) -> Vec<Pitch> {
743 names
744 .iter()
745 .map(|name| Pitch::from_name(name).unwrap())
746 .collect()
747 }
748
749 #[test]
752 fn a_clef_moved_past_the_last_octave_stops_there() {
753 for octaves in [IntegerType::MAX, IntegerType::MIN] {
754 let mut treble = Clef::treble();
755 treble.set_octave_change(octaves);
756 let lowest = treble.lowest_line().unwrap();
757 assert_eq!(lowest.signum(), octaves.signum(), "{octaves}");
758 }
759 }
760
761 #[test]
763 fn a_clef_answers_what_music21_s_answers() {
764 assert_eq!(Clef::treble().lowest_line(), Some(31));
765 assert_eq!(Clef::of_kind(ClefKind::Treble8vbClef).octave_change(), -1);
766 assert_eq!(Clef::of_kind(ClefKind::Treble8vbClef).name(), "treble8vb");
767 assert_eq!(
768 Clef::of_kind(ClefKind::MezzoSopranoClef).name(),
769 "mezzoSoprano"
770 );
771 assert_eq!(Clef::default().name(), "");
772
773 let mut treble = Clef::treble();
774 treble.set_octave_change(1);
775 assert_eq!(treble.lowest_line(), Some(38));
776 treble.set_octave_change(-1);
777 assert_eq!(treble.lowest_line(), Some(24));
778
779 let bass = Clef::bass();
780 assert_eq!(
781 bass.stem_direction_for_pitches(&pitches(&["C3", "B3", "C3"]), true, false)
782 .unwrap(),
783 StemDirection::Up
784 );
785 assert_eq!(
786 bass.stem_direction_for_pitches(&pitches(&["C3", "B3", "C3"]), false, false)
787 .unwrap(),
788 StemDirection::Down
789 );
790 assert!(bass.stem_direction_for_pitches(&[], true, false).is_err());
791 }
792
793 #[test]
794 fn a_clef_is_read_from_a_string_as_music21_reads_one() {
795 assert_eq!(
796 Clef::from_string("G2", 0).unwrap().kind(),
797 ClefKind::TrebleClef
798 );
799 assert_eq!(
800 Clef::from_string("f4", 0).unwrap().kind(),
801 ClefKind::BassClef
802 );
803 assert_eq!(
804 Clef::from_string("C", 0).unwrap().kind(),
805 ClefKind::AltoClef
806 );
807 assert_eq!(
808 Clef::from_string("tab", 0).unwrap().kind(),
809 ClefKind::TabClef
810 );
811 assert_eq!(
812 Clef::from_string("G2", -1).unwrap().kind(),
813 ClefKind::Treble8vbClef
814 );
815 assert_eq!(
816 Clef::from_string("mezzoSoprano", 0).unwrap().kind(),
817 ClefKind::MezzoSopranoClef
818 );
819 assert_eq!(
820 Clef::from_string("tenorclef", 0).unwrap().kind(),
821 ClefKind::TenorClef
822 );
823 let g5 = Clef::from_string("G5", 0).unwrap();
824 assert_eq!((g5.kind(), g5.line()), (ClefKind::GClef, Some(5)));
825 assert!(Clef::from_string("F6", 0).is_err());
826 assert!(Clef::from_string("", 0).is_err());
827 assert!(Clef::from_string("nonsense", 0).is_err());
828 }
829
830 #[test]
831 fn the_best_clef_is_music21_s() {
832 assert_eq!(
833 Clef::best_for(&pitches(&["D7"]), false).kind(),
834 ClefKind::Treble8vaClef
835 );
836 assert_eq!(
837 Clef::best_for(&pitches(&["C0"]), false).kind(),
838 ClefKind::Bass8vbClef
839 );
840 assert_eq!(Clef::best_for(&[], false).kind(), ClefKind::TrebleClef);
841 assert_eq!(
842 Clef::best_for(&pitches(&["A2", "C3"]), false).kind(),
843 ClefKind::BassClef
844 );
845 }
846}