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