Skip to main content

music21_rs/chordsymbol/
tables.rs

1//! music21's `harmony.CHORD_TYPES`: every kind of chord a lead-sheet symbol
2//! names, with its notation over the root and the abbreviations it is
3//! written with, and the intervals that notation stands for.
4
5use super::*;
6
7/// A chord type from music21's `harmony.CHORD_TYPES` table.
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub struct Music21ChordType {
10    /// music21's kind name, such as `"dominant-seventh"`.
11    pub kind: &'static str,
12    /// Scale-degree notation, such as `"1,3,5,-7"`.
13    pub notation: &'static str,
14    /// music21's first abbreviation for this kind, such as `"7"`, which is
15    /// the one it uses when writing a figure.
16    pub abbreviation: &'static str,
17    /// Every abbreviation music21 accepts for this kind, first one first.
18    pub abbreviations: &'static [&'static str],
19}
20
21/// Returns every chord type this crate knows from music21's harmony tables.
22///
23/// The table matches music21's `harmony.CHORD_TYPES`, and is checked
24/// against it rather than transcribed and hoped for.
25pub fn known_chord_symbol_types() -> &'static [Music21ChordType] {
26    MUSIC21_CHORD_TYPES
27}
28
29pub(super) fn chord_type_named(kind: &str) -> Option<&'static Music21ChordType> {
30    MUSIC21_CHORD_TYPES
31        .iter()
32        .find(|chord_type| chord_type.kind == kind)
33}
34
35/// The other names music21 takes for three of its kinds, as MusicXML writes
36/// them: music21's `CHORD_ALIASES`.
37pub const CHORD_KIND_ALIASES: [(&str, &str); 3] = [
38    ("dominant", "dominant-seventh"),
39    ("major-minor", "minor-major-seventh"),
40    ("half-diminished", "half-diminished-seventh"),
41];
42
43/// The kind an alias stands for, or the kind itself where it is none.
44pub fn resolve_kind_alias(kind: &str) -> &str {
45    CHORD_KIND_ALIASES
46        .iter()
47        .find(|(alias, _)| *alias == kind)
48        .map_or(kind, |(_, target)| target)
49}
50
51/// Every abbreviation music21 accepts for a chord kind: music21's
52/// `getAbbreviationListGivenChordType`, so `dominant-seventh` gives `7` and
53/// `dom7`. `None` for an unknown kind.
54pub fn abbreviations_for_kind(kind: &str) -> Option<&'static [&'static str]> {
55    chord_type_named(kind).map(|chord_type| chord_type.abbreviations)
56}
57
58/// The scale-degree notation of a chord kind: music21's
59/// `getNotationStringGivenChordType`, `1,3,5,-7` for `dominant-seventh`.
60pub fn notation_for_kind(kind: &str) -> Option<&'static str> {
61    chord_type_named(kind).map(|chord_type| chord_type.notation)
62}
63
64/// The abbreviation music21 writes a chord kind with: its
65/// `getCurrentAbbreviationFor`, the first of [`abbreviations_for_kind`].
66pub fn current_abbreviation_for_kind(kind: &str) -> Option<&'static str> {
67    chord_type_named(kind).map(|chord_type| chord_type.abbreviation)
68}
69
70#[derive(Clone, Copy, Debug, Eq, PartialEq)]
71pub(super) struct Music21Degree {
72    degree: u8,
73    semitone: u8,
74}
75
76pub(super) const MUSIC21_CHORD_TYPES: &[Music21ChordType] = &[
77    Music21ChordType {
78        kind: "major",
79        notation: "1,3,5",
80        abbreviation: "",
81        abbreviations: &["", "M", "maj"],
82    },
83    Music21ChordType {
84        kind: "minor",
85        notation: "1,-3,5",
86        abbreviation: "m",
87        abbreviations: &["m", "min"],
88    },
89    Music21ChordType {
90        kind: "augmented",
91        notation: "1,3,#5",
92        abbreviation: "+",
93        abbreviations: &["+", "aug"],
94    },
95    Music21ChordType {
96        kind: "diminished",
97        notation: "1,-3,-5",
98        abbreviation: "dim",
99        abbreviations: &["dim", "o"],
100    },
101    Music21ChordType {
102        kind: "dominant-seventh",
103        notation: "1,3,5,-7",
104        abbreviation: "7",
105        abbreviations: &["7", "dom7"],
106    },
107    Music21ChordType {
108        kind: "major-seventh",
109        notation: "1,3,5,7",
110        abbreviation: "maj7",
111        abbreviations: &["maj7", "M7"],
112    },
113    Music21ChordType {
114        kind: "minor-major-seventh",
115        notation: "1,-3,5,7",
116        abbreviation: "mM7",
117        abbreviations: &["mM7", "m#7", "minmaj7"],
118    },
119    Music21ChordType {
120        kind: "minor-seventh",
121        notation: "1,-3,5,-7",
122        abbreviation: "m7",
123        abbreviations: &["m7", "min7"],
124    },
125    Music21ChordType {
126        kind: "augmented-major-seventh",
127        notation: "1,3,#5,7",
128        abbreviation: "+M7",
129        abbreviations: &["+M7", "augmaj7"],
130    },
131    Music21ChordType {
132        kind: "augmented-seventh",
133        notation: "1,3,#5,-7",
134        abbreviation: "7+",
135        abbreviations: &["7+", "+7", "aug7"],
136    },
137    Music21ChordType {
138        kind: "half-diminished-seventh",
139        notation: "1,-3,-5,-7",
140        abbreviation: "ø7",
141        abbreviations: &["ø7", "m7b5"],
142    },
143    Music21ChordType {
144        kind: "diminished-seventh",
145        notation: "1,-3,-5,--7",
146        abbreviation: "o7",
147        abbreviations: &["o7", "dim7"],
148    },
149    Music21ChordType {
150        kind: "seventh-flat-five",
151        notation: "1,3,-5,-7",
152        abbreviation: "dom7dim5",
153        abbreviations: &["dom7dim5"],
154    },
155    Music21ChordType {
156        kind: "major-sixth",
157        notation: "1,3,5,6",
158        abbreviation: "6",
159        abbreviations: &["6"],
160    },
161    Music21ChordType {
162        kind: "minor-sixth",
163        notation: "1,-3,5,6",
164        abbreviation: "m6",
165        abbreviations: &["m6", "min6"],
166    },
167    Music21ChordType {
168        kind: "major-ninth",
169        notation: "1,3,5,7,9",
170        abbreviation: "M9",
171        abbreviations: &["M9", "Maj9"],
172    },
173    Music21ChordType {
174        kind: "dominant-ninth",
175        notation: "1,3,5,-7,9",
176        abbreviation: "9",
177        abbreviations: &["9", "dom9"],
178    },
179    Music21ChordType {
180        kind: "minor-major-ninth",
181        notation: "1,-3,5,7,9",
182        abbreviation: "mM9",
183        abbreviations: &["mM9", "minmaj9"],
184    },
185    Music21ChordType {
186        kind: "minor-ninth",
187        notation: "1,-3,5,-7,9",
188        abbreviation: "m9",
189        abbreviations: &["m9", "min9"],
190    },
191    Music21ChordType {
192        kind: "augmented-major-ninth",
193        notation: "1,3,#5,7,9",
194        abbreviation: "+M9",
195        abbreviations: &["+M9", "augmaj9"],
196    },
197    Music21ChordType {
198        kind: "augmented-dominant-ninth",
199        notation: "1,3,#5,-7,9",
200        abbreviation: "9#5",
201        abbreviations: &["9#5", "+9", "aug9"],
202    },
203    Music21ChordType {
204        kind: "half-diminished-ninth",
205        notation: "1,-3,-5,-7,9",
206        abbreviation: "ø9",
207        abbreviations: &["ø9"],
208    },
209    Music21ChordType {
210        kind: "half-diminished-minor-ninth",
211        notation: "1,-3,-5,-7,-9",
212        abbreviation: "øb9",
213        abbreviations: &["øb9"],
214    },
215    Music21ChordType {
216        kind: "diminished-ninth",
217        notation: "1,-3,-5,--7,9",
218        abbreviation: "o9",
219        abbreviations: &["o9", "dim9"],
220    },
221    Music21ChordType {
222        kind: "diminished-minor-ninth",
223        notation: "1,-3,-5,--7,-9",
224        abbreviation: "ob9",
225        abbreviations: &["ob9", "dimb9"],
226    },
227    Music21ChordType {
228        kind: "dominant-11th",
229        notation: "1,3,5,-7,9,11",
230        abbreviation: "11",
231        abbreviations: &["11", "dom11"],
232    },
233    Music21ChordType {
234        kind: "major-11th",
235        notation: "1,3,5,7,9,11",
236        abbreviation: "M11",
237        abbreviations: &["M11", "Maj11"],
238    },
239    Music21ChordType {
240        kind: "minor-major-11th",
241        notation: "1,-3,5,7,9,11",
242        abbreviation: "mM11",
243        abbreviations: &["mM11", "minmaj11"],
244    },
245    Music21ChordType {
246        kind: "minor-11th",
247        notation: "1,-3,5,-7,9,11",
248        abbreviation: "m11",
249        abbreviations: &["m11", "min11"],
250    },
251    Music21ChordType {
252        kind: "augmented-major-11th",
253        notation: "1,3,#5,7,9,11",
254        abbreviation: "+M11",
255        abbreviations: &["+M11", "augmaj11"],
256    },
257    Music21ChordType {
258        kind: "augmented-11th",
259        notation: "1,3,#5,-7,9,11",
260        abbreviation: "+11",
261        abbreviations: &["+11", "aug11"],
262    },
263    Music21ChordType {
264        kind: "half-diminished-11th",
265        notation: "1,-3,-5,-7,9,11",
266        abbreviation: "ø11",
267        abbreviations: &["ø11"],
268    },
269    Music21ChordType {
270        kind: "diminished-11th",
271        notation: "1,-3,-5,--7,9,11",
272        abbreviation: "o11",
273        abbreviations: &["o11", "dim11"],
274    },
275    Music21ChordType {
276        kind: "major-13th",
277        notation: "1,3,5,7,9,11,13",
278        abbreviation: "M13",
279        abbreviations: &["M13", "Maj13"],
280    },
281    Music21ChordType {
282        kind: "dominant-13th",
283        notation: "1,3,5,-7,9,11,13",
284        abbreviation: "13",
285        abbreviations: &["13", "dom13"],
286    },
287    Music21ChordType {
288        kind: "minor-major-13th",
289        notation: "1,-3,5,7,9,11,13",
290        abbreviation: "mM13",
291        abbreviations: &["mM13", "minmaj13"],
292    },
293    Music21ChordType {
294        kind: "minor-13th",
295        notation: "1,-3,5,-7,9,11,13",
296        abbreviation: "m13",
297        abbreviations: &["m13", "min13"],
298    },
299    Music21ChordType {
300        kind: "augmented-major-13th",
301        notation: "1,3,#5,7,9,11,13",
302        abbreviation: "+M13",
303        abbreviations: &["+M13", "augmaj13"],
304    },
305    Music21ChordType {
306        kind: "augmented-dominant-13th",
307        notation: "1,3,#5,-7,9,11,13",
308        abbreviation: "+13",
309        abbreviations: &["+13", "aug13"],
310    },
311    Music21ChordType {
312        kind: "half-diminished-13th",
313        notation: "1,-3,-5,-7,9,11,13",
314        abbreviation: "ø13",
315        abbreviations: &["ø13"],
316    },
317    Music21ChordType {
318        kind: "suspended-second",
319        notation: "1,2,5",
320        abbreviation: "sus2",
321        abbreviations: &["sus2"],
322    },
323    Music21ChordType {
324        kind: "suspended-fourth",
325        notation: "1,4,5",
326        abbreviation: "sus",
327        abbreviations: &["sus", "sus4"],
328    },
329    Music21ChordType {
330        kind: "suspended-fourth-seventh",
331        notation: "1,4,5,-7",
332        abbreviation: "7sus",
333        abbreviations: &["7sus", "7sus4"],
334    },
335    Music21ChordType {
336        kind: "Neapolitan",
337        notation: "1,-2,3,-5",
338        abbreviation: "N6",
339        abbreviations: &["N6"],
340    },
341    Music21ChordType {
342        kind: "Italian",
343        notation: "1,#4,-6",
344        abbreviation: "It+6",
345        abbreviations: &["It+6", "It"],
346    },
347    Music21ChordType {
348        kind: "French",
349        notation: "1,2,#4,-6",
350        abbreviation: "Fr+6",
351        abbreviations: &["Fr+6", "Fr"],
352    },
353    Music21ChordType {
354        kind: "German",
355        notation: "1,-3,#4,-6",
356        abbreviation: "Gr+6",
357        abbreviations: &["Gr+6", "Ger"],
358    },
359    Music21ChordType {
360        kind: "pedal",
361        notation: "1",
362        abbreviation: "pedal",
363        abbreviations: &["pedal"],
364    },
365    Music21ChordType {
366        kind: "power",
367        notation: "1,5",
368        abbreviation: "power",
369        abbreviations: &["power"],
370    },
371    Music21ChordType {
372        kind: "Tristan",
373        notation: "1,#4,#6,#9",
374        abbreviation: "tristan",
375        abbreviations: &["tristan"],
376    },
377];
378
379pub(super) fn chord_degrees_for_notation(notation: &str) -> Option<Vec<u8>> {
380    notation
381        .split(',')
382        .filter(|token| *token != "1")
383        .map(|token| parse_music21_degree(token).map(|degree| degree.semitone))
384        .collect()
385}
386
387pub(super) fn degree_numbers_for_notation(notation: &str) -> Option<Vec<u8>> {
388    notation
389        .split(',')
390        .map(|token| parse_music21_degree(token).map(|degree| degree.degree))
391        .collect()
392}
393
394pub(super) fn parse_music21_degree(token: &str) -> Option<Music21Degree> {
395    let alteration = token.chars().fold(0_i32, |sum, ch| match ch {
396        '#' => sum + 1,
397        '-' => sum - 1,
398        _ => sum,
399    });
400    let degree = token
401        .chars()
402        .filter(char::is_ascii_digit)
403        .collect::<String>()
404        .parse::<u8>()
405        .ok()?;
406    let semitone = (base_semitone_for_degree(degree)? + alteration).rem_euclid(12) as u8;
407
408    Some(Music21Degree { degree, semitone })
409}
410
411pub(super) fn base_semitone_for_degree(degree: u8) -> Option<IntegerType> {
412    match degree {
413        1 => Some(0),
414        2 | 9 => Some(2),
415        3 => Some(4),
416        4 | 11 => Some(5),
417        5 => Some(7),
418        6 | 13 => Some(9),
419        7 => Some(11),
420        _ => None,
421    }
422}
423
424/// The names of the notes a chord kind's notation stands for above a root:
425/// what music21's `ChordSymbol` realizes for the kind alone, and what the
426/// figure writer compares a chord against.
427pub(super) fn kind_pitch_names(root: &Pitch, notation: &str) -> Result<BTreeSet<String>> {
428    notation_intervals(notation)?
429        .iter()
430        .map(|(_, name)| Ok(Interval::from_name(name)?.transpose_pitch(root)?.name()))
431        .collect()
432}
433
434/// The interval above the root each degree of a kind's notation stands for,
435/// `1,3,#5,-7` being `P1`, `M3`, `a5` and `m7`: the major-scale interval of
436/// the degree, raised or lowered by each `#` or `-` written against it.
437pub(super) fn notation_intervals(notation: &str) -> Result<Vec<(u8, String)>> {
438    notation
439        .split(',')
440        .map(|token| {
441            let degree: u8 = token
442                .trim_matches(['#', '-'])
443                .parse()
444                .map_err(|_| Error::Chord(format!("{token} is not a chord degree")))?;
445            let alter = token.matches('#').count() as IntegerType
446                - token.matches('-').count() as IntegerType;
447            let perfect = matches!(degree % 7, 1 | 4 | 5);
448            let quality = match (perfect, alter) {
449                (true, 0) => "P".to_string(),
450                (false, 0) => "M".to_string(),
451                (_, raised) if raised > 0 => "a".repeat(raised as usize),
452                (true, lowered) => "d".repeat(lowered.unsigned_abs() as usize),
453                (false, -1) => "m".to_string(),
454                (false, lowered) => "d".repeat((lowered.unsigned_abs() - 1) as usize),
455            };
456            Ok((degree, format!("{quality}{degree}")))
457        })
458        .collect()
459}