use crate::core::Interval;
use crate::stream::Part;
pub fn ambitus(part: &Part) -> Option<Interval> {
let notes = part.find_consecutive_notes();
let lowest = notes.iter().map(|n| n.pitch()).min()?;
let highest = notes.iter().map(|n| n.pitch()).max()?;
Some(Interval::between(lowest, highest))
}
pub fn melodic_interval_diversity(part: &Part) -> f64 {
let intervals = part.melodic_intervals();
if intervals.is_empty() {
return 0.0;
}
let mut distinct: Vec<String> = intervals.iter().map(|iv| iv.simple_name()).collect();
distinct.sort();
distinct.dedup();
distinct.len() as f64 / intervals.len() as f64
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::{Duration, Fraction, Note, Pitch, Step};
use crate::stream::{Measure, MusicElement};
fn part_from_steps(steps: &[(Step, i8)]) -> Part {
let mut part = Part::new();
let mut measure = Measure::new(1);
let mut offset = Fraction::new(0, 1);
for &(step, octave) in steps {
measure.insert(
offset,
MusicElement::Note(Note::quarter(Pitch::from_parts(step, Some(octave), None))),
);
offset += Duration::quarter().quarter_length();
}
part.add_measure(measure);
part
}
#[test]
fn test_ambitus_spans_lowest_to_highest_note() {
let part = part_from_steps(&[(Step::C, 4), (Step::G, 5), (Step::E, 4)]);
let range = ambitus(&part).unwrap();
assert_eq!(range.semitones(), 19);
}
#[test]
fn test_ambitus_empty_part_is_none() {
let part = Part::new();
assert_eq!(ambitus(&part), None);
}
#[test]
fn test_melodic_interval_diversity_all_same_interval() {
let part = part_from_steps(&[(Step::C, 4), (Step::D, 4), (Step::E, 4), (Step::F, 4)]);
let diversity = melodic_interval_diversity(&part);
assert!((diversity - (2.0 / 3.0)).abs() < 1e-9);
}
#[test]
fn test_melodic_interval_diversity_no_intervals_is_zero() {
let part = part_from_steps(&[(Step::C, 4)]);
assert_eq!(melodic_interval_diversity(&part), 0.0);
}
}