Skip to main content

music21_rs/
clef.rs

1//! Clefs: music21's `clef` module.
2//!
3//! A clef says which line of the staff a note is written on. music21 has a
4//! class for each clef it knows, and each differs from the others only in
5//! its sign, the line the sign sits on, how many octaves it transposes and
6//! the note on its lowest line, so here a [`Clef`] is those values and a
7//! [`ClefKind`] names the class they started as.
8
9use crate::defaults::IntegerType;
10use crate::error::{Error, Result};
11use crate::notation::StemDirection;
12use crate::pitch::Pitch;
13
14/// One of music21's clef classes.
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17pub enum ClefKind {
18    /// The base class, which says nothing.
19    Clef,
20    /// No clef at all.
21    NoClef,
22    /// A clef for unpitched percussion.
23    PercussionClef,
24    /// Any clef that places pitches.
25    PitchClef,
26    /// A C clef on no particular line.
27    CClef,
28    /// An F clef on no particular line.
29    FClef,
30    /// A G clef on no particular line.
31    GClef,
32    /// The clef of jianpu, numbered notation, which is no clef.
33    JianpuClef,
34    /// A tablature clef.
35    TabClef,
36    /// C clef on the third line.
37    AltoClef,
38    /// F clef on the fourth line, an octave up.
39    Bass8vaClef,
40    /// F clef on the fourth line, an octave down.
41    Bass8vbClef,
42    /// F clef on the fourth line.
43    BassClef,
44    /// C clef on the fifth line.
45    CBaritoneClef,
46    /// F clef on the third line.
47    FBaritoneClef,
48    /// G clef on the first line.
49    FrenchViolinClef,
50    /// G clef on the third line.
51    GSopranoClef,
52    /// C clef on the second line.
53    MezzoSopranoClef,
54    /// C clef on the first line.
55    SopranoClef,
56    /// F clef on the fifth line.
57    SubBassClef,
58    /// C clef on the fourth line.
59    TenorClef,
60    /// G clef on the second line.
61    TrebleClef,
62    /// G clef on the second line, an octave up.
63    Treble8vaClef,
64    /// G clef on the second line, an octave down.
65    Treble8vbClef,
66}
67
68/// What a clef of one kind starts out as.
69struct KindRow {
70    kind: ClefKind,
71    class: &'static str,
72    /// Its ancestors up to `Clef`, nearest first.
73    parents: &'static [&'static str],
74    sign: Option<&'static str>,
75    line: Option<u8>,
76    octave_change: IntegerType,
77    lowest_line: Option<IntegerType>,
78}
79
80// music21's defaults, class by class. Three of its octave clefs set their
81// lowest line to a value that ignores their octave change: Treble8vaClef has
82// 24 where TrebleClef has 31, and Bass8vaClef and Bass8vbClef keep
83// BassClef's 19. They are music21's values and are kept as they are.
84const 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
303/// The diatonic note number of the treble staff's middle line, B4: where a
304/// clef that places no pitches reads a stem from.
305const TREBLE_MIDDLE_LINE: IntegerType = 35;
306
307impl ClefKind {
308    /// Every kind, in the order music21's table lists them.
309    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    /// music21's class name for the kind: `"TrebleClef"`.
344    pub fn class_name(self) -> &'static str {
345        self.row().class
346    }
347
348    /// The kind music21 names by that class.
349    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    /// The classes above this one, nearest first, up to `Clef`.
357    pub fn parents(self) -> &'static [&'static str] {
358        self.row().parents
359    }
360}
361
362/// A clef: its sign, the line it sits on, the octaves it transposes and the
363/// note written on its lowest line.
364///
365/// ```
366/// use music21_rs::clef::{Clef, ClefKind};
367///
368/// let alto = Clef::from_string("C3", 0)?;
369/// assert_eq!(alto.kind(), ClefKind::AltoClef);
370/// assert_eq!(alto.lowest_line(), Some(25));
371/// assert_eq!(alto.name(), "alto");
372/// # Ok::<(), music21_rs::Error>(())
373/// ```
374#[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    /// music21's bare `Clef`, which says nothing.
389    fn default() -> Self {
390        Self::of_kind(ClefKind::Clef)
391    }
392}
393
394impl Clef {
395    /// A clef as music21's class for `kind` starts out.
396    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    /// The colour the clef is drawn in, if one was said. Two clefs
409    /// differing only in colour are equal.
410    pub fn color(&self) -> Option<&str> {
411        self.color.0.as_deref()
412    }
413
414    /// Sets the colour the clef is drawn in.
415    pub fn set_color(&mut self, color: Option<String>) {
416        self.color.0 = color;
417    }
418
419    /// The treble clef.
420    pub fn treble() -> Self {
421        Self::of_kind(ClefKind::TrebleClef)
422    }
423
424    /// The bass clef.
425    pub fn bass() -> Self {
426        Self::of_kind(ClefKind::BassClef)
427    }
428
429    /// The kind of clef this started as.
430    pub fn kind(&self) -> ClefKind {
431        self.kind
432    }
433
434    /// Whether this is a clef of that music21 class or one below it:
435    /// `is_a("GClef")` holds for a treble clef.
436    pub fn is_a(&self, class: &str) -> bool {
437        self.kind.class_name() == class || self.kind.parents().contains(&class)
438    }
439
440    /// Whether the clef places pitches, as every one below music21's
441    /// `PitchClef` does.
442    pub fn places_pitches(&self) -> bool {
443        self.is_a("PitchClef")
444    }
445
446    /// music21's `name`: the class name without `Clef`, first letter lower
447    /// case, so a treble clef an octave down is `"treble8vb"`.
448    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    /// The sign: `G`, `F`, `C`, `TAB`, `percussion`, `none` or `jianpu`.
458    pub fn sign(&self) -> Option<&str> {
459        self.sign.as_deref()
460    }
461
462    /// Changes the sign.
463    pub fn set_sign(&mut self, sign: Option<String>) {
464        self.sign = sign;
465    }
466
467    /// The line the sign sits on, counted from the bottom.
468    pub fn line(&self) -> Option<u8> {
469        self.line
470    }
471
472    /// Changes the line.
473    pub fn set_line(&mut self, line: Option<u8>) {
474        self.line = line;
475    }
476
477    /// How many octaves the clef transposes what is written: music21's
478    /// `octaveChange`.
479    pub fn octave_change(&self) -> IntegerType {
480        self.octave_change
481    }
482
483    /// Changes how many octaves the clef transposes. On a clef that places
484    /// pitches this moves its lowest line by the octaves it changed by, as
485    /// music21's does; setting the lowest line moves nothing.
486    ///
487    /// ```
488    /// use music21_rs::clef::Clef;
489    ///
490    /// let mut treble = Clef::treble();
491    /// treble.set_octave_change(1);
492    /// assert_eq!(treble.lowest_line(), Some(38));
493    /// ```
494    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    /// The diatonic note number of the note on the lowest line: music21's
504    /// `lowestLine`.
505    pub fn lowest_line(&self) -> Option<IntegerType> {
506        self.lowest_line
507    }
508
509    /// Changes the note on the lowest line.
510    pub fn set_lowest_line(&mut self, lowest_line: Option<IntegerType>) {
511        self.lowest_line = lowest_line;
512    }
513
514    /// Which way a stem goes for these pitches under this clef: music21's
515    /// `getStemDirectionForPitches`. Each pitch counts by how far it sits
516    /// from the middle line; with `first_last_only`, as in a beamed group,
517    /// only the first and last count, and with `extreme_pitch_only` only the
518    /// lowest and the highest. Guitar tablature's stems always go down.
519    ///
520    /// ```
521    /// use music21_rs::{clef::Clef, Pitch, StemDirection};
522    ///
523    /// let pitches: Vec<Pitch> = ["C3", "B3", "C3"]
524    ///     .iter()
525    ///     .map(|name| Pitch::from_name(name))
526    ///     .collect::<Result<_, _>>()?;
527    /// let bass = Clef::bass();
528    /// assert_eq!(bass.stem_direction_for_pitches(&pitches, true, false)?, StemDirection::Up);
529    /// assert_eq!(bass.stem_direction_for_pitches(&pitches, false, false)?, StemDirection::Down);
530    /// # Ok::<(), music21_rs::Error>(())
531    /// ```
532    ///
533    /// # Errors
534    ///
535    /// No pitches at all.
536    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    /// The clef a string like `"G2"` or `"F4"` names: music21's
573    /// `clefFromString`. `tab`, `percussion`, `none` and `jianpu` name those
574    /// clefs, a sign alone takes its usual line, a longer string may be any
575    /// clef's class name with or without `Clef`, and `octave_shift`
576    /// transposes the result, giving the octave treble and bass clefs their
577    /// own kinds. Case does not matter.
578    ///
579    /// # Errors
580    ///
581    /// A string that names no clef, or a line outside one to five.
582    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    /// The clef that best fits these pitches: music21's `bestClef` over the
693    /// pitches of the notes and chords it finds. Pitches above A4 and below
694    /// F3 count a little further out than they are, and with
695    /// `allow_treble_8vb` notes around middle C may take the treble clef an
696    /// octave down.
697    ///
698    /// ```
699    /// use music21_rs::{clef::{Clef, ClefKind}, Pitch};
700    ///
701    /// let d4 = [Pitch::from_name("D4")?];
702    /// assert_eq!(Clef::best_for(&d4, false).kind(), ClefKind::TrebleClef);
703    /// assert_eq!(Clef::best_for(&d4, true).kind(), ClefKind::Treble8vbClef);
704    /// # Ok::<(), music21_rs::Error>(())
705    /// ```
706    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    /// Every answer here is music21's own, from its clef docstrings.
752    #[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}