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