use crate::chord::{
interval::{FIFTHS_SET, IntDegree, IntDegreeSet, Interval, IntervalSet, THIRDS_SET},
quality::{ChordQuality, Pc, PcSet},
};
use ChordQuality::*;
const MI: &str = "mi";
const MA: &str = "Ma";
const MA7: &str = "Ma7";
const MI7: &str = "mi7";
const MIMA7: &str = "miMa7";
const MIMA: &str = "miMa";
const AUG: &str = "+";
const DIM: &str = "dim";
const DIM7: &str = "dim7";
const FIVE: &str = "5";
const SIX: &str = "6";
const SEVEN: &str = "7";
const NINE: &str = "9";
pub(crate) fn normalized_descriptor(interval_set: IntervalSet, quality: ChordQuality) -> String {
let mut descriptor = String::with_capacity(64);
let pitch_set: PcSet = interval_set.into();
if quality == ChordQuality::Bass {
descriptor.push_str("Bass");
return descriptor;
}
let is_sus = quality.is_sus(&pitch_set);
let alterations = quality.alterations(&interval_set);
let extensions = quality
.extensions(&interval_set)
.replace(Interval::MajorSixth, Interval::Thirteenth);
let (modifier, mut adds) = split_extensions(&extensions, &alterations, &quality);
let (omits_list, omits_count) = omits(interval_set, is_sus, &quality);
append_quality_modifier(&mut descriptor, &quality, modifier);
if is_sus {
if interval_set.contains(Interval::MajorThird) {
adds.push(Interval::MajorThird);
}
descriptor.push_str("sus");
}
let mut open_paren = false;
let ensure_paren = |d: &mut String, has_p: &mut bool| {
if !*has_p {
d.push('(');
*has_p = true;
} else {
d.push(',');
}
};
for alt in alterations {
ensure_paren(&mut descriptor, &mut open_paren);
descriptor.push_str(alt.to_chord_notation());
}
for (i, add) in adds.iter().enumerate() {
if *add == Interval::Ninth && (quality == Maj6 || quality == Mi6) {
if let Some(pos) = descriptor.find('6') {
descriptor.insert_str(pos + 1, NINE);
}
continue;
}
ensure_paren(&mut descriptor, &mut open_paren);
if i == 0 {
descriptor.push_str("add");
}
descriptor.push_str(add.to_chord_notation());
}
for (i, omit) in omits_list.iter().enumerate().take(omits_count) {
if let Some(omit_str) = omit {
ensure_paren(&mut descriptor, &mut open_paren);
if i == 0 {
descriptor.push_str("omit");
}
descriptor.push_str(omit_str);
}
}
if open_paren {
descriptor.push(')');
}
descriptor
}
fn omits(
interval_set: IntervalSet,
is_sus: bool,
quality: &ChordQuality,
) -> ([Option<&'static str>; 2], usize) {
let mut res = [None; 2];
let mut count = 0;
if matches!(quality, ChordQuality::Bass | ChordQuality::Power) {
return (res, 0);
}
if !is_sus && interval_set.intersection(&THIRDS_SET).is_empty() {
res[count] = Some("3");
count += 1;
}
if interval_set.intersection(&FIFTHS_SET).is_empty()
&& !interval_set.contains(Interval::FlatThirteenth)
{
res[count] = Some("5");
count += 1;
}
(res, count)
}
fn split_extensions(
extensions: &IntervalSet,
alterations: &IntervalSet,
quality: &ChordQuality,
) -> (Option<Interval>, Vec<Interval>) {
if matches!(
quality,
ChordQuality::Diminished7 | ChordQuality::Diminished
) {
return (None, extensions.iter().collect());
}
let mut adds: Vec<Interval> = vec![];
let mut main: Option<Interval> = None;
let degrees = extensions_to_degrees(alterations, extensions, quality);
for curr in extensions.iter() {
if matches!(curr, Interval::MajorSeventh) {
adds.push(curr);
continue;
}
let stack: IntDegreeSet = quality
.extension_stack()
.iter()
.filter(|ext| ext <= &curr.into())
.collect();
let pc: Pc = (&curr).into();
if stack.is_subset_of(°rees) {
main = Some(curr);
} else if !quality.self_mask().contains(pc) {
adds.push(curr);
}
}
(main, adds)
}
fn extensions_to_degrees(
alterations: &IntervalSet,
extensions: &IntervalSet,
quality: &ChordQuality,
) -> IntDegreeSet {
let seventh = match quality {
Diminished7 | Dominant7 | Maj7 | Mi7 | MiMaj7 => Some(IntDegree::Seventh),
_ => None,
};
let alt_degrees: IntDegreeSet = alterations.into();
let ext_degrees: IntDegreeSet =
(&extensions.replace(Interval::MajorSixth, Interval::Thirteenth)).into();
let mut res = alt_degrees.union(&ext_degrees);
if let Some(seventh) = seventh {
res.insert(seventh);
}
res
}
fn append_quality_modifier(f: &mut String, quality: &ChordQuality, modifier: Option<Interval>) {
match quality {
Maj | Bass => {}
Maj6 => match modifier {
None => f.push_str(SIX),
Some(m) => {
f.push_str(SIX);
f.push_str(m.to_chord_notation());
}
},
Maj7 => match modifier {
None => f.push_str(MA7),
Some(m) => {
f.push_str(MA);
f.push_str(m.to_chord_notation());
}
},
Dominant7 => {
let modstring = modifier.map_or(SEVEN, |m| m.to_chord_notation());
f.push_str(modstring);
}
Mi => f.push_str(MI),
Mi6 => {
f.push_str(MI);
f.push_str(SIX);
}
Mi7 => match modifier {
None => f.push_str(MI7),
Some(m) => {
f.push_str(MI);
f.push_str(m.to_chord_notation());
}
},
MiMaj7 => match modifier {
None => f.push_str(MIMA7),
Some(m) => {
f.push_str(MIMA);
f.push_str(m.to_chord_notation());
}
},
Augmented => {
f.push_str(AUG);
if let Some(m) = modifier {
f.push_str(m.to_chord_notation());
}
}
Diminished => f.push_str(DIM),
Diminished7 => f.push_str(DIM7),
Power => f.push_str(FIVE),
}
}