Skip to main content

music21_rs/pitch/
names.rs

1//! What a pitch is called: its German, Italian, French and Spanish names,
2//! its full name, and its name in Unicode.
3
4use super::*;
5
6impl Pitch {
7    /// Returns the German name, where `B` is `H`, `B-` is `B`, sharps add
8    /// `is` and flats add `es` or `s`. Errors on a microtonal accidental.
9    pub fn german(&self) -> Result<String> {
10        let alter = self.whole_alteration("german")?;
11        let mut step = self.step.as_char().to_string();
12        let mut alter = alter;
13        if self.step == StepName::B {
14            if alter == -1 {
15                alter = 0;
16            } else {
17                step = "H".to_string();
18            }
19        }
20        Ok(match alter {
21            0 => step,
22            alter if alter > 0 => step + &"is".repeat(alter as usize),
23            alter => {
24                let first = if matches!(step.as_str(), "C" | "D" | "F" | "G" | "H") {
25                    "es"
26                } else {
27                    "s"
28                };
29                step + first + &"es".repeat(alter.unsigned_abs() as usize - 1)
30            }
31        })
32    }
33
34    /// Returns the Italian solfège name, such as `"do diesis"` or
35    /// `"si doppio bemolle"`. Errors on a microtonal accidental or more than
36    /// four sharps or flats.
37    pub fn italian(&self) -> Result<String> {
38        let alter = self.whole_alteration("italian")?;
39        let solfege = match self.step {
40            StepName::C => "do",
41            StepName::D => "re",
42            StepName::E => "mi",
43            StepName::F => "fa",
44            StepName::G => "sol",
45            StepName::A => "la",
46            StepName::B => "si",
47        };
48        let cardinality = match alter.unsigned_abs() {
49            0 => return Ok(solfege.to_string()),
50            1 => " ",
51            2 => " doppio ",
52            3 => " triplo ",
53            4 => " quadruplo ",
54            _ => {
55                return Err(Error::Pitch(format!(
56                    "entirely too many accidentals for an Italian name: {self}"
57                )));
58            }
59        };
60        let kind = if alter > 0 { "diesis" } else { "bemolle" };
61        Ok(format!("{solfege}{cardinality}{kind}"))
62    }
63
64    /// Returns the French solfège name, such as `"ré bémol"` or
65    /// `"fa double dièse"`. Errors on a microtonal accidental or more than
66    /// four sharps or flats.
67    pub fn french(&self) -> Result<String> {
68        let alter = self.whole_alteration("french")?;
69        let solfege = match self.step {
70            StepName::D => "ré",
71            other => romance_solfege(other),
72        };
73        let multiplier = match alter.unsigned_abs() {
74            0 => return Ok(solfege.to_string()),
75            1 => "",
76            2 => " double",
77            3 => " triple",
78            4 => " quadruple",
79            _ => {
80                return Err(Error::Pitch(format!(
81                    "entirely too many accidentals for a French name: {self}"
82                )));
83            }
84        };
85        let kind = if alter > 0 { "dièse" } else { "bémol" };
86        Ok(format!("{solfege}{multiplier} {kind}"))
87    }
88
89    /// Returns the Spanish solfège name, such as `"re bemol"` or
90    /// `"fa doble sostenido"`. Errors on a microtonal accidental or more than
91    /// four sharps or flats.
92    pub fn spanish(&self) -> Result<String> {
93        let alter = self.whole_alteration("spanish")?;
94        let solfege = romance_solfege(self.step);
95        let multiplier = match alter.unsigned_abs() {
96            0 => return Ok(solfege.to_string()),
97            1 => "",
98            2 => " doble",
99            3 => " triple",
100            4 => " cuádruple",
101            _ => {
102                return Err(Error::Pitch(format!(
103                    "entirely too many accidentals for a Spanish name: {self}"
104                )));
105            }
106        };
107        let kind = if alter > 0 { "sostenido" } else { "bemol" };
108        Ok(format!("{solfege}{multiplier} {kind}"))
109    }
110
111    /// Returns the name with the accidental as a Unicode symbol, such as
112    /// `"C♯"` or `"G𝄫"`.
113    pub fn unicode_name(&self) -> String {
114        if self.accidental.alter() == 0.0 {
115            return self.step.as_char().to_string();
116        }
117        format!("{}{}", self.step.as_char(), self.accidental.unicode())
118    }
119
120    /// Returns music21's `fullName`: the step, the accidental's full name, the
121    /// octave and any microtone, as in `E-flat in octave 4 (+20c)`.
122    pub fn full_name(&self) -> String {
123        let mut name = self.step.as_char().to_string();
124        // music21 asks whether the pitch carries an accidental object, not
125        // whether that accidental alters anything: a written natural is
126        // named, and a bare `C` — which carries none — is not.
127        if self.has_accidental {
128            name.push('-');
129            name.push_str(self.accidental.full_name());
130        }
131        if let Some(octave) = self.octave {
132            name.push_str(&format!(" in octave {octave}"));
133        }
134        if let Some(microtone) = &self.microtone
135            && microtone.cents() != 0.0
136        {
137            name.push(' ');
138            name.push_str(&microtone.to_string());
139        }
140        name
141    }
142
143    /// Returns the name with its octave and, when it has one that is not
144    /// zero, its microtone: music21's `str(Pitch)`, `A4(+20c)`.
145    pub fn name_with_octave_and_microtone(&self) -> String {
146        match &self.microtone {
147            Some(microtone) if microtone.cents() != 0.0 => {
148                format!("{}{microtone}", self.name_with_octave())
149            }
150            _ => self.name_with_octave(),
151        }
152    }
153
154    /// Returns [`Self::unicode_name`] followed by the octave when one is set.
155    pub fn unicode_name_with_octave(&self) -> String {
156        match self.octave {
157            Some(octave) => format!("{}{octave}", self.unicode_name()),
158            None => self.unicode_name(),
159        }
160    }
161
162    pub(super) fn whole_alteration(&self, language: &str) -> Result<IntegerType> {
163        let alter = self.accidental.alter();
164        if alter.fract() != 0.0 {
165            return Err(Error::Pitch(match language {
166                "german" => {
167                    "Es geht nicht \"german\" zu benutzen mit Microtönen.  Schade!".to_string()
168                }
169                "italian" => "Non si puo usare `italian` con microtoni".to_string(),
170                "french" => {
171                    "On ne peut pas utiliser les microtones avec \"french.\" Quelle Dommage!"
172                        .to_string()
173                }
174                "spanish" => "Unsupported accidental type.".to_string(),
175                other => {
176                    format!("{other} names cannot express the microtonal accidental of {self}")
177                }
178            }));
179        }
180        Ok(alter as IntegerType)
181    }
182}
183
184/// Canonical pitch names for chromatic pitch classes.
185pub const CHROMATIC_PITCH_CLASS_NAMES: [&str; 12] = [
186    "C", "D-", "D", "E-", "E", "F", "F#", "G", "A-", "A", "B-", "B",
187];
188
189/// Returns a canonical pitch name for a chromatic pitch class.
190pub fn pitch_class_name(pitch_class: u8) -> &'static str {
191    CHROMATIC_PITCH_CLASS_NAMES[pitch_class as usize % 12]
192}
193
194pub(super) fn romance_solfege(step: StepName) -> &'static str {
195    match step {
196        StepName::C => "do",
197        StepName::D => "re",
198        StepName::E => "mi",
199        StepName::F => "fa",
200        StepName::G => "sol",
201        StepName::A => "la",
202        StepName::B => "si",
203    }
204}