1use crate::{
15 defaults::IntegerType,
16 error::{Error, Result},
17 interval::Interval,
18 pitch::Pitch,
19 scale::{Scale, ScaleType},
20};
21
22use super::tables::notation_intervals;
23
24#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27pub enum ChordStepModificationType {
28 Add,
31 Subtract,
33 Alter,
35}
36
37impl ChordStepModificationType {
38 #[must_use]
40 pub fn music21_name(self) -> &'static str {
41 match self {
42 Self::Add => "add",
43 Self::Subtract => "subtract",
44 Self::Alter => "alter",
45 }
46 }
47
48 pub fn from_music21_name(name: &str) -> Result<Self> {
50 match name.to_ascii_lowercase().as_str() {
51 "add" => Ok(Self::Add),
52 "subtract" => Ok(Self::Subtract),
53 "alter" => Ok(Self::Alter),
54 _ => Err(Error::Chord(format!(
55 "not a valid degree modification type: {name}"
56 ))),
57 }
58 }
59}
60
61#[derive(Clone, Debug, PartialEq)]
69#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
70pub struct ChordStepModification {
71 modification_type: ChordStepModificationType,
72 degree: u8,
73 interval: Interval,
74}
75
76impl ChordStepModification {
77 pub fn new(
79 modification_type: ChordStepModificationType,
80 degree: u8,
81 alter: IntegerType,
82 ) -> Result<Self> {
83 Ok(Self {
84 modification_type,
85 degree,
86 interval: interval_of_alteration(alter)?,
87 })
88 }
89
90 pub fn with_interval(
92 modification_type: ChordStepModificationType,
93 degree: u8,
94 interval: Interval,
95 ) -> Self {
96 Self {
97 modification_type,
98 degree,
99 interval,
100 }
101 }
102
103 #[must_use]
105 pub fn modification_type(&self) -> ChordStepModificationType {
106 self.modification_type
107 }
108
109 #[must_use]
111 pub fn degree(&self) -> u8 {
112 self.degree
113 }
114
115 pub fn interval(&self) -> &Interval {
117 &self.interval
118 }
119}
120
121fn interval_of_alteration(alter: IntegerType) -> Result<Interval> {
125 let size = alter.unsigned_abs();
126 if size > 3 {
127 return Interval::from_semitones(alter);
128 }
129 let mut name = if size == 0 {
130 "P".to_string()
131 } else {
132 "a".repeat(size as usize)
133 };
134 name.push('1');
135 if alter < 0 {
136 name.push('-');
137 }
138 Interval::from_name(&name)
139}
140
141#[derive(Clone, Debug)]
144pub(super) struct Realized {
145 pub(super) pitches: Vec<Pitch>,
146 root: Pitch,
147 bass: Pitch,
148}
149
150#[derive(Clone, Debug, PartialEq)]
156pub struct ChordVoicing {
157 pitches: Vec<Pitch>,
158 root: Pitch,
159 bass: Pitch,
160}
161
162impl ChordVoicing {
163 pub fn pitches(&self) -> &[Pitch] {
165 &self.pitches
166 }
167
168 pub fn root(&self) -> &Pitch {
170 &self.root
171 }
172
173 pub fn bass(&self) -> &Pitch {
175 &self.bass
176 }
177}
178
179pub(super) fn inversion_between(root: &Pitch, bass: &Pitch) -> Result<Option<u8>> {
185 let steps = (root.diatonic_note_number() - bass.diatonic_note_number()).rem_euclid(7) + 1;
188 Ok(match steps {
189 1 => Some(0),
190 6 => Some(1),
191 4 => Some(2),
192 2 => Some(3),
193 7 => Some(4),
194 5 => Some(5),
195 3 => Some(6),
196 _ => None,
197 })
198}
199
200pub fn inversion_is_valid_for_kind(kind: &str, inversion: u8) -> bool {
204 const SEVENTHS: [&str; 12] = [
205 "French",
206 "German",
207 "Italian",
208 "Neapolitan",
209 "Tristan",
210 "augmented-seventh",
211 "diminished-seventh",
212 "dominant-seventh",
213 "half-diminished",
214 "major-minor",
215 "major-seventh",
216 "minor-seventh",
217 ];
218 const NINTHS: [&str; 3] = ["dominant-ninth", "major-ninth", "minor-ninth"];
219 const ELEVENTHS: [&str; 3] = ["dominant-11th", "major-11th", "minor-11th"];
220 const THIRTEENTHS: [&str; 3] = ["dominant-13th", "major-13th", "minor-13th"];
221 let within = |list: &[&str]| list.contains(&kind);
222 match inversion {
223 5 => within(&THIRTEENTHS) || within(&ELEVENTHS),
224 4 => within(&ELEVENTHS) || within(&THIRTEENTHS) || within(&NINTHS),
225 3 => within(&SEVENTHS) || within(&NINTHS) || within(&ELEVENTHS) || within(&THIRTEENTHS),
226 1 | 2 => kind != "pedal",
227 _ => false,
228 }
229}
230
231pub fn sound_chord_kind(
238 root: &Pitch,
239 kind: &str,
240 bass: Option<&Pitch>,
241 modifications: &[ChordStepModification],
242) -> Result<Vec<Pitch>> {
243 let notation = super::tables::notation_for_kind(super::tables::resolve_kind_alias(kind));
247 sound_chord_notation(root, kind, notation, bass, modifications)
248}
249
250pub fn sound_chord_notation(
255 root: &Pitch,
256 kind: &str,
257 notation: Option<&str>,
258 bass: Option<&Pitch>,
259 modifications: &[ChordStepModification],
260) -> Result<Vec<Pitch>> {
261 Ok(realize(root, bass, Some(kind), notation, None, modifications)?.pitches)
262}
263
264pub fn voice_chord_notation(
267 root: &Pitch,
268 kind: &str,
269 notation: Option<&str>,
270 bass: Option<&Pitch>,
271 modifications: &[ChordStepModification],
272) -> Result<ChordVoicing> {
273 let realized = realize(root, bass, Some(kind), notation, None, modifications)?;
274 Ok(ChordVoicing {
275 pitches: realized.pitches,
276 root: realized.root,
277 bass: realized.bass,
278 })
279}
280
281fn named(name: &str, octave: IntegerType) -> Result<Pitch> {
283 let mut pitch = Pitch::from_name(name)?;
284 pitch.set_octave(Some(octave));
285 Ok(pitch)
286}
287
288fn octave_of(pitch: &Pitch) -> IntegerType {
289 pitch
290 .octave()
291 .unwrap_or(crate::defaults::PITCH_OCTAVE as IntegerType)
292}
293
294fn shift_octave(pitch: &mut Pitch, by: IntegerType) {
295 let octave = octave_of(pitch);
296 pitch.set_octave(Some(octave + by));
297}
298
299fn sort_diatonic(pitches: &mut [Pitch]) {
302 pitches.sort_by(|left, right| {
303 left.diatonic_note_number()
304 .cmp(&right.diatonic_note_number())
305 .then(left.ps().total_cmp(&right.ps()))
306 });
307}
308
309fn sample_pitches(root: &Pitch, names: &[String]) -> Result<Vec<Pitch>> {
317 let top = root.transpose(&Interval::from_name("d8")?)?;
318 let highest_octave = octave_of(&top);
319 let mut order = vec![root.name()];
320 order.extend(names.iter().rev().cloned());
321 let mut placed = Vec::new();
322 for name in &order {
323 for octave in 0..=highest_octave {
324 let candidate = named(name, octave)?;
325 if candidate.ps() >= root.ps() && candidate.ps() <= top.ps() {
326 placed.push(candidate);
327 }
328 }
329 }
330 placed.sort_by(|left, right| left.ps().total_cmp(&right.ps()));
331 if !placed.is_empty() {
332 let _ = placed.remove(0);
333 }
334 Ok(placed)
335}
336
337#[derive(Clone, Copy, PartialEq)]
349enum Slot {
350 Root,
351 Bass,
352 Own(usize),
353}
354
355struct Voicing {
364 root: Pitch,
365 bass: Pitch,
366 own: Vec<Pitch>,
367 slots: Vec<Slot>,
368}
369
370impl Voicing {
371 fn owning(root: Pitch, bass: Pitch, pitches: Vec<Pitch>) -> Self {
373 let slots = (0..pitches.len()).map(Slot::Own).collect();
374 Self {
375 root,
376 bass,
377 own: pitches,
378 slots,
379 }
380 }
381
382 fn pitch(&self, slot: Slot) -> &Pitch {
383 match slot {
384 Slot::Root => &self.root,
385 Slot::Bass => &self.bass,
386 Slot::Own(index) => &self.own[index],
387 }
388 }
389
390 fn pitch_mut(&mut self, slot: Slot) -> &mut Pitch {
391 match slot {
392 Slot::Root => &mut self.root,
393 Slot::Bass => &mut self.bass,
394 Slot::Own(index) => &mut self.own[index],
395 }
396 }
397
398 fn values(&self) -> Vec<Pitch> {
400 self.slots
401 .iter()
402 .map(|slot| self.pitch(*slot).clone())
403 .collect()
404 }
405
406 fn shift(&mut self, index: usize, by: IntegerType) {
408 let slot = self.slots[index];
409 shift_octave(self.pitch_mut(slot), by);
410 }
411
412 fn shift_all(&mut self, by: IntegerType) {
415 for index in 0..self.slots.len() {
416 self.shift(index, by);
417 }
418 }
419
420 fn replace(&mut self, pitches: Vec<Pitch>) {
425 let old = std::mem::take(&mut self.slots);
426 let mut remaining = old.into_iter().peekable();
427 for pitch in pitches {
428 while remaining
430 .peek()
431 .is_some_and(|slot| *self.pitch(*slot) != pitch)
432 {
433 remaining.next();
434 }
435 match remaining.next() {
436 Some(slot) => self.slots.push(slot),
437 None => {
438 self.slots.push(Slot::Own(self.own.len()));
439 self.own.push(pitch);
440 }
441 }
442 }
443 }
444}
445
446pub(super) fn realize(
447 root: &Pitch,
448 bass: Option<&Pitch>,
449 kind: Option<&str>,
450 notation: Option<&str>,
451 names: Option<Vec<String>>,
452 modifications: &[ChordStepModification],
453) -> Result<Realized> {
454 let root = named(&root.name(), 3)?;
455 let bass = named(&bass.unwrap_or(&root).name(), 3)?;
456 let kind = kind.unwrap_or("");
457
458 let mut degrees: Vec<String> = notation
459 .map(|notation| notation.split(',').map(str::to_string).collect())
460 .unwrap_or_default();
461 let mut voicing = match (&names, notation) {
462 (Some(names), _) => {
463 Voicing::owning(root.clone(), bass.clone(), sample_pitches(&root, names)?)
464 }
465 (None, Some(notation)) => {
466 let names = notation_intervals(notation)?
467 .into_iter()
468 .map(|(_, name)| Ok(Interval::from_name(&name)?.transpose_pitch(&root)?.name()))
469 .collect::<Result<Vec<_>>>()?;
470 Voicing::owning(root.clone(), bass.clone(), sample_pitches(&root, &names)?)
471 }
472 (None, None) => {
475 let mut slots = vec![Slot::Root];
476 if bass != root {
477 slots.push(Slot::Bass);
478 }
479 Voicing {
480 root: root.clone(),
481 bass: bass.clone(),
482 own: Vec::new(),
483 slots,
484 }
485 }
486 };
487
488 let lifted: &[usize] = match kind {
492 "dominant-ninth" | "major-ninth" | "minor-ninth" => &[1],
493 "dominant-11th" | "major-11th" | "minor-11th" => &[1, 3],
494 "dominant-13th" | "major-13th" | "minor-13th" => &[1, 3, 5],
495 _ => &[],
496 };
497 if !lifted.is_empty() {
498 let mut pitches = voicing.values();
499 for &index in lifted {
500 if let Some(pitch) = pitches.get_mut(index) {
501 shift_octave(pitch, 1);
502 }
503 }
504 sort_diatonic(&mut pitches);
505 voicing = Voicing::owning(voicing.root, voicing.bass, pitches);
506 }
507
508 let mut inversion = None;
509 if voicing.root.name() != voicing.bass.name() {
510 let found = inversion_between(&voicing.root, &voicing.bass)?;
511 match found {
512 Some(number) if inversion_is_valid_for_kind(kind, number) => inversion = Some(number),
513 _ => {
517 voicing.bass.set_octave(Some(2));
518 voicing.slots.push(Slot::Bass);
519 }
520 }
521 }
522
523 let mut pitches = voicing.values();
524 let current_root = voicing.root.clone();
525 apply_modifications(
526 ¤t_root,
527 kind,
528 &mut pitches,
529 &mut degrees,
530 modifications,
531 )?;
532 voicing.replace(pitches);
533
534 if let Some(number) = inversion.filter(|number| *number != 0) {
535 let number = usize::from(number);
536 for index in 0..number.min(voicing.slots.len()) {
537 voicing.shift(index, 1);
538 if number > 3 {
539 voicing.shift(index, 1);
540 }
541 }
542 for index in 0..voicing.slots.len() {
544 let slot = voicing.slots[index];
545 if voicing.pitch(slot).diatonic_note_number() < voicing.bass.diatonic_note_number() {
546 voicing.shift(index, 1);
547 }
548 }
549 }
550
551 let highest = |voicing: &Voicing| {
555 voicing
556 .values()
557 .iter()
558 .map(Pitch::diatonic_note_number)
559 .max()
560 };
561 while highest(&voicing).is_some_and(|number| number > 30) {
562 voicing.shift_all(-1);
563 }
564 let lowest = |voicing: &Voicing| {
565 voicing
566 .values()
567 .iter()
568 .map(Pitch::diatonic_note_number)
569 .min()
570 };
571 while lowest(&voicing).is_some_and(|number| number < 13) {
572 voicing.shift_all(1);
573 }
574
575 let mut pitches = voicing.values();
576 sort_diatonic(&mut pitches);
577 let bass = voicing.bass;
581 if !pitches.iter().any(|pitch| pitch.name() == bass.name()) {
582 pitches.insert(0, bass.clone());
583 }
584 Ok(Realized {
585 pitches,
586 root: voicing.root,
587 bass,
588 })
589}
590
591fn degree_number(token: &str) -> Option<u8> {
594 token.replace(['-', '#', 'A'], "").parse().ok()
595}
596
597fn apply_modifications(
605 root: &Pitch,
606 kind: &str,
607 pitches: &mut Vec<Pitch>,
608 degrees: &mut Vec<String>,
609 modifications: &[ChordStepModification],
610) -> Result<()> {
611 let scale = Scale::new(ScaleType::Major, root.clone());
612 for modification in modifications {
613 match modification.modification_type {
614 ChordStepModificationType::Add => {
615 add_degree(&scale, root, kind, pitches, degrees, modification)?;
616 }
617 ChordStepModificationType::Subtract => {
618 if degrees.is_empty() {
619 continue;
620 }
621 let mut found = false;
622 let mut index = 0;
623 while index < pitches.len() && index < degrees.len() {
624 if degree_number(°rees[index]) == Some(modification.degree) {
625 let taken = pitches[index].clone();
626 if let Some(at) = pitches.iter().position(|pitch| *pitch == taken) {
627 let _ = pitches.remove(at);
628 }
629 found = true;
630 let written = modification.degree.to_string();
631 if let Some(at) = degrees.iter().position(|token| token.contains(&written))
632 {
633 let _ = degrees.remove(at);
634 }
635 }
636 index += 1;
637 }
638 if !found {
639 return Err(Error::Chord(format!(
640 "Degree not in specified chord: {}",
641 modification.degree
642 )));
643 }
644 }
645 ChordStepModificationType::Alter => {
646 let mut found = false;
647 for index in 0..pitches.len().min(degrees.len()) {
648 if degree_number(°rees[index]) == Some(modification.degree) {
649 pitches[index] = pitches[index].transpose(&modification.interval)?;
650 found = true;
651 }
652 }
653 if !found {
656 add_degree(&scale, root, kind, pitches, degrees, modification)?;
657 }
658 }
659 }
660 }
661 Ok(())
662}
663
664fn add_degree(
669 scale: &Scale,
670 root: &Pitch,
671 kind: &str,
672 pitches: &mut Vec<Pitch>,
673 degrees: &[String],
674 modification: &ChordStepModification,
675) -> Result<()> {
676 let folded = IntegerType::from((modification.degree.max(1) - 1) % 7 + 1);
677 let mut added = scale.pitch_at_degree(folded)?;
678 added.set_octave(Some(octave_of(root)));
679 if added.ps() < root.ps() {
680 shift_octave(&mut added, 1);
681 }
682 let semitones = modification.interval.semitones();
683 if semitones != 0.0 {
684 if modification.degree == 7 && semitones > 0.0 && !kind.is_empty() {
689 let mut lowered = Pitch::from_number(added.ps() - 1.0)?;
690 lowered.set_octave(Some(octave_of(&lowered)));
691 added = lowered;
692 }
693 added = added.transpose(&modification.interval)?;
694 }
695 if modification.degree >= 7 {
696 shift_octave(&mut added, 1);
697 }
698 if degrees.contains(&modification.degree.to_string()) {
699 let mut index = 0;
700 while index < pitches.len() {
701 let on_degree = scale.degree_of(&pitches[index])?;
702 if on_degree == Some(usize::from(modification.degree)) {
703 let _ = pitches.remove(index);
704 pitches.push(added.clone());
705 }
706 index += 1;
707 }
708 } else {
709 pitches.push(added);
710 }
711 Ok(())
712}