Skip to main content

music21/
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.
81const 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
300/// The diatonic note number of the treble staff's middle line, B4: where a
301/// clef that places no pitches reads a stem from.
302const TREBLE_MIDDLE_LINE: IntegerType = 35;
303
304impl ClefKind {
305    /// Every kind, in the order music21's table lists them.
306    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    /// music21's class name for the kind: `"TrebleClef"`.
341    pub fn class_name(self) -> &'static str {
342        self.row().class
343    }
344
345    /// The kind music21 names by that class.
346    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    /// The classes above this one, nearest first, up to `Clef`.
354    pub fn parents(self) -> &'static [&'static str] {
355        self.row().parents
356    }
357}
358
359/// A clef: its sign, the line it sits on, the octaves it transposes and the
360/// note written on its lowest line.
361///
362/// ```
363/// use music21::clef::{Clef, ClefKind};
364///
365/// let alto = Clef::from_string("C3", 0)?;
366/// assert_eq!(alto.kind(), ClefKind::AltoClef);
367/// assert_eq!(alto.lowest_line(), Some(25));
368/// assert_eq!(alto.name(), "alto");
369/// # Ok::<(), music21::Error>(())
370/// ```
371#[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    /// music21's bare `Clef`, which says nothing.
386    fn default() -> Self {
387        Self::of_kind(ClefKind::Clef)
388    }
389}
390
391impl Clef {
392    /// A clef as music21's class for `kind` starts out.
393    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    /// The colour the clef is drawn in, if one was said. Two clefs
406    /// differing only in colour are equal.
407    pub fn color(&self) -> Option<&str> {
408        self.color.0.as_deref()
409    }
410
411    /// Sets the colour the clef is drawn in.
412    pub fn set_color(&mut self, color: Option<String>) {
413        self.color.0 = color;
414    }
415
416    /// The treble clef.
417    pub fn treble() -> Self {
418        Self::of_kind(ClefKind::TrebleClef)
419    }
420
421    /// The bass clef.
422    pub fn bass() -> Self {
423        Self::of_kind(ClefKind::BassClef)
424    }
425
426    /// The kind of clef this started as.
427    pub fn kind(&self) -> ClefKind {
428        self.kind
429    }
430
431    /// Whether this is a clef of that music21 class or one below it:
432    /// `is_a("GClef")` holds for a treble clef.
433    pub fn is_a(&self, class: &str) -> bool {
434        self.kind.class_name() == class || self.kind.parents().contains(&class)
435    }
436
437    /// Whether the clef places pitches, as every one below music21's
438    /// `PitchClef` does.
439    pub fn places_pitches(&self) -> bool {
440        self.is_a("PitchClef")
441    }
442
443    /// music21's `name`: the class name without `Clef`, first letter lower
444    /// case, so a treble clef an octave down is `"treble8vb"`.
445    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    /// The sign: `G`, `F`, `C`, `TAB`, `percussion`, `none` or `jianpu`.
455    pub fn sign(&self) -> Option<&str> {
456        self.sign.as_deref()
457    }
458
459    /// Changes the sign.
460    pub fn set_sign(&mut self, sign: Option<String>) {
461        self.sign = sign;
462    }
463
464    /// The line the sign sits on, counted from the bottom.
465    pub fn line(&self) -> Option<u8> {
466        self.line
467    }
468
469    /// Changes the line.
470    pub fn set_line(&mut self, line: Option<u8>) {
471        self.line = line;
472    }
473
474    /// How many octaves the clef transposes what is written: music21's
475    /// `octaveChange`.
476    pub fn octave_change(&self) -> IntegerType {
477        self.octave_change
478    }
479
480    /// Changes how many octaves the clef transposes. On a clef that places
481    /// pitches this moves its lowest line by the octaves it changed by, as
482    /// music21's does; setting the lowest line moves nothing.
483    ///
484    /// ```
485    /// use music21::clef::Clef;
486    ///
487    /// let mut treble = Clef::treble();
488    /// treble.set_octave_change(1);
489    /// assert_eq!(treble.lowest_line(), Some(38));
490    /// ```
491    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    /// The diatonic note number of the note on the lowest line: music21's
502    /// `lowestLine`.
503    pub fn lowest_line(&self) -> Option<IntegerType> {
504        self.lowest_line
505    }
506
507    /// Changes the note on the lowest line.
508    pub fn set_lowest_line(&mut self, lowest_line: Option<IntegerType>) {
509        self.lowest_line = lowest_line;
510    }
511
512    /// Which way a stem goes for these pitches under this clef: music21's
513    /// `getStemDirectionForPitches`. Each pitch counts by how far it sits
514    /// from the middle line; with `first_last_only`, as in a beamed group,
515    /// only the first and last count, and with `extreme_pitch_only` only the
516    /// lowest and the highest. Guitar tablature's stems always go down.
517    ///
518    /// ```
519    /// use music21::{clef::Clef, Pitch, StemDirection};
520    ///
521    /// let pitches: Vec<Pitch> = ["C3", "B3", "C3"]
522    ///     .iter()
523    ///     .map(|name| Pitch::from_name(name))
524    ///     .collect::<Result<_, _>>()?;
525    /// let bass = Clef::bass();
526    /// assert_eq!(bass.stem_direction_for_pitches(&pitches, true, false)?, StemDirection::Up);
527    /// assert_eq!(bass.stem_direction_for_pitches(&pitches, false, false)?, StemDirection::Down);
528    /// # Ok::<(), music21::Error>(())
529    /// ```
530    ///
531    /// # Errors
532    ///
533    /// No pitches at all.
534    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    /// The clef a string like `"G2"` or `"F4"` names: music21's
571    /// `clefFromString`. `tab`, `percussion`, `none` and `jianpu` name those
572    /// clefs, a sign alone takes its usual line, a longer string may be any
573    /// clef's class name with or without `Clef`, and `octave_shift`
574    /// transposes the result, giving the octave treble and bass clefs their
575    /// own kinds. Case does not matter.
576    ///
577    /// # Errors
578    ///
579    /// A string that names no clef, or a line outside one to five.
580    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    /// The clef that best fits these pitches: music21's `bestClef` over the
691    /// pitches of the notes and chords it finds. Pitches above A4 and below
692    /// F3 count a little further out than they are, and with
693    /// `allow_treble_8vb` notes around middle C may take the treble clef an
694    /// octave down.
695    ///
696    /// ```
697    /// use music21::{clef::{Clef, ClefKind}, Pitch};
698    ///
699    /// let d4 = [Pitch::from_name("D4")?];
700    /// assert_eq!(Clef::best_for(&d4, false).kind(), ClefKind::TrebleClef);
701    /// assert_eq!(Clef::best_for(&d4, true).kind(), ClefKind::Treble8vbClef);
702    /// # Ok::<(), music21::Error>(())
703    /// ```
704    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    /// A clef moved more octaves than its lines can be counted in keeps
750    /// its lowest line as far off as an integer holds.
751    #[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    /// Every answer here is music21's own, from its clef docstrings.
762    #[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}