1use crate::chord::Chord;
13use crate::defaults::FloatType;
14use crate::error::{Error, Result};
15use crate::figuredbass::Notation;
16use crate::figuredbass::possibility::{self, ItalianSixth};
17use crate::figuredbass::resolution::{self, AugmentedSixth, ChordInfo};
18use crate::figuredbass::rules::Rules;
19use crate::figuredbass::scale::FiguredBassScale;
20use crate::interval::Interval;
21use crate::pitch::Pitch;
22use crate::scale::{Scale, ScaleType};
23
24pub type Possibility = Vec<Pitch>;
26
27pub type PossibilityPair = (Possibility, Possibility);
29
30#[derive(Clone, Debug, PartialEq)]
33pub struct Consecutive {
34 pub pairs: Vec<PossibilityPair>,
36 pub fallback: Option<String>,
40}
41
42impl Consecutive {
43 fn resolved(pairs: Vec<PossibilityPair>) -> Self {
44 Self {
45 pairs,
46 fallback: None,
47 }
48 }
49}
50
51#[derive(Clone, Debug)]
53#[must_use]
54pub struct Segment {
55 bass: Pitch,
56 quarter_length: FloatType,
57 num_parts: usize,
58 max_pitch: Pitch,
59 pitch_names: Vec<String>,
60 pitches_above_bass: Vec<Pitch>,
61 chord: Chord,
62 overlaid: bool,
63 pub rules: Rules,
65}
66
67impl Segment {
68 pub fn new(
76 bass: Pitch,
77 quarter_length: FloatType,
78 notation: &Notation,
79 scale: &FiguredBassScale,
80 rules: Rules,
81 num_parts: usize,
82 max_pitch: Pitch,
83 ) -> Result<Self> {
84 let names = scale.pitch_names(&bass, notation)?;
85 Self::from_pitch_names(bass, quarter_length, names, rules, num_parts, max_pitch)
86 }
87
88 pub fn from_pitch_names(
95 bass: Pitch,
96 quarter_length: FloatType,
97 pitch_names: Vec<String>,
98 rules: Rules,
99 num_parts: usize,
100 max_pitch: Pitch,
101 ) -> Result<Self> {
102 let pitches_above_bass = pitches(&pitch_names, &bass, &max_pitch)?;
103 let chord = Chord::new(pitches_above_bass.as_slice())?;
104 Ok(Self {
105 bass,
106 quarter_length,
107 num_parts,
108 max_pitch,
109 pitch_names,
110 pitches_above_bass,
111 chord,
112 overlaid: false,
113 rules,
114 })
115 }
116
117 pub fn overlaid(mut self) -> Self {
120 self.overlaid = true;
121 self
122 }
123
124 pub fn bass(&self) -> &Pitch {
126 &self.bass
127 }
128
129 pub fn quarter_length(&self) -> FloatType {
131 self.quarter_length
132 }
133
134 pub fn num_parts(&self) -> usize {
136 self.num_parts
137 }
138
139 pub fn max_pitch(&self) -> &Pitch {
141 &self.max_pitch
142 }
143
144 pub fn pitch_names(&self) -> &[String] {
147 &self.pitch_names
148 }
149
150 pub fn pitches_above_bass(&self) -> &[Pitch] {
153 &self.pitches_above_bass
154 }
155
156 pub fn chord(&self) -> &Chord {
158 &self.chord
159 }
160
161 fn choices(&self) -> Result<Vec<Vec<Pitch>>> {
165 let upper = self.num_parts.saturating_sub(1);
166 let mut choices: Vec<Vec<Pitch>> = vec![self.pitches_above_bass.clone(); upper];
167 choices.push(vec![Pitch::from_name(self.bass.name_with_octave())?]);
168 if self.overlaid {
169 for (part, pitch) in &self.rules.part_pitch_limits {
170 let slot = part.checked_sub(1).and_then(|index| choices.get_mut(index));
171 let Some(slot) = slot else {
172 return Err(Error::FiguredBass(format!(
173 "a segment of {} parts has no part {part}",
174 self.num_parts
175 )));
176 };
177 *slot = vec![Pitch::from_name(pitch.name_with_octave())?];
178 }
179 }
180 Ok(choices)
181 }
182
183 pub fn all_single_possibilities(&self) -> Result<Vec<Possibility>> {
186 Ok(product(&self.choices()?))
187 }
188
189 pub fn all_correct_single_possibilities(&self) -> Result<Vec<Possibility>> {
197 let choices = self.choices()?;
198 if choices.iter().any(Vec::is_empty) {
199 return Ok(Vec::new());
200 }
201 let rules = &self.rules;
202 let wanted: Option<u64> = if rules.forbid_incomplete_possibilities {
205 if self.pitch_names.len() > 64 {
206 return Ok(product(&choices)
207 .into_iter()
208 .filter(|voicing| self.is_correct_single(voicing))
209 .collect());
210 }
211 Some(
212 self.pitch_names
213 .iter()
214 .enumerate()
215 .fold(0, |bits, (index, _)| bits | 1 << index),
216 )
217 } else {
218 None
219 };
220 let spaces: Vec<Vec<FloatType>> = choices
221 .iter()
222 .map(|part| part.iter().map(Pitch::ps).collect())
223 .collect();
224 let spelled: Vec<Vec<u64>> = choices
225 .iter()
226 .map(|part| {
227 part.iter()
228 .map(|pitch| {
229 self.pitch_names
230 .iter()
231 .enumerate()
232 .filter(|(_, name)| pitch.is_named(name))
233 .fold(0, |bits, (index, _)| bits | 1 << index)
234 })
235 .collect()
236 })
237 .collect();
238 let limit = rules
239 .upper_parts_max_semitone_separation
240 .map(FloatType::from);
241 let upper = choices.len() - 1;
242 let mut indices = vec![0; choices.len()];
243 let mut places = vec![0.0; choices.len()];
244 let mut kept = Vec::new();
245 loop {
246 for (part, &index) in indices.iter().enumerate() {
247 places[part] = spaces[part][index];
248 }
249 let complete = wanted.is_none_or(|wanted| {
250 indices
251 .iter()
252 .enumerate()
253 .fold(0, |bits, (part, &index)| bits | spelled[part][index])
254 & wanted
255 == wanted
256 });
257 let within = limit.is_none_or(|limit| {
258 (0..upper).all(|high| {
259 (high + 1..upper).all(|low| (places[high] - places[low]).abs() <= limit)
260 })
261 });
262 let crossing = rules.forbid_voice_crossing
263 && (0..places.len())
264 .any(|high| (high + 1..places.len()).any(|low| places[high] < places[low]));
265 if complete && within && !crossing {
266 kept.push(
267 indices
268 .iter()
269 .enumerate()
270 .map(|(part, &index)| choices[part][index].clone())
271 .collect(),
272 );
273 }
274 let mut position = choices.len();
275 loop {
276 if position == 0 {
277 return Ok(kept);
278 }
279 position -= 1;
280 indices[position] += 1;
281 if indices[position] < choices[position].len() {
282 break;
283 }
284 indices[position] = 0;
285 }
286 }
287 }
288
289 fn is_correct_single(&self, voicing: &[Pitch]) -> bool {
291 let rules = &self.rules;
292 !(rules.forbid_incomplete_possibilities
293 && possibility::is_incomplete(voicing, &self.pitch_names))
294 && possibility::upper_parts_within_limit(
295 voicing,
296 rules.upper_parts_max_semitone_separation,
297 )
298 && !(rules.forbid_voice_crossing && possibility::voice_crossing(voicing))
299 }
300
301 fn is_correct_consecutive(
304 &self,
305 from: &[Pitch],
306 to: &[Pitch],
307 italian: Option<&ItalianSixth>,
308 ) -> Result<bool> {
309 let rules = &self.rules;
310 Ok(
311 possibility::parts_same(from, to, Some(&rules.parts_to_check))?
312 && (!rules.upper_parts_remain_same || possibility::upper_parts_same(from, to)?)
313 && !(rules.forbid_voice_overlap && possibility::voice_overlap(from, to)?)
314 && possibility::part_movements_within_limits(
315 from,
316 to,
317 &rules.part_movement_limits,
318 )?
319 && !(rules.forbid_parallel_fifths && possibility::parallel_fifths(from, to)?)
320 && !(rules.forbid_parallel_octaves && possibility::parallel_octaves(from, to)?)
321 && !(rules.forbid_hidden_fifths && possibility::hidden_fifths(from, to)?)
322 && !(rules.forbid_hidden_octaves && possibility::hidden_octaves(from, to)?)
323 && match italian {
324 Some(sixth) => possibility::could_be_italian_a6_resolution(
325 from,
326 to,
327 Some(sixth),
328 rules.restrict_doublings_in_italian_a6_resolution,
329 )?,
330 None => true,
331 },
332 )
333 }
334
335 fn italian_sixth(&self) -> Result<Option<ItalianSixth>> {
338 if self.rules.resolve_augmented_sixth_properly
339 && self.chord.is_italian_augmented_sixth(false, false)
340 {
341 Ok(Some(ItalianSixth::from_chord(&self.chord)?))
342 } else {
343 Ok(None)
344 }
345 }
346
347 pub fn all_correct_consecutive_possibilities(&self, next: &mut Segment) -> Result<Consecutive> {
360 if self.num_parts != next.num_parts {
361 return Err(Error::FiguredBass(
362 "Two segments with unequal numParts cannot be compared.".to_string(),
363 ));
364 }
365 if self.max_pitch != next.max_pitch {
366 return Err(Error::FiguredBass(
367 "Two segments with unequal maxPitch cannot be compared.".to_string(),
368 ));
369 }
370 let rules = &self.rules;
371 if rules.resolve_dominant_seventh_properly && self.chord.is_dominant_seventh() {
372 return self.resolve_dominant_seventh_segment(next);
373 }
374 if rules.resolve_diminished_seventh_properly && self.chord.is_diminished_seventh() {
375 return self.resolve_diminished_seventh_segment(next, rules.doubled_root_in_dim7);
376 }
377 if rules.resolve_augmented_sixth_properly && self.chord.is_augmented_sixth(false) {
378 return self.resolve_augmented_sixth_segment(next);
379 }
380 Ok(Consecutive::resolved(self.resolve_ordinary(next)?))
381 }
382
383 fn resolve_ordinary(&self, next: &Segment) -> Result<Vec<PossibilityPair>> {
387 let italian = self.italian_sixth()?;
388 let from = self.all_correct_single_possibilities()?;
389 let to = next.all_correct_single_possibilities()?;
390 let mut pairs = Vec::new();
391 for a in &from {
392 for b in &to {
393 if self.is_correct_consecutive(a, b, italian.as_ref())? {
394 pairs.push((a.clone(), b.clone()));
395 }
396 }
397 }
398 Ok(pairs)
399 }
400
401 fn resolve_special(
405 &self,
406 next: &Segment,
407 resolve: impl Fn(&[Pitch]) -> Result<Vec<Pitch>>,
408 ) -> Result<Vec<PossibilityPair>> {
409 let italian = self.italian_sixth()?;
410 let mut pairs = Vec::new();
411 for voicing in self.all_correct_single_possibilities()? {
412 let resolved = resolve(&voicing)?;
413 if !possibility::pitches_within_limit(&resolved, &next.max_pitch) {
414 continue;
415 }
416 if self.rules.apply_consecutive_possib_rules_to_resolution
417 && !self.is_correct_consecutive(&voicing, &resolved, italian.as_ref())?
418 {
419 continue;
420 }
421 if self.rules.apply_single_possib_rules_to_resolution
422 && !next.is_correct_single(&resolved)
423 {
424 continue;
425 }
426 pairs.push((voicing, resolved));
427 }
428 Ok(pairs)
429 }
430
431 fn resolve_first(
434 &self,
435 next: &Segment,
436 candidates: Vec<(bool, SpecialResolution<'_>)>,
437 warning: &str,
438 ) -> Result<Consecutive> {
439 match candidates.into_iter().find(|(applies, _)| *applies) {
440 Some((_, resolve)) => Ok(Consecutive::resolved(self.resolve_special(next, resolve)?)),
441 None => Ok(Consecutive {
442 pairs: self.resolve_ordinary(next)?,
443 fallback: Some(warning.to_string()),
444 }),
445 }
446 }
447
448 pub fn resolve_dominant_seventh_segment(&self, next: &mut Segment) -> Result<Consecutive> {
456 let dominant = &self.chord;
457 if !dominant.is_dominant_seventh() {
458 return Err(Error::FiguredBass(
459 "Dominant seventh resolution: Not a dominant seventh Segment.".to_string(),
460 ));
461 }
462 let info = ChordInfo::of(dominant);
463 let dominant_scale = ScaleType::Major.derive(dominant.pitches().as_slice())?;
464 let minor_scale = dominant_scale.parallel_minor();
465 let tonic = dominant_scale.tonic().name();
466 let subdominant = dominant_scale.pitch_at_degree(4)?.name();
467 let major_submediant = dominant_scale.pitch_at_degree(6)?.name();
468 let minor_submediant = minor_scale.pitch_at_degree(6)?.name();
469
470 let resolving = next.chord.clone();
471 let second_inversion = dominant.inversion() == Some(2);
472 let v43_to_i6 = second_inversion && resolving.inversion() == Some(1);
473 let root = resolving.root().map(Pitch::name).unwrap_or_default();
474 let major = resolving.is_major_triad();
475 let minor = resolving.is_minor_triad();
476 if dominant.inversion() == Some(0) && root == tonic && (major || minor) {
477 next.rules.forbid_incomplete_possibilities = false;
478 }
479 let info = &info;
480 let candidates: Vec<(bool, SpecialResolution<'_>)> = vec![
481 (
482 root == tonic && major,
483 Box::new(move |voicing: &[Pitch]| {
484 resolution::dominant_seventh_to_major_tonic(voicing, v43_to_i6, Some(info))
485 }),
486 ),
487 (
488 root == tonic && minor,
489 Box::new(move |voicing: &[Pitch]| {
490 resolution::dominant_seventh_to_minor_tonic(voicing, v43_to_i6, Some(info))
491 }),
492 ),
493 (
494 root == major_submediant && minor && !second_inversion,
495 Box::new(move |voicing: &[Pitch]| {
496 resolution::dominant_seventh_to_minor_submediant(voicing, Some(info))
497 }),
498 ),
499 (
500 root == minor_submediant && major && !second_inversion,
501 Box::new(move |voicing: &[Pitch]| {
502 resolution::dominant_seventh_to_major_submediant(voicing, Some(info))
503 }),
504 ),
505 (
506 root == subdominant && major && !second_inversion,
507 Box::new(move |voicing: &[Pitch]| {
508 resolution::dominant_seventh_to_major_subdominant(voicing, Some(info))
509 }),
510 ),
511 (
512 root == subdominant && minor && !second_inversion,
513 Box::new(move |voicing: &[Pitch]| {
514 resolution::dominant_seventh_to_minor_subdominant(voicing, Some(info))
515 }),
516 ),
517 ];
518 self.resolve_first(
519 next,
520 candidates,
521 "Dominant seventh resolution: No proper resolution available. Executing ordinary resolution.",
522 )
523 }
524
525 pub fn resolve_diminished_seventh_segment(
534 &self,
535 next: &Segment,
536 doubled_root: bool,
537 ) -> Result<Consecutive> {
538 let diminished = &self.chord;
539 if !diminished.is_diminished_seventh() {
540 return Err(Error::FiguredBass(
541 "Diminished seventh resolution: Not a diminished seventh Segment.".to_string(),
542 ));
543 }
544 let info = ChordInfo::of(diminished);
545 let root = diminished
546 .root()
547 .ok_or_else(|| Error::FiguredBass("a diminished seventh with no root".to_string()))?;
548 let placeholder = Pitch::from_name("C")?;
549 let scale = Scale::new(ScaleType::HarmonicMinor, placeholder).derive_by_degree(7, root)?;
550 let tonic = scale.tonic().name();
551 let subdominant = scale.pitch_at_degree(4)?.name();
552
553 let resolving = &next.chord;
554 let mut doubled_root = doubled_root;
555 if diminished.inversion() == Some(1) {
556 match resolving.inversion() {
557 Some(0) => doubled_root = true,
558 Some(1) => doubled_root = false,
559 _ => {}
560 }
561 }
562 let resolving_root = resolving.root().map(Pitch::name).unwrap_or_default();
563 let major = resolving.is_major_triad();
564 let minor = resolving.is_minor_triad();
565 let info = &info;
566 let candidates: Vec<(bool, SpecialResolution<'_>)> = vec![
567 (
568 resolving_root == tonic && major,
569 Box::new(move |voicing: &[Pitch]| {
570 resolution::diminished_seventh_to_major_tonic(voicing, doubled_root, Some(info))
571 }),
572 ),
573 (
574 resolving_root == tonic && minor,
575 Box::new(move |voicing: &[Pitch]| {
576 resolution::diminished_seventh_to_minor_tonic(voicing, doubled_root, Some(info))
577 }),
578 ),
579 (
580 resolving_root == subdominant && major,
581 Box::new(move |voicing: &[Pitch]| {
582 resolution::diminished_seventh_to_major_subdominant(voicing, Some(info))
583 }),
584 ),
585 (
586 resolving_root == subdominant && minor,
587 Box::new(move |voicing: &[Pitch]| {
588 resolution::diminished_seventh_to_minor_subdominant(voicing, Some(info))
589 }),
590 ),
591 ];
592 self.resolve_first(
593 next,
594 candidates,
595 "Diminished seventh resolution: No proper resolution available. Executing ordinary resolution.",
596 )
597 }
598
599 pub fn resolve_augmented_sixth_segment(&self, next: &Segment) -> Result<Consecutive> {
608 let sixth = &self.chord;
609 if !sixth.is_augmented_sixth(false) {
610 return Err(Error::FiguredBass(
611 "Augmented sixth resolution: Not an augmented sixth Segment.".to_string(),
612 ));
613 }
614 if sixth.is_italian_augmented_sixth(false, false) {
615 return Ok(Consecutive::resolved(self.resolve_ordinary(next)?));
616 }
617 let kind = if sixth.is_french_augmented_sixth(false) {
618 AugmentedSixth::French
619 } else if sixth.is_german_augmented_sixth(false) {
620 AugmentedSixth::German
621 } else if sixth.is_swiss_augmented_sixth(false) {
622 AugmentedSixth::Swiss
623 } else {
624 return Ok(Consecutive {
625 pairs: self.resolve_ordinary(next)?,
626 fallback: Some(
627 "Augmented sixth resolution: Augmented sixth type not supported. Executing ordinary resolution."
628 .to_string(),
629 ),
630 });
631 };
632 let bass = sixth
633 .bass()
634 .ok_or_else(|| Error::FiguredBass("an augmented sixth with no bass".to_string()))?;
635 let tonic = bass.transpose(&Interval::from_name("M3")?)?;
636 let dominant = Scale::new(ScaleType::Major, tonic.clone())
637 .pitch_at_degree(5)?
638 .name();
639 let tonic = tonic.name();
640 let info = ChordInfo::of(sixth);
641
642 let resolving = &next.chord;
643 let root = resolving.root().map(Pitch::name).unwrap_or_default();
644 let resolving_bass = resolving.bass().map(Pitch::name).unwrap_or_default();
645 let second_inversion = resolving.inversion() == Some(2);
646 let major = resolving.is_major_triad();
647 let minor = resolving.is_minor_triad();
648 let info = &info;
649 let candidates: Vec<(bool, SpecialResolution<'_>)> = vec![
650 (
651 second_inversion && root == tonic && major,
652 Box::new(move |voicing: &[Pitch]| {
653 resolution::augmented_sixth_to_major_tonic(voicing, Some(kind), Some(info))
654 }),
655 ),
656 (
657 second_inversion && root == tonic && minor,
658 Box::new(move |voicing: &[Pitch]| {
659 resolution::augmented_sixth_to_minor_tonic(voicing, Some(kind), Some(info))
660 }),
661 ),
662 (
663 dominant == resolving_bass && major,
664 Box::new(move |voicing: &[Pitch]| {
665 resolution::augmented_sixth_to_dominant(voicing, Some(kind), Some(info))
666 }),
667 ),
668 ];
669 self.resolve_first(
670 next,
671 candidates,
672 "Augmented sixth resolution: No proper resolution available. Executing ordinary resolution.",
673 )
674 }
675}
676
677type SpecialResolution<'a> = Box<dyn Fn(&[Pitch]) -> Result<Vec<Pitch>> + 'a>;
679
680fn product(choices: &[Vec<Pitch>]) -> Vec<Possibility> {
683 if choices.iter().any(Vec::is_empty) {
684 return Vec::new();
685 }
686 let mut indices = vec![0; choices.len()];
687 let mut all = Vec::new();
688 loop {
689 all.push(
690 indices
691 .iter()
692 .zip(choices)
693 .map(|(&index, choice)| choice[index].clone())
694 .collect(),
695 );
696 let mut position = choices.len();
697 loop {
698 if position == 0 {
699 return all;
700 }
701 position -= 1;
702 indices[position] += 1;
703 if indices[position] < choices[position].len() {
704 break;
705 }
706 indices[position] = 0;
707 }
708 }
709}
710
711pub fn pitches<S: AsRef<str>>(
718 pitch_names: &[S],
719 bass: &Pitch,
720 max_pitch: &Pitch,
721) -> Result<Vec<Pitch>> {
722 let Some(top_octave) = max_pitch.octave() else {
723 return Err(Error::Value(
724 "maxPitch must not have an implicit octave".to_string(),
725 ));
726 };
727 let mut found = Vec::new();
728 for name in pitch_names {
729 for octave in 0..=top_octave {
730 let pitch = Pitch::from_name(format!("{}{octave}", name.as_ref()))?;
731 if pitch.ps() >= bass.ps() && pitch.ps() <= max_pitch.ps() {
732 found.push(pitch);
733 }
734 }
735 }
736 found.sort_by(|a, b| a.ps().total_cmp(&b.ps()));
737 Ok(found)
738}