1use std::str::FromStr;
2
3use crate::{
4 chord::Chord,
5 chord::root::{pitch_class, step_num},
6 defaults::{FloatType, IntegerType},
7 duration::{Duration, DurationType},
8 error::{Error, Result},
9 interval::Interval,
10 pitch::Pitch,
11 stream::{Stream, StreamElement},
12};
13use std::collections::BTreeSet;
14
15mod figure;
16mod parse;
17mod realize;
18mod tables;
19
20pub use figure::{
21 ChordSymbolFigure, alter_to_chord_symbol_string, chord_symbol_figure_from_chord,
22 chord_symbol_from_chord, chord_symbol_kind_from_chord,
23};
24pub(crate) use figure::{chord_symbol_spellings, chord_symbol_spellings_with_root};
25pub use tables::{
26 CHORD_KIND_ALIASES, Music21ChordType, abbreviations_for_kind, current_abbreviation_for_kind,
27 known_chord_symbol_types, notation_for_kind, resolve_kind_alias,
28};
29
30use parse::*;
31pub use realize::{
32 ChordStepModification, ChordStepModificationType, ChordVoicing, inversion_is_valid_for_kind,
33 sound_chord_kind, sound_chord_notation, voice_chord_notation,
34};
35use tables::*;
36
37#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40pub enum ChordQuality {
41 Major,
43 Minor,
45 Dominant,
47 Diminished,
49 Augmented,
51 HalfDiminished,
53 Suspended2,
55 Suspended4,
57 Power,
59 Pedal,
61}
62
63#[derive(Clone, Debug, Eq, PartialEq)]
65#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
66pub struct ChordAlteration {
67 degree: u8,
68 semitones: IntegerType,
69}
70
71impl ChordAlteration {
72 pub fn new(degree: u8, semitones: IntegerType) -> Self {
74 Self { degree, semitones }
75 }
76
77 pub fn degree(&self) -> u8 {
79 self.degree
80 }
81
82 pub fn semitones(&self) -> IntegerType {
84 self.semitones
85 }
86}
87
88#[derive(Clone, Debug, PartialEq)]
90#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
91#[must_use]
92pub struct ChordSymbol {
93 figure: String,
94 root: Pitch,
95 bass: Option<Pitch>,
96 quality: ChordQuality,
97 extensions: Vec<u8>,
98 alterations: Vec<ChordAlteration>,
99 #[cfg_attr(feature = "serde", serde(default))]
100 omissions: Vec<u8>,
101 #[cfg_attr(feature = "serde", serde(default))]
102 additions: Vec<ChordAlteration>,
103 #[cfg_attr(feature = "serde", serde(default))]
105 kind: Option<String>,
106 #[cfg_attr(feature = "serde", serde(default))]
109 modifications: Vec<ChordStepModification>,
110 #[cfg_attr(feature = "serde", serde(default = "no_time"))]
113 duration: Duration,
114 #[cfg_attr(feature = "serde", serde(default))]
117 kind_text: crate::display::Drawn<Option<String>>,
118 #[cfg_attr(feature = "serde", serde(default))]
119 placement: crate::display::Drawn<Option<crate::notation::Placement>>,
120 #[cfg_attr(feature = "serde", serde(default))]
123 lyrics: Vec<crate::notation::Lyric>,
124}
125
126fn no_time() -> Duration {
128 Duration::from_type(DurationType::Zero)
129}
130
131fn music21_kind(shorthand: &str) -> Option<(&'static Music21ChordType, usize)> {
136 let mut cut = shorthand.len();
137 for marker in ["add", "alter", "omit", "subtract"] {
138 if let Some(at) = shorthand.find(marker) {
139 cut = cut.min(at);
140 }
141 }
142 let mut head = &shorthand[..cut];
143 if let Some(at) = head.find('#')
144 && head[at + 1..].starts_with(|c: char| c.is_ascii_digit())
145 {
146 head = &head[..at];
147 }
148 if let Some(at) = head.find('b')
149 && at < head.len() - 1
150 && head[at + 1..].starts_with(|c: char| c.is_ascii_digit())
151 && !head.contains("ob9")
152 && !head.contains("øb9")
153 {
154 head = &head[..at];
155 }
156 MUSIC21_CHORD_TYPES
157 .iter()
158 .find(|chord_type| chord_type.abbreviations.contains(&head))
159 .map(|chord_type| (chord_type, head.len()))
160}
161
162fn python_int(text: &str) -> Option<i64> {
169 let digits = text.strip_prefix(['+', '-']).unwrap_or(text);
170 if digits.is_empty() || !digits.chars().all(|c| c.is_ascii_digit()) {
171 return None;
172 }
173 text.parse().ok()
174}
175
176fn music21_modifications(remaining: &str) -> Option<Vec<ChordStepModification>> {
177 const MARKERS: [(&str, ChordStepModificationType); 4] = [
178 ("add", ChordStepModificationType::Add),
179 ("alter", ChordStepModificationType::Alter),
180 ("omit", ChordStepModificationType::Subtract),
181 ("subtract", ChordStepModificationType::Subtract),
182 ];
183 let mut out = Vec::new();
184 let first_marker = MARKERS
185 .iter()
186 .filter_map(|(marker, _)| remaining.find(marker))
187 .min();
188 if let Some(start) = first_marker {
189 let mut text = &remaining[start..];
190 while let Some((at, marker, kind)) = MARKERS
191 .iter()
192 .filter_map(|(marker, kind)| text.find(marker).map(|at| (at, *marker, *kind)))
193 .min_by_key(|(at, _, _)| *at)
194 {
195 let mut rest = &text[at + marker.len()..];
196 let mut alter = 0;
197 while let Some(sign) = rest.chars().next().filter(|c| matches!(c, 'b' | '-' | '#')) {
198 alter += if sign == '#' { 1 } else { -1 };
199 rest = &rest[1..];
200 }
201 let digits = rest.chars().take_while(char::is_ascii_digit).count().min(2);
202 let degree_text = &rest[..digits];
203 if let Ok(degree) = degree_text.parse::<u8>() {
204 out.push(ChordStepModification::new(kind, degree, alter).ok()?);
205 }
206 text = &rest[digits..];
207 }
208 }
209
210 let before: String = remaining[..first_marker.unwrap_or(remaining.len())].replace(',', "");
213 let mut items: Vec<String> = Vec::new();
214 if before.contains(['b', '#']) {
215 let chars: Vec<char> = before.chars().collect();
216 let mut current = String::new();
217 let mut index = 0;
218 while index < chars.len() {
219 if matches!(chars[index], 'b' | '#') {
220 if !current.is_empty() {
221 items.push(std::mem::take(&mut current));
222 }
223 let mut group = String::new();
224 while index < chars.len() && matches!(chars[index], 'b' | '#') {
225 group.push(chars[index]);
226 index += 1;
227 }
228 while index < chars.len() && !matches!(chars[index], 'b' | '#') {
229 group.push(chars[index]);
230 index += 1;
231 }
232 items.push(group);
233 } else {
234 current.push(chars[index]);
235 index += 1;
236 }
237 }
238 if !current.is_empty() {
239 items.push(current);
240 }
241 } else if !before.is_empty() {
242 items.push(before);
243 }
244
245 let mut tokens: Vec<String> = Vec::new();
249 for item in items {
250 let unsigned: String = item.chars().filter(|c| !matches!(c, 'b' | '#')).collect();
251 let number = python_int(&unsigned)?;
252 if number > 20 {
253 let chars: Vec<char> = item.chars().collect();
256 let mut prefix = String::new();
257 let mut skip = false;
258 for (index, &ch) in chars.iter().enumerate() {
259 if skip {
260 skip = false;
261 continue;
262 }
263 if ch == '1' {
264 let mut token = String::from(ch);
265 if let Some(next) = chars.get(index + 1) {
266 token.push(*next);
267 }
268 tokens.push(token);
269 skip = true;
270 } else if matches!(ch, 'b' | '#') {
271 prefix.push(ch);
272 } else {
273 prefix.push(ch);
274 tokens.push(std::mem::take(&mut prefix));
275 }
276 }
277 } else {
278 tokens.push(item);
279 }
280 }
281 for token in tokens {
282 let alter = if token.contains('b') {
283 -(token.matches('b').count() as IntegerType)
284 } else {
285 token.matches('#').count() as IntegerType
286 };
287 let nonzero = |c: &char| matches!(c, '1'..='9');
290 let digits: String = token
291 .chars()
292 .skip_while(|c| !nonzero(c))
293 .take_while(nonzero)
294 .collect();
295 if let Ok(degree) = digits.parse::<u8>() {
296 out.push(
297 ChordStepModification::new(ChordStepModificationType::Add, degree, alter).ok()?,
298 );
299 }
300 }
301 Some(out)
302}
303
304impl ChordSymbol {
305 pub fn parse(figure: impl Into<String>) -> Result<Self> {
310 Self::parse_with(figure.into(), false)
311 }
312
313 pub fn parse_music21(figure: impl Into<String>) -> Result<Self> {
327 let figure = figure.into();
328 let compact: String = figure.chars().filter(|c| !c.is_whitespace()).collect();
329 let refused_root = || {
330 Error::Chord(format!(
331 "Chord {compact} does not begin with a valid root note."
332 ))
333 };
334 let (root_text, rest) = match compact.find(',') {
335 Some(at) => {
336 let root = compact[..at].to_string();
337 let rest = compact.replace(',', "").replace(root.as_str(), "");
338 (root, rest)
339 }
340 None => {
341 let mut letters = compact.char_indices();
342 match letters.next() {
343 Some((_, first)) if "ABCDEFGabcdefg".contains(first) => {}
344 _ => return Err(refused_root()),
345 }
346 let end = letters
347 .find(|(_, c)| !matches!(c, '#' | '-'))
348 .map_or(compact.len(), |(at, _)| at);
349 let root = compact[..end].to_string();
350 let rest = compact.replacen(root.as_str(), "", 1);
351 (root, rest)
352 }
353 };
354 let root = Pitch::from_name(&root_text)?;
357 let mut bass = None;
360 let mut remaining = rest.clone();
361 if let Some(slash) = compact.find('/') {
362 let after = &compact[slash + 1..];
363 let mut letters = after.char_indices();
364 if let Some((_, first)) = letters.next()
365 && "ABCDEFGabcdefg".contains(first)
366 {
367 let end = letters
368 .find(|(_, c)| !matches!(c, '#' | '-'))
369 .map_or(after.len(), |(at, _)| at);
370 let written = &after[..end];
371 bass = Some(Pitch::from_name(written)?);
372 remaining = rest.replace(&format!("/{written}"), "");
373 }
374 }
375 let (kind, taken) = match music21_kind(&remaining) {
376 Some((chord_type, taken)) => (chord_type.kind.to_string(), taken),
377 None => (String::new(), 0),
378 };
379 let refused = || {
380 let mut said = remaining[taken..].replace(',', "");
381 for marker in ["add", "alter", "omit", "subtract"] {
382 if let Some(at) = said.find(marker) {
383 said.truncate(at);
384 }
385 }
386 Error::Chord(format!(
387 "Invalid chord abbreviation '{said}'; see music21.harmony.CHORD_TYPES for valid abbreviations or specify all alterations."
388 ))
389 };
390 let modifications = music21_modifications(&remaining[taken..]).ok_or_else(refused)?;
391 let own = Self::parse_with(format!("C{remaining}"), false).ok();
395 Ok(Self {
396 figure: figure.trim().to_string(),
397 root,
398 bass,
399 quality: own.as_ref().map_or(ChordQuality::Major, |own| own.quality),
400 extensions: own
401 .as_ref()
402 .map(|own| own.extensions.clone())
403 .unwrap_or_default(),
404 alterations: own.map(|own| own.alterations).unwrap_or_default(),
405 omissions: Vec::new(),
406 additions: Vec::new(),
407 kind: Some(kind),
408 modifications,
409 duration: no_time(),
410 kind_text: crate::display::Drawn(None),
411 placement: crate::display::Drawn(None),
412 lyrics: Vec::new(),
413 })
414 }
415
416 fn parse_with(figure: String, music21: bool) -> Result<Self> {
417 let trimmed = figure.trim();
418 if trimmed.is_empty() {
419 return Err(Error::Chord("chord symbol cannot be empty".to_string()));
420 }
421 let compact: String = trimmed.chars().filter(|c| !c.is_whitespace()).collect();
424
425 let (body, bass_segment) = match compact.split_once('/') {
426 Some((body, bass)) => (body, Some(bass)),
427 None => (compact.as_str(), None),
428 };
429 let body_parts = split_music21_pitch_modifiers(body);
430 let bass_parts = bass_segment.map(split_music21_pitch_modifiers);
431 let bass = bass_parts
432 .as_ref()
433 .map(|parts| parse_pitch_only(&parts.base, music21))
434 .transpose()?;
435
436 let (root_name, suffix) = parse_pitch_prefix(&body_parts.base, music21)?;
437 let root = Pitch::from_name(root_name)?;
438 let suffix_without_additions = strip_addition_groups(suffix);
439 let mut additions = parse_additions(suffix);
440 let mut omissions = parse_omissions(suffix);
441 for pitch_name in body_parts
442 .additions
443 .iter()
444 .chain(bass_parts.iter().flat_map(|parts| parts.additions.iter()))
445 {
446 if let Some(addition) = pitch_name_addition(&root, pitch_name) {
447 additions.push(addition);
448 }
449 }
450 for pitch_name in body_parts
451 .omissions
452 .iter()
453 .chain(bass_parts.iter().flat_map(|parts| parts.omissions.iter()))
454 {
455 if let Some(omission) = pitch_name_degree(&root, pitch_name)
456 && !omissions.contains(&omission)
457 {
458 omissions.push(omission);
459 }
460 }
461
462 let mut alterations = parse_alterations(&suffix_without_additions);
463 add_implicit_music21_alterations(&suffix_without_additions, &mut alterations);
464 let extensions = parse_extensions(&suffix_without_additions, &alterations);
465 let quality = parse_quality(&suffix_without_additions, &alterations);
466 let full_suffix = &body[body_parts.base.len() - suffix.len()..];
473 let (kind, modifications) = match music21_kind(full_suffix) {
474 Some((chord_type, taken)) => match music21_modifications(&full_suffix[taken..]) {
475 Some(modifications) => (Some(chord_type.kind.to_string()), modifications),
476 None => (None, Vec::new()),
477 },
478 None => (None, Vec::new()),
479 };
480
481 Ok(Self {
482 figure: trimmed.to_string(),
483 root,
484 bass,
485 quality,
486 extensions,
487 alterations,
488 omissions,
489 additions,
490 kind,
491 modifications,
492 duration: no_time(),
493 kind_text: crate::display::Drawn(None),
494 placement: crate::display::Drawn(None),
495 lyrics: Vec::new(),
496 })
497 }
498
499 pub fn no_chord(text: Option<String>) -> Self {
503 Self {
504 figure: "N.C.".to_string(),
505 root: Pitch::from_name("C").expect("C is a pitch name"),
506 bass: None,
507 quality: ChordQuality::Major,
508 extensions: Vec::new(),
509 alterations: Vec::new(),
510 omissions: Vec::new(),
511 additions: Vec::new(),
512 kind: Some("none".to_string()),
513 modifications: Vec::new(),
514 duration: no_time(),
515 kind_text: crate::display::Drawn(Some(text.unwrap_or_else(|| "N.C.".to_string()))),
516 placement: crate::display::Drawn(None),
517 lyrics: Vec::new(),
518 }
519 }
520
521 pub fn lyrics(&self) -> &[crate::notation::Lyric] {
523 &self.lyrics
524 }
525
526 pub fn lyrics_mut(&mut self) -> &mut Vec<crate::notation::Lyric> {
528 &mut self.lyrics
529 }
530
531 pub fn add_lyric(&mut self, text: &str, number: Option<IntegerType>, apply_raw: bool) {
534 crate::notation::verses::add(&mut self.lyrics, text, number, apply_raw);
535 }
536
537 pub fn is_no_chord(&self) -> bool {
539 self.kind.as_deref() == Some("none")
540 }
541
542 pub fn kind_text(&self) -> Option<&str> {
545 self.kind_text.0.as_deref()
546 }
547
548 pub fn set_kind_text(&mut self, text: Option<String>) {
550 self.kind_text.0 = text.filter(|text| !text.is_empty());
551 }
552
553 pub fn placement(&self) -> Option<crate::notation::Placement> {
555 self.placement.0
556 }
557
558 pub fn set_placement(&mut self, placement: Option<crate::notation::Placement>) {
560 self.placement.0 = placement;
561 }
562
563 pub fn duration(&self) -> &Duration {
567 &self.duration
568 }
569
570 pub fn set_duration(&mut self, duration: Duration) {
572 self.duration = duration;
573 }
574
575 pub fn with_duration(mut self, duration: Duration) -> Self {
577 self.duration = duration;
578 self
579 }
580
581 pub fn kind(&self) -> Option<&str> {
585 self.kind.as_deref()
586 }
587
588 pub fn figure(&self) -> &str {
590 &self.figure
591 }
592
593 pub fn root(&self) -> &Pitch {
595 &self.root
596 }
597
598 pub fn bass(&self) -> Option<&Pitch> {
600 self.bass.as_ref()
601 }
602
603 pub fn quality(&self) -> ChordQuality {
605 self.quality
606 }
607
608 pub fn extensions(&self) -> &[u8] {
610 &self.extensions
611 }
612
613 pub fn alterations(&self) -> &[ChordAlteration] {
615 &self.alterations
616 }
617
618 pub fn omissions(&self) -> &[u8] {
620 &self.omissions
621 }
622
623 pub fn additions(&self) -> &[ChordAlteration] {
625 &self.additions
626 }
627
628 pub fn find_figure(&self) -> String {
633 let mut figure = self.root.name();
634 figure.push_str(&self.quality_and_extension());
635 for alteration in &self.alterations {
636 if alteration.degree == 5 && self.fifth_is_implied() {
637 continue;
638 }
639 figure.push_str(&alteration_text(alteration));
640 }
641 for addition in &self.additions {
642 figure.push_str(&format!("add({})", alteration_text(addition)));
643 }
644 for omission in &self.omissions {
645 figure.push_str(&format!("no{omission}"));
646 }
647 if let Some(bass) = &self.bass
648 && bass.name() != self.root.name()
649 {
650 figure.push('/');
651 figure.push_str(&bass.name());
652 }
653 figure
654 }
655
656 pub fn transpose(&self, interval: &Interval) -> Result<ChordSymbol> {
660 let mut transposed = self.clone();
661 transposed.root = self.root.transpose(interval)?;
662 transposed.bass = self
663 .bass
664 .as_ref()
665 .map(|bass| bass.transpose(interval))
666 .transpose()?;
667 transposed.figure = transposed.find_figure();
668 Ok(transposed)
669 }
670
671 pub fn inversion_is_valid(&self, inversion: u8) -> bool {
679 if let Some(kind) = &self.kind {
680 return realize::inversion_is_valid_for_kind(kind, inversion);
681 }
682 let highest = self
683 .extensions
684 .iter()
685 .copied()
686 .filter(|degree| matches!(degree, 7 | 9 | 11 | 13))
687 .max()
688 .unwrap_or(5);
689 match inversion {
690 1 | 2 => true,
691 3 => highest >= 7,
692 4 => highest >= 9,
693 5 => highest >= 11,
694 _ => false,
695 }
696 }
697
698 pub fn chord_step_modifications(&self) -> &[ChordStepModification] {
701 &self.modifications
702 }
703
704 pub fn add_chord_step_modification(&mut self, modification: ChordStepModification) {
708 if !self.modifications.contains(&modification) {
709 self.modifications.push(modification);
710 }
711 }
712
713 pub fn set_chord_step_modifications(&mut self, modifications: Vec<ChordStepModification>) {
715 self.modifications = modifications;
716 }
717
718 pub fn from_kind(root: Pitch, kind: &str, bass: Option<Pitch>) -> Result<Self> {
722 let abbreviation = current_abbreviation_for_kind(kind)
723 .ok_or_else(|| Error::Chord(format!("no such chord kind: {kind}")))?;
724 let mut figure = format!("{}{abbreviation}", root.name());
725 if let Some(bass) = bass.as_ref().filter(|bass| bass.name() != root.name()) {
726 figure.push('/');
727 figure.push_str(&bass.name());
728 }
729 let mut symbol = Self::parse(&figure)?;
730 symbol.kind = Some(kind.to_string());
731 symbol.modifications.clear();
732 symbol.root = root;
733 if bass.is_some() {
734 symbol.bass = bass;
735 }
736 Ok(symbol)
737 }
738
739 pub fn with_kind(mut self, kind: &str) -> Self {
743 self.kind = Some(kind.to_string());
744 self.modifications.clear();
745 self
746 }
747
748 pub fn set_root(&mut self, root: Pitch) {
750 self.root = root;
751 }
752
753 pub fn set_bass(&mut self, bass: Option<Pitch>) {
755 self.bass = bass;
756 }
757
758 fn fifth_is_implied(&self) -> bool {
759 matches!(
760 self.quality,
761 ChordQuality::Diminished | ChordQuality::Augmented | ChordQuality::HalfDiminished
762 )
763 }
764
765 fn quality_and_extension(&self) -> String {
768 let seventh_family = self
769 .extensions
770 .iter()
771 .copied()
772 .filter(|degree| matches!(degree, 7 | 9 | 11 | 13))
773 .filter(|degree| {
774 !self
775 .alterations
776 .iter()
777 .any(|alteration| alteration.degree == *degree)
778 })
779 .max()
780 .or_else(|| {
781 self.extensions
782 .iter()
783 .any(|degree| matches!(degree, 7 | 9 | 11 | 13))
784 .then_some(7)
785 });
786 let sixth = self.extensions.contains(&6);
787 let extension = |written: &str| -> String {
788 match seventh_family {
789 Some(degree) => format!("{written}{degree}"),
790 None if sixth => format!("{written}6"),
791 None => written.to_string(),
792 }
793 };
794 match self.quality {
795 ChordQuality::Major => match seventh_family {
796 Some(degree) => format!("maj{degree}"),
797 None if sixth => "6".to_string(),
798 None => String::new(),
799 },
800 ChordQuality::Minor => extension("m"),
801 ChordQuality::Dominant => seventh_family.unwrap_or(7).to_string(),
802 ChordQuality::Diminished => extension("dim"),
803 ChordQuality::Augmented => extension("aug"),
804 ChordQuality::HalfDiminished => format!("m{}b5", seventh_family.unwrap_or(7)),
805 ChordQuality::Suspended2 => match seventh_family {
806 Some(degree) => format!("{degree}sus2"),
807 None => "sus2".to_string(),
808 },
809 ChordQuality::Suspended4 => match seventh_family {
810 Some(degree) => format!("{degree}sus4"),
811 None => "sus4".to_string(),
812 },
813 ChordQuality::Power => "5".to_string(),
814 ChordQuality::Pedal => "pedal".to_string(),
815 }
816 }
817
818 pub fn to_chord(&self) -> Result<Chord> {
821 Chord::new(self.pitches()?.as_slice())
822 }
823
824 pub fn pitches(&self) -> Result<Vec<Pitch>> {
839 if self.is_no_chord() {
840 return Ok(Vec::new());
841 }
842 if self.kind.as_deref() == Some("")
846 || (self.kind.is_some() && self.kind_notation().is_none())
847 {
848 return realize::sound_chord_kind(
849 &self.root,
850 self.kind.as_deref().unwrap_or(""),
851 self.bass.as_ref(),
852 &self.modifications,
853 );
854 }
855 match self.kind_notation() {
856 Some(notation) => {
857 let mut realized = realize::realize(
858 &self.root,
859 self.bass.as_ref(),
860 self.kind.as_deref(),
861 Some(notation),
862 None,
863 &self.modifications,
864 )?;
865 self.apply_own_reading(&mut realized.pitches, notation)?;
866 Ok(realized.pitches)
867 }
868 None => {
869 let mut intervals = self.spelled_intervals()?;
870 intervals.sort_unstable_by_key(|(degree, _)| *degree);
871 intervals.dedup();
872 let names = intervals
873 .into_iter()
874 .map(|(_, name)| {
875 Ok(Interval::from_name(&name)?
876 .transpose_pitch(&self.root)?
877 .name())
878 })
879 .collect::<Result<Vec<_>>>()?;
880 Ok(
881 realize::realize(&self.root, self.bass.as_ref(), None, None, Some(names), &[])?
882 .pitches,
883 )
884 }
885 }
886 }
887
888 fn apply_own_reading(&self, pitches: &mut Vec<Pitch>, notation: &str) -> Result<()> {
892 if !self.omissions.is_empty() {
893 for (degree, name) in notation_intervals(notation)? {
894 if !self.omissions.contains(°ree) {
895 continue;
896 }
897 let omitted = Interval::from_name(&name)?
898 .transpose_pitch(&self.root)?
899 .name();
900 pitches.retain(|pitch| pitch.name() != omitted);
901 }
902 }
903 for addition in &self.additions {
904 let (_, name) = added_interval(addition)?;
905 let pitch = Interval::from_name(name)?.transpose_pitch(&self.root)?;
906 if pitches
907 .iter()
908 .any(|existing| existing.name() == pitch.name())
909 {
910 continue;
911 }
912 let mut placed = pitch;
914 let top = pitches.last().map_or(0.0, Pitch::ps);
915 placed.set_octave(pitches.last().and_then(Pitch::octave));
916 while placed.ps() <= top {
917 let octave = placed.octave().unwrap_or(3);
918 placed.set_octave(Some(octave + 1));
919 }
920 pitches.push(placed);
921 }
922 Ok(())
923 }
924
925 fn kind_notation(&self) -> Option<&'static str> {
929 notation_for_kind(self.kind.as_deref()?)
930 }
931
932 fn spelled_intervals(&self) -> Result<Vec<(u8, String)>> {
935 let mut intervals = self.base_intervals();
936
937 let highest = self
938 .extensions
939 .iter()
940 .copied()
941 .filter(|degree| !self.alterations.iter().any(|alt| alt.degree == *degree))
942 .max()
943 .unwrap_or(0);
944 for extension in [6, 9, 11, 13] {
945 let implied = match extension {
946 9 => highest >= 11,
947 11 => highest == 13,
948 _ => false,
949 };
950 if (self.extensions.contains(&extension) || implied)
951 && !self.alterations.iter().any(|alt| alt.degree == extension)
952 && !self.omissions.contains(&extension)
953 {
954 intervals.push((extension, default_extension_interval(extension)));
955 }
956 }
957
958 for alteration in &self.alterations {
959 if alteration.degree == 5 {
960 continue;
961 }
962 let altered = altered_interval(alteration)?;
963 intervals.retain(|(degree, _)| *degree != altered.0);
967 intervals.push(altered);
968 }
969
970 for addition in &self.additions {
971 intervals.push(added_interval(addition)?);
972 }
973
974 Ok(intervals
975 .into_iter()
976 .map(|(degree, name)| (degree, name.to_string()))
977 .collect())
978 }
979
980 fn base_intervals(&self) -> Vec<(u8, &'static str)> {
981 let altered_fifth = self
982 .alterations
983 .iter()
984 .find(|alteration| alteration.degree == 5)
985 .and_then(|alteration| match alteration.semitones {
986 -1 => Some("d5"),
987 1 => Some("a5"),
988 _ => None,
989 });
990
991 let fifth = altered_fifth.unwrap_or("P5");
992 let has_seventh = self
993 .extensions
994 .iter()
995 .any(|degree| matches!(degree, 7 | 9 | 11 | 13));
996
997 let intervals = match self.quality {
998 ChordQuality::Major => {
999 if has_seventh {
1000 vec![(1, "P1"), (3, "M3"), (5, fifth), (7, "M7")]
1001 } else {
1002 vec![(1, "P1"), (3, "M3"), (5, fifth)]
1003 }
1004 }
1005 ChordQuality::Minor => {
1006 if has_seventh {
1007 vec![(1, "P1"), (3, "m3"), (5, fifth), (7, "m7")]
1008 } else {
1009 vec![(1, "P1"), (3, "m3"), (5, fifth)]
1010 }
1011 }
1012 ChordQuality::Dominant => vec![(1, "P1"), (3, "M3"), (5, fifth), (7, "m7")],
1013 ChordQuality::Diminished => {
1014 if has_seventh {
1015 vec![(1, "P1"), (3, "m3"), (5, "d5"), (7, "d7")]
1016 } else {
1017 vec![(1, "P1"), (3, "m3"), (5, "d5")]
1018 }
1019 }
1020 ChordQuality::Augmented => vec![(1, "P1"), (3, "M3"), (5, "a5")],
1021 ChordQuality::HalfDiminished => vec![(1, "P1"), (3, "m3"), (5, "d5"), (7, "m7")],
1022 ChordQuality::Suspended2 => vec![(1, "P1"), (2, "M2"), (5, fifth)],
1023 ChordQuality::Suspended4 => vec![(1, "P1"), (4, "P4"), (5, fifth)],
1024 ChordQuality::Power => vec![(1, "P1"), (5, fifth)],
1025 ChordQuality::Pedal => vec![(1, "P1")],
1026 };
1027
1028 intervals
1029 .into_iter()
1030 .filter(|(degree, _)| !self.omissions.contains(degree))
1031 .collect()
1032 }
1033}
1034
1035pub fn realize_chord_symbol_durations(stream: &mut Stream) {
1067 let lengths = chord_symbol_durations(stream);
1068 let mut position = 0;
1069 stream.for_each_mut(&mut |_, element| {
1070 if let StreamElement::ChordSymbol(symbol) = element
1071 && let Some((_, length)) = lengths.iter().find(|(at, _)| *at == position)
1072 && let Ok(duration) = Duration::new(*length)
1073 {
1074 symbol.set_duration(duration);
1075 }
1076 position += 1;
1077 });
1078}
1079
1080pub fn chord_symbol_durations(stream: &Stream) -> Vec<(usize, FloatType)> {
1098 let end = stream.end_offset();
1099 let mut symbols: Vec<(FloatType, usize)> = stream
1100 .leaves()
1101 .iter()
1102 .enumerate()
1103 .filter(|(_, (_, element))| matches!(element, StreamElement::ChordSymbol(_)))
1104 .map(|(position, (offset, _))| (*offset, position))
1105 .collect();
1106 symbols.sort_by(|left, right| left.0.total_cmp(&right.0));
1108 symbols
1109 .iter()
1110 .enumerate()
1111 .map(|(index, &(offset, position))| {
1112 let until = symbols.get(index + 1).map_or(end, |next| next.0);
1113 (position, (until - offset).max(0.0))
1114 })
1115 .collect()
1116}
1117
1118impl FromStr for ChordSymbol {
1119 type Err = Error;
1120
1121 fn from_str(value: &str) -> Result<Self> {
1122 Self::parse(value)
1123 }
1124}
1125
1126impl TryFrom<&str> for ChordSymbol {
1127 type Error = Error;
1128
1129 fn try_from(value: &str) -> Result<Self> {
1130 Self::parse(value)
1131 }
1132}
1133
1134impl TryFrom<String> for ChordSymbol {
1135 type Error = Error;
1136
1137 fn try_from(value: String) -> Result<Self> {
1138 Self::parse(value)
1139 }
1140}
1141
1142#[cfg(test)]
1143mod tests {
1144
1145 #[test]
1150 fn the_voicing_keeps_music21s_root_and_bass() {
1151 let voiced = |figure: &str| {
1152 let symbol = ChordSymbol::parse_music21(figure).unwrap();
1153 let kind = symbol.kind().unwrap_or_default();
1154 voice_chord_notation(
1155 symbol.root(),
1156 kind,
1157 notation_for_kind(resolve_kind_alias(kind)),
1158 symbol.bass(),
1159 symbol.chord_step_modifications(),
1160 )
1161 .unwrap()
1162 };
1163 let a = voiced("A10/C");
1164 assert_eq!(a.root().name_with_octave(), "A3");
1165 assert_eq!(a.bass().name_with_octave(), "C2");
1166 let names: Vec<String> = a.pitches().iter().map(Pitch::name_with_octave).collect();
1167 assert_eq!(names, ["C2", "A2", "A3"]);
1168 assert_eq!(voiced("Ab10/F#").root().name_with_octave(), "A3");
1169
1170 let flat_one = voiced("Bb10/B-");
1173 assert_eq!(flat_one.bass().name_with_octave(), "Bb1");
1174 assert_eq!(voiced("Ab10/A-").bass().name_with_octave(), "Ab1");
1175 }
1176
1177 #[test]
1178 fn a_slash_bass_is_voiced_as_music21_voices_it() {
1179 let cases: [(&str, &[&str]); 20] = [
1180 ("B-5/E", &["E2", "E2", "Bb3", "F4"]),
1181 ("B-69/E", &["E2", "E2", "Bb3", "G4", "C5"]),
1182 ("C5/G", &["G3", "G3", "C4"]),
1183 ("C5/D", &["D2", "D2", "C3", "G3"]),
1184 ("C69/D", &["D2", "D2", "C3", "A3", "D4"]),
1185 ("C5/C#", &["C#2", "C#2", "C3", "G3"]),
1186 ("F5/E", &["E2", "E2", "F3", "C4"]),
1187 ("A-10/E", &["E3", "Ab3", "Ab3"]),
1188 ("A#10/E", &["E3", "A#3", "A#3"]),
1189 ("C10/G", &["C3", "G3", "C4"]),
1190 ("C35/B", &["B2", "B2", "C3", "E3", "G3"]),
1191 ("C/B-", &["Bb2", "C3", "E3", "G3"]),
1192 ("C7/D", &["D2", "C3", "E3", "G3", "Bb3"]),
1193 ("Cm/E-", &["Eb3", "G3", "C4"]),
1194 ("C9/E", &["E3", "G3", "Bb3", "C4", "D4"]),
1195 ("C13/B-", &["Bb2", "C3", "D3", "E3", "F3", "G3", "A3"]),
1196 ("Cadd9/D", &["D2", "C3", "E3", "G3", "D4"]),
1197 ("C6/D", &["D2", "C3", "E3", "G3", "A3"]),
1198 ("C69/E", &["E3", "A3", "C4", "D4"]),
1199 ("C5/E", &["E3", "G3", "C4"]),
1200 ];
1201 for (figure, voiced) in cases {
1202 let symbol = ChordSymbol::parse_music21(figure).unwrap();
1203 let names: Vec<String> = symbol
1204 .pitches()
1205 .unwrap()
1206 .iter()
1207 .map(Pitch::name_with_octave)
1208 .collect();
1209 assert_eq!(names, voiced, "{figure}");
1210 }
1211 }
1212
1213 #[test]
1217 fn degrees_are_read_as_music21_reads_them() {
1218 type Adds = &'static [(u8, IntegerType)];
1219 let cases: [(&str, &str, Adds); 8] = [
1220 ("Cb-7", "", &[(7, -1)]),
1221 ("Cb+7", "", &[(7, -1)]),
1222 ("C7b-9", "", &[(7, 0), (9, -1)]),
1223 ("C9b-13", "", &[(9, 0), (13, -1)]),
1224 ("C10", "", &[(1, 0)]),
1225 ("C#10", "", &[(1, 0)]),
1226 ("Cb10", "major", &[(1, -1)]),
1227 ("Abb7", "", &[(7, -2)]),
1228 ];
1229 for (figure, kind, adds) in cases {
1230 let symbol = ChordSymbol::parse_music21(figure).unwrap();
1231 assert_eq!(symbol.kind(), Some(kind), "{figure}");
1232 let read: Vec<(u8, IntegerType)> = symbol
1233 .chord_step_modifications()
1234 .iter()
1235 .map(|modification| {
1236 let semitones = modification.interval().semitones() as IntegerType;
1237 (modification.degree(), semitones)
1238 })
1239 .collect();
1240 assert_eq!(read, adds, "{figure}");
1241 }
1242 }
1243
1244 #[test]
1247 fn a_modification_takes_every_accidental_before_its_degree() {
1248 for (figure, semitones) in [("G7addb9", -1), ("G7add-9", -1), ("G7addbb9", -2)] {
1249 let symbol = ChordSymbol::parse_music21(figure).unwrap();
1250 let modification = &symbol.chord_step_modifications()[0];
1251 assert_eq!(modification.degree(), 9, "{figure}");
1252 assert_eq!(
1253 modification.interval().semitones() as IntegerType,
1254 semitones,
1255 "{figure}"
1256 );
1257 }
1258 }
1259
1260 #[test]
1263 fn additions_and_omissions_are_written_as_degrees_that_read_back() {
1264 use super::chord_symbol_figure_from_chord;
1265 for (notes, expected) in [
1266 ("G3 B3 D4 F4 A-4 C5 E-5", "G7addb9add11addb13"),
1267 ("E2 B2 G#3 D4 F##4", "E7add#9"),
1268 ("B-2 F3 A3 D4 E4", "Bbmaj7add#11"),
1269 ("A-2 E-3 G-3 C4 D4 B-4", "Ab9add#11"),
1270 ("A2 E3 A3 B3 C4 E4", "Amadd9"),
1271 ("D4 E4 F#4 A4", "Dadd9"),
1272 ("D3 A3 F#4 E5", "Dadd9"),
1273 ("G B D F A C# E", "C#øb9addb11addb13"),
1274 ("C E G B- D- F#", "C7addb9add#11"),
1275 ("C E G A D", "Am7add11/C"),
1276 ("C# F# B D-", "C#susaddb7addbb9omit5"),
1277 ("D-3 F#3 A-3", "Dbsusadd#3omit4"),
1278 ("F#2 C#3 E-3 A3", "F#m6addbb7omit6"),
1279 ] {
1280 let chord = Chord::new(notes).unwrap();
1281 let figure = chord_symbol_figure_from_chord(&chord).unwrap().unwrap();
1282 assert_eq!(figure, expected, "{notes}");
1283 let respell = |name: &str| {
1286 let mut chars = name.chars();
1287 let Some(first) = chars.next() else {
1288 return String::new();
1289 };
1290 let rest = chars.as_str();
1291 let flats = rest.chars().take_while(|c| *c == 'b').count();
1292 format!("{first}{}{}", "-".repeat(flats), &rest[flats..])
1293 };
1294 let spelled = match figure.split_once('/') {
1295 Some((root, bass)) => format!("{}/{}", respell(root), respell(bass)),
1296 None => respell(&figure),
1297 };
1298 let sounded: BTreeSet<String> = ChordSymbol::parse_music21(spelled)
1299 .unwrap()
1300 .pitches()
1301 .unwrap()
1302 .iter()
1303 .map(Pitch::name)
1304 .collect();
1305 let written: BTreeSet<String> = chord.pitches().iter().map(Pitch::name).collect();
1306 assert_eq!(sounded, written, "{notes}");
1307 }
1308 assert_eq!(super::alter_to_chord_symbol_string(-1), "b");
1309 assert_eq!(super::alter_to_chord_symbol_string(2), "##");
1310 assert_eq!(super::alter_to_chord_symbol_string(0), "");
1311 }
1312
1313 #[test]
1316 fn a_root_with_an_unspellable_accidental_is_an_accidental_error() {
1317 for figure in ["C#-7", "C##-", "F#-7/E"] {
1318 let refused = ChordSymbol::parse_music21(figure).unwrap_err();
1319 assert!(
1320 matches!(refused, Error::Accidental(_)),
1321 "{figure}: {refused:?}"
1322 );
1323 }
1324 assert!(ChordSymbol::parse_music21("H7").is_err());
1325 }
1326 #[test]
1329 fn every_music21_kind_realizes_its_own_notation() {
1330 use super::{ChordSymbol, MUSIC21_CHORD_TYPES};
1331 use crate::pitch::Accidental;
1332 use crate::{Key, Pitch};
1333 use std::collections::BTreeSet;
1334
1335 let major = Key::from_tonic("C").unwrap();
1336 for chord_type in MUSIC21_CHORD_TYPES {
1337 let expected: BTreeSet<String> = chord_type
1338 .notation
1339 .split(',')
1340 .map(|token| {
1341 let degree: usize = token.trim_matches(['#', '-']).parse().unwrap();
1342 let alter =
1343 token.matches('#').count() as f64 - token.matches('-').count() as f64;
1344 let mut pitch = major.pitch_from_degree((degree - 1) % 7 + 1).unwrap();
1345 if alter != 0.0 {
1346 pitch.set_accidental(Accidental::new(alter).unwrap());
1347 }
1348 pitch.name()
1349 })
1350 .collect();
1351 for abbreviation in chord_type.abbreviations {
1352 if music21_kind(abbreviation).is_none_or(|(read, _)| read.kind != chord_type.kind) {
1357 continue;
1358 }
1359 let symbol = ChordSymbol::parse(format!("C{abbreviation}")).unwrap();
1360 assert_eq!(symbol.kind(), Some(chord_type.kind), "C{abbreviation}");
1361 let names: BTreeSet<String> = symbol
1362 .to_chord()
1363 .unwrap()
1364 .pitch_names()
1365 .into_iter()
1366 .collect();
1367 assert_eq!(names, expected, "C{abbreviation} ({})", chord_type.kind);
1368 }
1369 }
1370 assert_eq!(
1373 ChordSymbol::parse("C7#11").unwrap().kind(),
1374 Some("dominant-seventh")
1375 );
1376 assert_eq!(
1377 ChordSymbol::parse("CN6/E")
1378 .unwrap()
1379 .to_chord()
1380 .unwrap()
1381 .pitch_names(),
1382 ["E", "Gb", "C", "Db"]
1383 );
1384 let _ = Pitch::from_name("C").unwrap();
1385 }
1386
1387 #[test]
1390 fn figures_are_written_as_music21_writes_them() {
1391 use super::{
1392 ChordSymbolFigure, chord_symbol_figure_from_chord, chord_symbol_kind_from_chord,
1393 };
1394 use crate::{Chord, Pitch};
1395
1396 let figure =
1397 |notes: &str| chord_symbol_figure_from_chord(&Chord::new(notes).unwrap()).unwrap();
1398 let kind = |notes: &str| chord_symbol_kind_from_chord(&Chord::new(notes).unwrap());
1399 assert_eq!(figure("F3 A3 C#4 E-4 G4 B-4").as_deref(), Some("F+11"));
1400 assert_eq!(kind("F3 A3 C#4 E-4 G4 B-4"), Some("augmented-11th"));
1401 assert_eq!(figure("C3 E3 B3 D4").as_deref(), Some("CM9"));
1402 assert_eq!(figure("C3 D-3 E3 G-3").as_deref(), Some("CN6"));
1403 assert_eq!(kind("C3 D-3 E3 G-3"), Some("Neapolitan"));
1404 assert_eq!(figure("C3 D3 G3").as_deref(), Some("Csus2"));
1405 assert_eq!(figure("C3 E3 G3 D-4").as_deref(), Some("Caddb9"));
1406 assert_eq!(figure("C3").as_deref(), Some("Cpedal"));
1407 assert_eq!(figure("").as_deref(), Some(""));
1408
1409 let inverted = Chord::new("C3 F3 G3").unwrap();
1411 let parts = ChordSymbolFigure::from_chord(&inverted).unwrap();
1412 assert_eq!(parts.root, "C");
1413 assert_eq!(
1414 (parts.kind, parts.abbreviation, parts.bass.as_deref()),
1415 ("suspended-fourth", "sus", None)
1416 );
1417 assert_eq!(parts.to_string(), "Csus");
1418 assert_eq!(parts.written_with("sus4"), "Csus4");
1419
1420 let with_root = |notes: &str, root: &str| {
1422 ChordSymbolFigure::from_chord_with_root(
1423 &Chord::new(notes).unwrap(),
1424 &Pitch::from_name(root).unwrap(),
1425 )
1426 .unwrap()
1427 };
1428 assert_eq!(with_root("C3 F#3 A-3", "C3").to_string(), "CIt+6");
1429 assert_eq!(with_root("C3 D3 F#3 A-3", "C3").to_string(), "CFr+6");
1430 assert_eq!(with_root("C3 E-3 F#3 A-3", "C3").to_string(), "CGr+6");
1431 assert_eq!(with_root("F2 B2 D#3 G#3", "F2").to_string(), "Ftristan");
1432 assert_eq!(with_root("F2 B2 D#3 G#3", "F2").kind, "Tristan");
1433
1434 let parts = ChordSymbolFigure::from_chord(&Chord::new("E3 G3 C4 B-4").unwrap()).unwrap();
1437 assert_eq!(parts.to_string(), "C7/E");
1438 assert_eq!(figure("D3 C4 E-4 G4").as_deref(), Some("Cm/D"));
1440 assert!(ChordSymbolFigure::from_chord(&Chord::new("C4 C~4").unwrap()).is_none());
1441 assert!(ChordSymbolFigure::from_chord(&Chord::new("").unwrap()).is_none());
1442 }
1443
1444 #[test]
1445 fn a_symbol_is_parsed_through_try_from_and_reports_its_alterations() {
1446 use super::{ChordSymbol, chord_symbol_kind_from_chord};
1447 use crate::chord::Chord;
1448
1449 let symbol = ChordSymbol::try_from("C7#11").unwrap();
1450 assert_eq!(symbol.alterations().len(), 1);
1451 assert_eq!(symbol.alterations()[0].degree(), 11);
1452 assert_eq!(symbol.alterations()[0].semitones(), 1);
1453 assert!(symbol.omissions().is_empty());
1454 let omitting = ChordSymbol::try_from("C[no3]".to_string()).unwrap();
1455 assert_eq!(omitting.omissions(), [3]);
1456 assert!(ChordSymbol::try_from("").is_err());
1457
1458 assert_eq!(
1459 chord_symbol_kind_from_chord(&Chord::new("C").unwrap()),
1460 Some("pedal")
1461 );
1462 assert_eq!(
1463 chord_symbol_kind_from_chord(&Chord::new("C G").unwrap()),
1464 Some("power")
1465 );
1466 assert_eq!(
1467 chord_symbol_kind_from_chord(&Chord::new("C E G").unwrap()),
1468 Some("major")
1469 );
1470 assert_eq!(chord_symbol_kind_from_chord(&Chord::new("").unwrap()), None);
1471 }
1472
1473 #[test]
1474 fn chord_symbol_figures_match_music21() {
1475 let cases: [(&str, Option<&str>); 26] = [
1476 ("C4 E4 G4", Some("C")),
1477 ("C4 E-4 G4", Some("Cm")),
1478 ("C4 E4 G#4", Some("C+")),
1479 ("C4 E-4 G-4", Some("Cdim")),
1480 ("C4 E4 G4 B-4", Some("C7")),
1481 ("C4 E4 G4 B4", Some("Cmaj7")),
1482 ("C4 E-4 G4 B-4", Some("Cm7")),
1483 ("C4 E-4 G-4 B--4", Some("Co7")),
1484 ("C4 E-4 G-4 B-4", Some("C\u{00f8}7")),
1485 ("E4 G4 C5", Some("C/E")),
1486 ("G3 C4 E4", Some("C/G")),
1487 ("E4 G4 B-4 C5", Some("C7/E")),
1488 ("C4 E4 G4 B-4 D5", Some("C9")),
1489 ("C4 E4 G4 B4 D5", Some("CM9")),
1490 ("C4 D4 G4", Some("Csus2")),
1491 ("C4 F4 G4", Some("Csus")),
1492 ("C4", Some("Cpedal")),
1493 ("C4 G4", Some("Cpower")),
1494 ("C4 E4 G4 B-4 D5 F5", Some("C11")),
1495 ("C4 E4 G4 B-4 D5 F5 A5", Some("C13")),
1496 ("C4 E4 G4 A4", Some("Am7/C")),
1497 ("C4 E-4 G4 A4", Some("A\u{00f8}7/C")),
1498 ("C4 E4 G4 D5", Some("Cadd9")),
1499 ("F4 A4 C5 D5", Some("Dm7/F")),
1500 ("B3 D4 F4", Some("Bdim")),
1501 ("C4 D-4 E4", None),
1502 ];
1503 for (notes, expected) in cases {
1504 let chord = Chord::new(notes).unwrap();
1505 assert_eq!(
1506 chord_symbol_figure_from_chord(&chord).unwrap().as_deref(),
1507 expected,
1508 "{notes}"
1509 );
1510 }
1511 assert_eq!(
1512 chord_symbol_figure_from_chord(&Chord::empty())
1513 .unwrap()
1514 .as_deref(),
1515 Some("")
1516 );
1517
1518 let symbol = chord_symbol_from_chord(&Chord::new("E4 G4 B-4 C5").unwrap())
1519 .unwrap()
1520 .unwrap();
1521 assert_eq!(symbol.figure(), "C7/E");
1522 assert_eq!(symbol.root().name(), "C");
1523 assert_eq!(symbol.bass().map(Pitch::name).as_deref(), Some("E"));
1524 assert!(
1525 chord_symbol_from_chord(&Chord::new("C4 D-4 E4").unwrap())
1526 .unwrap()
1527 .is_none()
1528 );
1529 assert!(chord_symbol_from_chord(&Chord::empty()).unwrap().is_none());
1530 }
1531
1532 #[test]
1533 fn kind_lookups_match_music21() {
1534 assert_eq!(
1535 abbreviations_for_kind("dominant-seventh"),
1536 Some(&["7", "dom7"][..])
1537 );
1538 assert_eq!(notation_for_kind("dominant-seventh"), Some("1,3,5,-7"));
1539 assert_eq!(current_abbreviation_for_kind("dominant-seventh"), Some("7"));
1540 assert_eq!(abbreviations_for_kind("major"), Some(&["", "M", "maj"][..]));
1541 assert_eq!(current_abbreviation_for_kind("major"), Some(""));
1542 assert_eq!(abbreviations_for_kind("nonsense"), None);
1543 for chord_type in known_chord_symbol_types() {
1544 assert_eq!(
1545 chord_type.abbreviations.first(),
1546 Some(&chord_type.abbreviation)
1547 );
1548 }
1549 }
1550
1551 #[test]
1552 fn find_figure_writes_one_canonical_spelling_that_parses_back() {
1553 let cases = [
1554 ("C", "C", "D"),
1555 ("Cm7", "Cm7", "Dm7"),
1556 ("F#dim", "F#dim", "G#dim"),
1557 ("Bb/D", "Bb/D", "C/E"),
1558 ("G7/B", "G7/B", "A7/C#"),
1559 ("Am/C", "Am/C", "Bm/D"),
1560 ("Cmaj7", "Cmaj7", "Dmaj7"),
1561 ("Dm7b5", "Dm7b5", "Em7b5"),
1562 ("C\u{00f8}7", "Cm7b5", "Dm7b5"),
1563 ("E7#9", "E7#9", "F#7#9"),
1564 ("Csus4", "Csus4", "Dsus4"),
1565 ("G7sus4", "G7sus4", "A7sus4"),
1566 ("Cadd(9)", "Cadd(9)", "Dadd(9)"),
1567 ("C6", "C6", "D6"),
1568 ("Cm6", "Cm6", "Dm6"),
1569 ("Cdim7", "Cdim7", "Ddim7"),
1570 ("Caug", "Caug", "Daug"),
1571 ("C+", "Caug", "Daug"),
1572 ("C9", "C9", "D9"),
1573 ("Cmaj7#11", "Cmaj7#11", "Dmaj7#11"),
1574 ("C5", "C5", "D5"),
1575 ("Cno3", "Cno3", "Dno3"),
1576 ];
1577 let whole_tone = Interval::from_name("M2").unwrap();
1578 for (typed, canonical, transposed) in cases {
1579 let symbol = ChordSymbol::parse(typed).unwrap();
1580 assert_eq!(symbol.find_figure(), canonical, "{typed}");
1581 let reparsed = ChordSymbol::parse(symbol.find_figure()).unwrap();
1582 assert_eq!(reparsed.quality(), symbol.quality(), "{typed}");
1583 assert_eq!(
1584 reparsed.to_chord().unwrap().pitch_names(),
1585 symbol.to_chord().unwrap().pitch_names(),
1586 "{typed}"
1587 );
1588 let up = symbol.transpose(&whole_tone).unwrap();
1589 assert_eq!(up.figure(), transposed, "{typed}");
1590 assert_eq!(up.find_figure(), transposed, "{typed}");
1591 }
1592 }
1593
1594 #[test]
1595 fn inversion_validity_follows_the_chord_size() {
1596 let valid = |figure: &str| -> Vec<u8> {
1597 let symbol = ChordSymbol::parse(figure).unwrap();
1598 (0..=6).filter(|n| symbol.inversion_is_valid(*n)).collect()
1599 };
1600 assert_eq!(valid("C"), [1, 2]);
1601 assert_eq!(valid("C6"), [1, 2]);
1602 assert_eq!(valid("Cm7"), [1, 2, 3]);
1603 assert_eq!(valid("C9"), [1, 2, 3, 4]);
1604 assert_eq!(valid("C13"), [1, 2, 3, 4, 5]);
1605 assert_eq!(valid("E7#9"), [1, 2, 3]);
1608 assert_eq!(valid("Cmaj11"), [1, 2, 3, 4, 5]);
1611 }
1612 use super::*;
1613
1614 #[test]
1615 fn parses_major_seventh_symbol() {
1616 let symbol: ChordSymbol = "Cmaj7".parse().unwrap();
1617 assert_eq!(symbol.root().name(), "C");
1618 assert_eq!(symbol.quality(), ChordQuality::Major);
1619 assert_eq!(symbol.extensions(), &[7]);
1620 assert_eq!(
1621 symbol.to_chord().unwrap().pitched_common_name(),
1622 "C-major seventh chord"
1623 );
1624 }
1625
1626 #[test]
1627 fn parses_half_diminished_symbol() {
1628 let symbol = ChordSymbol::parse("F#m7b5").unwrap();
1629 assert_eq!(symbol.root().name(), "F#");
1630 assert_eq!(symbol.quality(), ChordQuality::HalfDiminished);
1631 assert_eq!(
1632 symbol.to_chord().unwrap().pitched_common_name(),
1633 "F#-half-diminished seventh chord"
1634 );
1635 }
1636
1637 #[test]
1638 fn parses_dominant_altered_symbol() {
1639 let symbol = ChordSymbol::parse("Bb7#11").unwrap();
1640 assert_eq!(symbol.root().name(), "Bb");
1641 assert_eq!(symbol.quality(), ChordQuality::Dominant);
1642 assert_eq!(symbol.extensions(), &[7, 11]);
1643 assert_eq!(symbol.alterations()[0], ChordAlteration::new(11, 1));
1644 let names = symbol
1645 .to_chord()
1646 .unwrap()
1647 .pitches()
1648 .iter()
1649 .map(Pitch::name)
1650 .collect::<Vec<_>>();
1651 assert_eq!(names, vec!["Bb", "D", "F", "Ab", "E"]);
1652 }
1653
1654 #[test]
1655 fn parses_added_tones_without_changing_the_base_chord() {
1656 let symbol = ChordSymbol::parse("Cdim9 add(#5)").unwrap();
1657 assert_eq!(symbol.extensions(), &[9]);
1658 assert_eq!(symbol.additions(), &[ChordAlteration::new(5, 1)]);
1659 assert_eq!(
1660 symbol.to_chord().unwrap().pitch_classes(),
1661 vec![0, 2, 3, 6, 8, 9]
1662 );
1663 }
1664
1665 #[test]
1666 fn parses_altered_dominant_with_slash_bass() {
1667 let symbol = ChordSymbol::parse("D7b9#11/C").unwrap();
1668 assert_eq!(symbol.root().name(), "D");
1669 assert_eq!(symbol.bass().map(Pitch::name).as_deref(), Some("C"));
1670 assert_eq!(symbol.quality(), ChordQuality::Dominant);
1671 assert_eq!(symbol.extensions(), &[7, 9, 11]);
1672 assert_eq!(
1673 symbol.alterations(),
1674 &[ChordAlteration::new(9, -1), ChordAlteration::new(11, 1)]
1675 );
1676 assert_eq!(
1677 symbol.to_chord().unwrap().pitch_classes(),
1678 vec![0, 2, 3, 6, 8, 9]
1679 );
1680 }
1681
1682 #[test]
1683 fn parses_music21_pitch_name_additions() {
1684 let symbol = ChordSymbol::parse("Ddom7dim5/CaddA,E-").unwrap();
1685
1686 assert_eq!(symbol.root().name(), "D");
1687 assert_eq!(symbol.bass().map(Pitch::name).as_deref(), Some("C"));
1688 assert_eq!(symbol.quality(), ChordQuality::Dominant);
1689 assert_eq!(symbol.alterations(), &[ChordAlteration::new(5, -1)]);
1690 assert_eq!(
1691 symbol.additions(),
1692 &[ChordAlteration::new(5, 0), ChordAlteration::new(9, -1)]
1693 );
1694 assert_eq!(
1695 symbol.to_chord().unwrap().pitch_classes(),
1696 vec![0, 2, 3, 6, 8, 9]
1697 );
1698 }
1699
1700 #[test]
1701 fn generates_petrushka_chord_symbol_name() {
1702 let chord = Chord::new("C4 D4 Eb4 F#4 Ab4 A4").unwrap();
1703 let names = chord_symbol_spellings(&chord);
1704
1705 assert_eq!(
1706 names.first().map(String::as_str),
1707 Some("Ddom7dim5add5addb9/C")
1708 );
1709 }
1710
1711 #[test]
1712 fn generates_common_chord_symbols() {
1713 let major_seventh = Chord::new("C E G B").unwrap();
1714 let dominant_ninth = Chord::new("C E G B- D").unwrap();
1715
1716 assert_eq!(
1717 chord_symbol_spellings(&major_seventh)
1718 .first()
1719 .map(String::as_str),
1720 Some("Cmaj7")
1721 );
1722 assert_eq!(
1723 chord_symbol_spellings(&dominant_ninth)
1724 .first()
1725 .map(String::as_str),
1726 Some("C9")
1727 );
1728 }
1729
1730 #[test]
1731 fn split_third_triads_do_not_spell_lower_third_as_sharp_nine() {
1732 let split_third = Chord::new("D4 A4 F#4 F4").unwrap();
1733 let names = chord_symbol_spellings(&split_third);
1734
1735 assert_eq!(names.first().map(String::as_str), Some("Daddb3"));
1736 assert!(!names.iter().any(|name| name == "D add(#9)"));
1737 }
1738
1739 #[test]
1740 fn altered_dominants_write_additions_as_degrees() {
1741 let altered_dominant = Chord::new("C4 E4 G4 Bb4 Eb5").unwrap();
1742 let names = chord_symbol_spellings(&altered_dominant);
1743
1744 assert_eq!(names.first().map(String::as_str), Some("C7addb3"));
1745 }
1746
1747 #[test]
1748 fn unrecognized_music21_figures_return_no_symbol() {
1749 let chord = Chord::new("F4 C5 D5 E-5").unwrap();
1750 let names = chord_symbol_spellings(&chord);
1751
1752 assert!(names.is_empty());
1753 }
1754
1755 #[test]
1756 fn generates_music21_figures_with_explicit_root() {
1757 let major_triad = Chord::new("G3 C4 E4").unwrap();
1758 let dominant_seventh = Chord::new("G3 B-3 C4 E4").unwrap();
1759 let power_chord = Chord::new("C4 G4").unwrap();
1760 let unsupported_dyad = Chord::new("C4 A4").unwrap();
1761
1762 assert_eq!(
1763 chord_symbol_spellings_with_root(&major_triad, 0)
1764 .first()
1765 .map(String::as_str),
1766 Some("C/G")
1767 );
1768 assert_eq!(
1769 chord_symbol_spellings_with_root(&dominant_seventh, 0)
1770 .first()
1771 .map(String::as_str),
1772 Some("C7/G")
1773 );
1774 assert_eq!(
1775 chord_symbol_spellings_with_root(&power_chord, 0)
1776 .first()
1777 .map(String::as_str),
1778 Some("Cpower")
1779 );
1780 assert!(chord_symbol_spellings_with_root(&unsupported_dyad, 0).is_empty());
1781 }
1782
1783 #[test]
1784 fn dense_sets_follow_music21_fallback_matching() {
1785 let chord = Chord::new("C4 D-4 E-4 E4 F#4 G4 A-4 A4").unwrap();
1786
1787 assert_eq!(
1788 chord_symbol_spellings(&chord).first().map(String::as_str),
1789 Some("Csusaddb3add3add#4addb9addb13add13omit4")
1790 );
1791 }
1792
1793 #[test]
1796 fn chord_symbols_hold_until_the_next_one_across_bars() {
1797 use crate::{Note, Stream, StreamElement, StreamKind};
1798
1799 let bar = |figure: &str, at: FloatType| {
1800 let mut measure = Stream::with_kind(StreamKind::Measure);
1801 measure.insert(at, ChordSymbol::parse(figure).unwrap());
1802 for _ in 0..4 {
1805 measure.push(Note::from_name("C4").unwrap());
1806 }
1807 measure
1808 };
1809 let held = |stream: &Stream| -> Vec<FloatType> {
1810 stream
1811 .flatten()
1812 .events()
1813 .iter()
1814 .filter(|event| matches!(event.element(), StreamElement::ChordSymbol(_)))
1815 .map(|event| event.element().quarter_length())
1816 .collect()
1817 };
1818
1819 let mut part = Stream::with_kind(StreamKind::Part);
1820 part.insert(0.0, bar("C", 0.0));
1821 part.insert(4.0, bar("G7", 1.5));
1822 part.insert(8.0, bar("Am", 2.0));
1823 assert_eq!(held(&part), [0.0, 0.0, 0.0]);
1824 realize_chord_symbol_durations(&mut part);
1825 assert_eq!(held(&part), [5.5, 4.5, 4.0]);
1826
1827 let mut stream = Stream::new();
1829 stream.insert(1.0, ChordSymbol::parse("C").unwrap());
1830 for beat in 1..7 {
1831 stream.insert(FloatType::from(beat), Note::from_name("C4").unwrap());
1832 }
1833 realize_chord_symbol_durations(&mut stream);
1834 assert_eq!(held(&stream), [6.0]);
1835
1836 let mut bare = Stream::new();
1838 bare.push(Note::from_name("C4").unwrap());
1839 realize_chord_symbol_durations(&mut bare);
1840 assert_eq!(bare.len(), 1);
1841 }
1842}