use std::fmt;
use crate::core::{
AccidentalDisplay, Duration, Fraction, Interval, Note, Pitch, Tie, TieType,
update_accidental_display,
};
use crate::notation::{Beam, Clef, KeySignature, TimeSignature, compute_beams};
use super::base::MusicElement;
use super::measure::Measure;
use super::voice::Voice;
#[derive(Debug, Clone, PartialEq)]
pub struct RecursedElement {
pub measure_number: u32,
pub offset_in_measure: Fraction,
pub absolute_offset: Fraction,
pub element: MusicElement,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Instrument {
name: String,
abbreviation: Option<String>,
midi_program: u8,
midi_channel: Option<u8>,
transposition: i8,
}
impl Instrument {
pub fn new(name: impl Into<String>, midi_program: u8) -> Self {
Self {
name: name.into(),
abbreviation: None,
midi_program,
midi_channel: None,
transposition: 0,
}
}
pub fn piano() -> Self {
Self::new("Piano", 0)
}
pub fn violin() -> Self {
Self::new("Violin", 40)
}
pub fn flute() -> Self {
Self::new("Flute", 73)
}
pub fn trumpet() -> Self {
let mut inst = Self::new("Trumpet", 56);
inst.transposition = -2; inst
}
pub fn name(&self) -> &str {
&self.name
}
pub fn set_name(&mut self, name: impl Into<String>) {
self.name = name.into();
}
pub fn abbreviation(&self) -> Option<&str> {
self.abbreviation.as_deref()
}
pub fn set_abbreviation(&mut self, abbr: impl Into<String>) {
self.abbreviation = Some(abbr.into());
}
pub fn midi_program(&self) -> u8 {
self.midi_program
}
pub fn set_midi_program(&mut self, program: u8) {
self.midi_program = program;
}
pub fn midi_channel(&self) -> Option<u8> {
self.midi_channel
}
pub fn set_midi_channel(&mut self, channel: u8) {
self.midi_channel = Some(channel);
}
pub fn transposition(&self) -> i8 {
self.transposition
}
pub fn set_transposition(&mut self, semitones: i8) {
self.transposition = semitones;
}
}
impl Default for Instrument {
fn default() -> Self {
Self::piano()
}
}
#[derive(Debug, Clone, Default)]
pub struct Part {
name: Option<String>,
abbreviation: Option<String>,
instrument: Option<Instrument>,
measures: Vec<Measure>,
id: Option<String>,
}
impl Part {
pub fn new() -> Self {
Self::default()
}
pub fn with_name(name: impl Into<String>) -> Self {
Self {
name: Some(name.into()),
..Default::default()
}
}
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn set_name(&mut self, name: impl Into<String>) {
self.name = Some(name.into());
}
pub fn abbreviation(&self) -> Option<&str> {
self.abbreviation.as_deref()
}
pub fn set_abbreviation(&mut self, abbr: impl Into<String>) {
self.abbreviation = Some(abbr.into());
}
pub fn instrument(&self) -> Option<&Instrument> {
self.instrument.as_ref()
}
pub fn set_instrument(&mut self, instrument: Instrument) {
self.instrument = Some(instrument);
}
pub fn id(&self) -> Option<&str> {
self.id.as_deref()
}
pub fn set_id(&mut self, id: impl Into<String>) {
self.id = Some(id.into());
}
pub fn measures(&self) -> &[Measure] {
&self.measures
}
pub fn measures_mut(&mut self) -> &mut Vec<Measure> {
&mut self.measures
}
pub fn measure(&self, index: usize) -> Option<&Measure> {
self.measures.get(index)
}
pub fn measure_mut(&mut self, index: usize) -> Option<&mut Measure> {
self.measures.get_mut(index)
}
pub fn measure_by_number(&self, number: u32) -> Option<&Measure> {
self.measures.iter().find(|m| m.number() == number)
}
pub fn time_signature_at(&self, index: usize) -> Option<&TimeSignature> {
self.measures
.get(..=index)?
.iter()
.rev()
.find_map(|m| m.time_signature())
}
pub fn key_signature_at(&self, index: usize) -> Option<&KeySignature> {
self.measures
.get(..=index)?
.iter()
.rev()
.find_map(|m| m.key_signature())
}
pub fn clef_at(&self, index: usize) -> Option<&Clef> {
self.measures
.get(..=index)?
.iter()
.rev()
.find_map(|m| m.clef())
}
pub fn measure_duration(&self, index: usize) -> Fraction {
let measure = match self.measures.get(index) {
Some(m) => m,
None => return Fraction::new(0, 1),
};
if let Some(explicit) = measure.explicit_duration() {
return explicit;
}
if measure.time_signature().is_some() {
return measure.duration();
}
match self.time_signature_at(index) {
Some(ts) => ts.bar_duration(),
None => Fraction::new(4, 1),
}
}
pub fn add_measure(&mut self, measure: Measure) {
self.measures.push(measure);
}
pub fn insert_measure(&mut self, index: usize, measure: Measure) {
self.measures.insert(index, measure);
}
pub fn remove_measure(&mut self, index: usize) -> Option<Measure> {
if index < self.measures.len() {
Some(self.measures.remove(index))
} else {
None
}
}
pub fn num_measures(&self) -> usize {
self.measures.len()
}
pub fn is_empty(&self) -> bool {
self.measures.is_empty()
}
pub fn clear(&mut self) {
self.measures.clear();
}
pub fn duration(&self) -> crate::core::Fraction {
(0..self.measures.len())
.map(|i| self.measure_duration(i))
.sum()
}
pub fn notes(&self) -> impl Iterator<Item = &crate::core::Note> {
self.measures.iter().flat_map(|m| m.notes())
}
pub fn chords(&self) -> impl Iterator<Item = &crate::core::Chord> {
self.measures.iter().flat_map(|m| m.chords())
}
pub fn ensure_measures(&mut self, count: usize) {
while self.measures.len() < count {
let num = self.measures.len() as u32 + 1;
self.measures.push(Measure::new(num));
}
}
pub fn make_measures(
elements: &[(Fraction, MusicElement)],
time_signature: TimeSignature,
) -> Part {
let bar_duration = time_signature.bar_duration();
let mut part = Part::new();
let num_measures = if elements.is_empty() {
1
} else {
let max_offset = elements
.iter()
.map(|(offset, element)| *offset + element.quarter_length())
.fold(Fraction::new(0, 1), |a, b| if b > a { b } else { a });
(max_offset / bar_duration).ceil().to_integer().max(1) as usize
};
for i in 0..num_measures {
let mut measure = Measure::new(i as u32 + 1);
if i == 0 {
measure.set_time_signature(time_signature);
}
part.add_measure(measure);
}
for (offset, element) in elements {
let last_index = part.num_measures() - 1;
let measure_index = (*offset / bar_duration).to_integer().max(0) as usize;
let measure_index = measure_index.min(last_index);
let local_offset = *offset - bar_duration * Fraction::from(measure_index as i64);
part.measures[measure_index].insert(local_offset, element.clone());
}
part
}
pub fn make_ties(&mut self) {
let mut i = 0;
while i < self.measures.len() {
let bar_duration = self.measure_duration(i);
let old_elements: Vec<(Fraction, MusicElement)> = self.measures[i].elements().to_vec();
let mut kept: Vec<(Fraction, MusicElement)> = Vec::new();
let mut carry: Vec<(Fraction, Pitch)> = Vec::new();
for (offset, element) in old_elements {
if let MusicElement::Note(note) = &element {
let end = offset + note.quarter_length();
if end > bar_duration {
let first_len = bar_duration - offset;
let mut first_note = note.clone();
first_note.set_duration(Duration::from_quarter_length(first_len));
first_note.set_tie(Some(Tie::start()));
kept.push((offset, MusicElement::Note(first_note)));
carry.push((note.quarter_length() - first_len, note.pitch().clone()));
continue;
}
}
kept.push((offset, element));
}
self.measures[i].clear();
for (offset, element) in kept {
self.measures[i].insert(offset, element);
}
let mut next_measure = i + 1;
for (remaining, pitch) in carry {
let mut remaining = remaining;
loop {
if next_measure >= self.measures.len() {
self.ensure_measures(next_measure + 1);
}
let next_bar_duration = self.measure_duration(next_measure);
if remaining <= next_bar_duration {
let mut note =
Note::new(pitch.clone(), Duration::from_quarter_length(remaining));
note.set_tie(Some(Tie::stop()));
self.measures[next_measure]
.insert(Fraction::new(0, 1), MusicElement::Note(note));
break;
} else {
let mut note = Note::new(
pitch.clone(),
Duration::from_quarter_length(next_bar_duration),
);
note.set_tie(Some(Tie::new(TieType::Continue)));
self.measures[next_measure]
.insert(Fraction::new(0, 1), MusicElement::Note(note));
remaining -= next_bar_duration;
next_measure += 1;
}
}
}
i += 1;
}
}
pub fn strip_ties(&mut self) {
let recursed = self.recurse();
let mut merges: Vec<(usize, usize, Fraction)> = Vec::new();
let mut i = 0;
while i < recursed.len() {
let is_start = matches!(
&recursed[i].element,
MusicElement::Note(n) if n.tie().map(|t| t.type_ == TieType::Start).unwrap_or(false)
);
if is_start {
let mut total = recursed[i].element.quarter_length();
let mut j = i + 1;
while j < recursed.len() {
let continuation_type = match &recursed[j].element {
MusicElement::Note(n) => n.tie().map(|t| t.type_),
_ => None,
};
match continuation_type {
Some(TieType::Continue) => {
total += recursed[j].element.quarter_length();
j += 1;
}
Some(TieType::Stop) => {
total += recursed[j].element.quarter_length();
j += 1;
break;
}
_ => break,
}
}
if j > i + 1 {
merges.push((i, j - 1, total));
i = j;
continue;
}
}
i += 1;
}
let mut removals: Vec<(u32, Fraction)> = Vec::new();
for (start, end, total_duration) in merges {
let first = &recursed[start];
if let Some(measure) = self
.measures
.iter_mut()
.find(|m| m.number() == first.measure_number)
&& let Some((_, MusicElement::Note(note))) = measure
.stream_mut()
.elements_mut()
.iter_mut()
.find(|(o, _)| *o == first.offset_in_measure)
{
note.set_duration(Duration::from_quarter_length(total_duration));
note.set_tie(None);
}
for recursed_element in recursed.iter().take(end + 1).skip(start + 1) {
removals.push((
recursed_element.measure_number,
recursed_element.offset_in_measure,
));
}
}
for (measure_number, offset) in removals {
if let Some(measure) = self
.measures
.iter_mut()
.find(|m| m.number() == measure_number)
{
measure
.stream_mut()
.elements_mut()
.retain(|(o, _)| *o != offset);
}
}
}
pub fn extend_ties(&mut self) {
let recursed = self.recurse();
let mut to_tie: Vec<((u32, Fraction), (u32, Fraction))> = Vec::new();
for pair in recursed.windows(2) {
let (a, b) = (&pair[0], &pair[1]);
let (a_note, b_note) = match (&a.element, &b.element) {
(MusicElement::Note(an), MusicElement::Note(bn)) => (an, bn),
_ => continue,
};
if a_note.tie().is_some() || b_note.tie().is_some() {
continue;
}
let a_end = a.absolute_offset + a_note.quarter_length();
if a_note.pitch() == b_note.pitch() && a_end == b.absolute_offset {
to_tie.push((
(a.measure_number, a.offset_in_measure),
(b.measure_number, b.offset_in_measure),
));
}
}
for ((a_measure, a_offset), (b_measure, b_offset)) in to_tie {
self.set_note_tie(a_measure, a_offset, Tie::start());
self.set_note_tie(b_measure, b_offset, Tie::stop());
}
}
fn set_note_tie(&mut self, measure_number: u32, offset: Fraction, tie: Tie) {
if let Some(measure) = self
.measures
.iter_mut()
.find(|m| m.number() == measure_number)
&& let Some((_, MusicElement::Note(note))) = measure
.stream_mut()
.elements_mut()
.iter_mut()
.find(|(o, _)| *o == offset)
{
note.set_tie(Some(tie));
}
}
pub fn make_beams(&self, index: usize) -> Vec<Beam> {
let Some(measure) = self.measures.get(index) else {
return Vec::new();
};
let time_signature = self.time_signature_at(index).copied().unwrap_or_default();
let durations: Vec<Duration> = measure
.elements()
.iter()
.map(|(_, element)| element.duration().clone())
.collect();
compute_beams(&durations, &time_signature)
}
pub fn make_accidentals(&self, index: usize) -> Vec<AccidentalDisplay> {
let Some(measure) = self.measures.get(index) else {
return Vec::new();
};
let key_signature = self.key_signature_at(index).cloned().unwrap_or_default();
let pitches: Vec<Pitch> = measure
.elements()
.iter()
.flat_map(|(_, element)| match element {
MusicElement::Note(n) => vec![n.pitch().clone()],
MusicElement::Chord(c) => c.pitches().into_iter().cloned().collect(),
MusicElement::Rest(_) => Vec::new(),
})
.collect();
update_accidental_display(&pitches, &key_signature)
}
pub fn make_notation(&mut self) -> Vec<(Vec<Beam>, Vec<AccidentalDisplay>)> {
self.make_ties();
(0..self.measures.len())
.map(|i| (self.make_beams(i), self.make_accidentals(i)))
.collect()
}
pub fn insert_and_shift(
&mut self,
measure_index: usize,
offset: Fraction,
element: MusicElement,
) {
let Some(measure) = self.measures.get_mut(measure_index) else {
return;
};
let shift_amount = element.quarter_length();
let old_elements = measure.elements().to_vec();
measure.clear();
for (o, e) in old_elements {
if o >= offset {
measure.insert(o + shift_amount, e);
} else {
measure.insert(o, e);
}
}
measure.insert(offset, element);
}
pub fn find_consecutive_notes(&self) -> Vec<Note> {
self.recurse()
.into_iter()
.filter_map(|r| match r.element {
MusicElement::Note(n) => Some(n),
_ => None,
})
.collect()
}
pub fn melodic_intervals(&self) -> Vec<Interval> {
let notes = self.find_consecutive_notes();
notes
.windows(2)
.map(|pair| Interval::between(pair[0].pitch(), pair[1].pitch()))
.collect()
}
pub fn transpose(&self, interval: &Interval) -> Part {
let mut result = self.clone();
for measure in &mut result.measures {
let old_elements = measure.elements().to_vec();
measure.clear();
for (offset, element) in old_elements {
let transposed = match element {
MusicElement::Note(n) => MusicElement::Note(n.transpose(interval)),
MusicElement::Chord(c) => MusicElement::Chord(c.transpose(interval)),
rest @ MusicElement::Rest(_) => rest,
};
measure.insert(offset, transposed);
}
}
result
}
pub fn augment_or_diminish(&self, scalar: Fraction) -> Part {
let mut result = self.clone();
for measure in &mut result.measures {
let old_elements = measure.elements().to_vec();
measure.clear();
for (offset, element) in old_elements {
let scaled = match element {
MusicElement::Note(n) => MusicElement::Note(n.augment_or_diminish(scalar)),
MusicElement::Chord(c) => {
let mut c = c;
let new_duration = c.duration().augment_or_diminish(scalar);
c.set_duration(new_duration);
MusicElement::Chord(c)
}
MusicElement::Rest(r) => MusicElement::Rest(r.augment_or_diminish(scalar)),
};
measure.insert(offset * scalar, scaled);
}
}
result
}
pub fn quantize(&mut self, grid: Fraction) {
if grid <= Fraction::new(0, 1) {
return;
}
for measure in &mut self.measures {
let old_elements = measure.elements().to_vec();
measure.clear();
for (offset, mut element) in old_elements {
let quantized_offset = round_to_grid(offset, grid);
let quantized_len = round_to_grid(element.quarter_length(), grid).max(grid);
let new_duration = Duration::from_quarter_length(quantized_len);
match &mut element {
MusicElement::Note(n) => n.set_duration(new_duration),
MusicElement::Chord(c) => c.set_duration(new_duration),
MusicElement::Rest(r) => r.set_duration(new_duration),
}
measure.insert(quantized_offset, element);
}
}
}
pub fn slice_by_quarter_lengths(&mut self, quarter_length: Fraction, add_ties: bool) {
if quarter_length <= Fraction::new(0, 1) {
return;
}
for measure in &mut self.measures {
let old_elements = measure.elements().to_vec();
measure.clear();
for (offset, element) in old_elements {
let MusicElement::Note(note) = &element else {
measure.insert(offset, element);
continue;
};
let mut piece_lengths = Vec::new();
let mut remaining = note.quarter_length();
while remaining > Fraction::new(0, 1) {
let len = if remaining < quarter_length {
remaining
} else {
quarter_length
};
piece_lengths.push(len);
remaining -= len;
}
let n = piece_lengths.len();
let mut cursor = offset;
for (i, len) in piece_lengths.into_iter().enumerate() {
let mut piece = note.clone();
piece.set_duration(Duration::from_quarter_length(len));
if add_ties && n > 1 {
piece.set_tie(Some(Tie::new(tie_type_for_position(i, n))));
}
measure.insert(cursor, MusicElement::Note(piece));
cursor += len;
}
}
}
}
pub fn slice_by_beat(&mut self) {
for i in 0..self.measures.len() {
let time_signature = self.time_signature_at(i).copied().unwrap_or_default();
let mut boundaries = time_signature.beat_offsets();
boundaries.push(time_signature.bar_duration());
self.slice_measure_at(i, &boundaries);
}
}
pub fn slice_at_offsets(&mut self, offsets: &[Fraction]) {
for i in 0..self.measures.len() {
self.slice_measure_at(i, offsets);
}
}
pub fn slice_by_greatest_divisor(&mut self) {
let mut divisor: Option<Fraction> = None;
for measure in &self.measures {
for (_, element) in measure.elements() {
let len = element.quarter_length();
divisor = Some(match divisor {
None => len,
Some(g) => fraction_gcd(g, len),
});
}
}
if let Some(g) = divisor
&& g > Fraction::new(0, 1)
{
self.slice_by_quarter_lengths(g, true);
}
}
fn slice_measure_at(&mut self, index: usize, cut_points: &[Fraction]) {
let Some(measure) = self.measures.get_mut(index) else {
return;
};
let old_elements = measure.elements().to_vec();
measure.clear();
for (offset, element) in old_elements {
let MusicElement::Note(note) = &element else {
measure.insert(offset, element);
continue;
};
let end = offset + note.quarter_length();
let mut cuts: Vec<Fraction> = cut_points
.iter()
.copied()
.filter(|&c| c > offset && c < end)
.collect();
if cuts.is_empty() {
measure.insert(offset, element);
continue;
}
cuts.sort();
let mut points = vec![offset];
points.extend(cuts);
points.push(end);
let n = points.len() - 1;
for k in 0..n {
let seg_start = points[k];
let seg_len = points[k + 1] - points[k];
let mut piece = note.clone();
piece.set_duration(Duration::from_quarter_length(seg_len));
piece.set_tie(Some(Tie::new(tie_type_for_position(k, n))));
measure.insert(seg_start, MusicElement::Note(piece));
}
}
}
pub fn get_time_signatures(&self) -> Vec<(Fraction, TimeSignature)> {
self.measure_offset_map()
.into_iter()
.zip(self.measures.iter())
.filter_map(|((offset, _), measure)| measure.time_signature().map(|ts| (offset, *ts)))
.collect()
}
pub fn measure_offset_map(&self) -> Vec<(Fraction, u32)> {
let mut offset = Fraction::new(0, 1);
let mut map = Vec::with_capacity(self.measures.len());
for (i, measure) in self.measures.iter().enumerate() {
map.push((offset, measure.number()));
offset += self.measure_duration(i);
}
map
}
pub fn recurse(&self) -> Vec<RecursedElement> {
let mut result = Vec::new();
let mut absolute_offset = Fraction::new(0, 1);
for (i, measure) in self.measures.iter().enumerate() {
for (offset_in_measure, element) in measure.elements() {
result.push(RecursedElement {
measure_number: measure.number(),
offset_in_measure: *offset_in_measure,
absolute_offset: absolute_offset + *offset_in_measure,
element: element.clone(),
});
}
absolute_offset += self.measure_duration(i);
}
result
}
pub fn flatten(&self) -> Vec<(Fraction, MusicElement)> {
self.recurse()
.into_iter()
.map(|r| (r.absolute_offset, r.element))
.collect()
}
pub fn get_elements_by_offset(
&self,
start: Fraction,
end: Fraction,
) -> Vec<(Fraction, MusicElement)> {
self.flatten()
.into_iter()
.filter(|(offset, _)| *offset >= start && *offset < end)
.collect()
}
pub fn get_element_at_or_before(&self, offset: Fraction) -> Option<(Fraction, MusicElement)> {
self.flatten()
.into_iter()
.filter(|(o, _)| *o <= offset)
.max_by_key(|(o, _)| *o)
}
pub fn has_measures(&self) -> bool {
!self.measures.is_empty()
}
pub fn has_voices(&self) -> bool {
false
}
pub fn is_well_formed_notation(&self) -> bool {
(0..self.measures.len()).all(|i| {
let measure = &self.measures[i];
measure.content_duration() <= self.measure_duration(i)
})
}
pub fn template(&self) -> Part {
let mut templated = self.clone();
for measure in templated.measures_mut() {
measure.clear();
}
templated
}
pub fn explode(&self) -> Vec<Part> {
let max_voices = self
.measures
.iter()
.flat_map(|m| m.elements())
.map(|(_, e)| match e {
MusicElement::Chord(c) => c.notes().len(),
_ => 1,
})
.max()
.unwrap_or(1)
.max(1);
let base_name = self.name.clone().unwrap_or_else(|| "Part".to_string());
let mut result: Vec<Part> = (0..max_voices)
.map(|i| {
let mut p = self.template();
p.set_name(format!("{base_name} {}", i + 1));
p
})
.collect();
for (mi, measure) in self.measures.iter().enumerate() {
for (offset, element) in measure.elements() {
match element {
MusicElement::Chord(chord) => {
let mut pitches: Vec<Pitch> =
chord.pitches().into_iter().cloned().collect();
pitches.sort_by(|a, b| b.cmp(a));
for (vi, pitch) in pitches.into_iter().enumerate() {
if let Some(part_measure) = result[vi].measures.get_mut(mi) {
part_measure.insert(
*offset,
MusicElement::Note(Note::new(pitch, chord.duration().clone())),
);
}
}
}
other => {
if let Some(part_measure) = result[0].measures.get_mut(mi) {
part_measure.insert(*offset, other.clone());
}
}
}
}
}
result
}
pub fn renumber_measures(&mut self) {
let has_pickup = self
.measures
.first()
.map(|m| m.is_pickup())
.unwrap_or(false);
for (i, measure) in self.measures.iter_mut().enumerate() {
let number = if has_pickup { i as u32 } else { i as u32 + 1 };
measure.set_number(number);
}
}
pub fn expand_repeats(&self) -> Part {
let mut result = self.clone();
result.measures.clear();
let mut section_start = 0usize;
let mut i = 0usize;
while i < self.measures.len() {
if self.measures[i].is_repeat_start() {
section_start = i;
}
result.measures.push(self.measures[i].clone());
if self.measures[i].is_repeat_end() {
for measure in &self.measures[section_start..=i] {
result.measures.push(measure.clone());
}
section_start = i + 1;
}
i += 1;
}
result.renumber_measures();
result
}
pub fn analyze(&self, method: &str) -> Option<crate::analysis::PartAnalysisResult> {
crate::analysis::analyze_part_by_method(self, method)
}
}
impl fmt::Display for Part {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = self.name.as_deref().unwrap_or("Unnamed Part");
write!(f, "Part '{}' ({} measures)", name, self.measures.len())
}
}
pub fn voices_to_parts(voices: &[Voice]) -> Vec<Part> {
voices
.iter()
.map(|voice| {
let mut part = Part::with_name(format!("Voice {}", voice.id()));
let mut measure = Measure::new(1);
for (offset, element) in voice.elements() {
measure.insert(*offset, element.clone());
}
part.add_measure(measure);
part
})
.collect()
}
pub fn parts_to_voices(parts: &[&Part]) -> Vec<Voice> {
parts
.iter()
.enumerate()
.map(|(i, part)| {
let mut voice = Voice::new(i as u8 + 1);
for (offset, element) in part.flatten() {
voice.insert(offset, element);
}
voice
})
.collect()
}
fn round_to_grid(value: Fraction, grid: Fraction) -> Fraction {
grid * (value / grid).round()
}
fn fraction_gcd(a: Fraction, b: Fraction) -> Fraction {
use num::Integer;
let common_denom = a.denom() * b.denom();
let a_scaled = a.numer() * b.denom();
let b_scaled = b.numer() * a.denom();
Fraction::new(a_scaled.gcd(&b_scaled), common_denom)
}
fn tie_type_for_position(index: usize, total: usize) -> TieType {
if index == 0 {
TieType::Start
} else if index == total - 1 {
TieType::Stop
} else {
TieType::Continue
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_creation() {
let part = Part::with_name("Violin I");
assert_eq!(part.name(), Some("Violin I"));
assert!(part.is_empty());
}
#[test]
fn test_part_measures() {
let mut part = Part::new();
part.add_measure(Measure::new(1));
part.add_measure(Measure::new(2));
assert_eq!(part.num_measures(), 2);
assert_eq!(part.measure(0).unwrap().number(), 1);
}
#[test]
fn test_part_instrument() {
let mut part = Part::with_name("Piano");
part.set_instrument(Instrument::piano());
assert_eq!(part.instrument().unwrap().midi_program(), 0);
}
#[test]
fn test_instrument_creation() {
let trumpet = Instrument::trumpet();
assert_eq!(trumpet.name(), "Trumpet");
assert_eq!(trumpet.transposition(), -2);
}
#[test]
fn test_ensure_measures() {
let mut part = Part::new();
part.ensure_measures(5);
assert_eq!(part.num_measures(), 5);
assert_eq!(part.measure(4).unwrap().number(), 5);
}
#[test]
fn test_measure_duration_context_search() {
use crate::notation::TimeSignature;
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.set_time_signature(TimeSignature::new(3, 4));
part.add_measure(m1);
part.add_measure(Measure::new(2)); part.add_measure(Measure::new(3));
assert_eq!(part.measure(1).unwrap().duration(), Fraction::new(4, 1));
assert_eq!(part.measure_duration(0), Fraction::new(3, 1));
assert_eq!(part.measure_duration(1), Fraction::new(3, 1));
assert_eq!(part.measure_duration(2), Fraction::new(3, 1));
assert_eq!(part.duration(), Fraction::new(9, 1));
}
#[test]
fn test_key_signature_and_clef_context_search() {
use crate::notation::{Clef, KeySignature};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.set_key_signature(KeySignature::g_major());
m1.set_clef(Clef::bass());
part.add_measure(m1);
part.add_measure(Measure::new(2));
assert_eq!(part.key_signature_at(1), Some(&KeySignature::g_major()));
assert_eq!(part.clef_at(1), Some(&Clef::bass()));
assert_eq!(part.time_signature_at(1), None);
assert_eq!(part.key_signature_at(10), None); }
#[test]
fn test_renumber_measures_no_pickup() {
let mut part = Part::new();
part.add_measure(Measure::new(99));
part.add_measure(Measure::new(100));
part.add_measure(Measure::new(101));
part.renumber_measures();
assert_eq!(part.measure(0).unwrap().number(), 1);
assert_eq!(part.measure(1).unwrap().number(), 2);
assert_eq!(part.measure(2).unwrap().number(), 3);
}
#[test]
fn test_insert_and_shift_pushes_later_elements_back() {
use crate::core::{Note, Pitch, Step};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.insert(
Fraction::new(0, 1),
MusicElement::Note(Note::quarter(Pitch::from_parts(Step::C, Some(4), None))),
);
m1.insert(
Fraction::new(1, 1),
MusicElement::Note(Note::quarter(Pitch::from_parts(Step::D, Some(4), None))),
);
part.add_measure(m1);
part.insert_and_shift(
0,
Fraction::new(1, 1),
MusicElement::Note(Note::half(Pitch::from_parts(Step::E, Some(4), None))),
);
let elements = part.measure(0).unwrap().elements().to_vec();
assert_eq!(elements.len(), 3);
assert_eq!(elements[0].0, Fraction::new(0, 1)); assert_eq!(elements[1].0, Fraction::new(1, 1)); assert_eq!(elements[1].1.as_note().unwrap().pitch().name(), "E");
assert_eq!(elements[2].0, Fraction::new(3, 1)); assert_eq!(elements[2].1.as_note().unwrap().pitch().name(), "D");
}
#[test]
fn test_find_consecutive_notes_skips_rests_and_chords() {
use crate::core::{Chord, Note, Pitch, Rest, Step};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.insert(
Fraction::new(0, 1),
MusicElement::Note(Note::quarter(Pitch::from_parts(Step::C, Some(4), None))),
);
m1.insert(Fraction::new(1, 1), MusicElement::Rest(Rest::quarter()));
m1.insert(
Fraction::new(2, 1),
MusicElement::Chord(Chord::major_triad(Pitch::from_parts(
Step::C,
Some(4),
None,
))),
);
part.add_measure(m1);
let mut m2 = Measure::new(2);
m2.insert(
Fraction::new(0, 1),
MusicElement::Note(Note::quarter(Pitch::from_parts(Step::E, Some(4), None))),
);
part.add_measure(m2);
let notes = part.find_consecutive_notes();
assert_eq!(notes.len(), 2);
assert_eq!(notes[0].pitch().name(), "C");
assert_eq!(notes[1].pitch().name(), "E");
}
#[test]
fn test_melodic_intervals_between_consecutive_notes() {
use crate::core::{Note, Pitch, Step};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.insert(
Fraction::new(0, 1),
MusicElement::Note(Note::quarter(Pitch::from_parts(Step::C, Some(4), None))),
);
m1.insert(
Fraction::new(1, 1),
MusicElement::Note(Note::quarter(Pitch::from_parts(Step::E, Some(4), None))),
);
m1.insert(
Fraction::new(2, 1),
MusicElement::Note(Note::quarter(Pitch::from_parts(Step::D, Some(4), None))),
);
part.add_measure(m1);
let intervals = part.melodic_intervals();
assert_eq!(intervals.len(), 2);
assert_eq!(intervals[0].semitones(), 4); assert_eq!(intervals[1].semitones(), -2); }
#[test]
fn test_transpose_shifts_note_and_chord_pitches_leaves_rests() {
use crate::core::{Chord, Interval, Note, Pitch, Rest, Step};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.insert(
Fraction::new(0, 1),
MusicElement::Note(Note::quarter(Pitch::from_parts(Step::C, Some(4), None))),
);
m1.insert(
Fraction::new(1, 1),
MusicElement::Chord(Chord::major_triad(Pitch::from_parts(
Step::C,
Some(4),
None,
))),
);
m1.insert(Fraction::new(2, 1), MusicElement::Rest(Rest::quarter()));
part.add_measure(m1);
let transposed = part.transpose(&Interval::perfect_fifth());
let elements = transposed.measure(0).unwrap().elements().to_vec();
assert_eq!(elements[0].1.as_note().unwrap().pitch().name(), "G");
assert_eq!(
elements[1].1.as_chord().unwrap().root().unwrap().name(),
"G"
);
assert!(elements[2].1.is_rest());
assert_eq!(
part.measure(0).unwrap().elements()[0]
.1
.as_note()
.unwrap()
.pitch()
.name(),
"C"
);
}
#[test]
fn test_augment_or_diminish_scales_offsets_and_durations() {
use crate::core::{Note, Pitch, Step};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.insert(
Fraction::new(1, 1),
MusicElement::Note(Note::quarter(Pitch::from_parts(Step::C, Some(4), None))),
);
part.add_measure(m1);
let augmented = part.augment_or_diminish(Fraction::new(2, 1));
let (offset, element) = &augmented.measure(0).unwrap().elements()[0];
assert_eq!(*offset, Fraction::new(2, 1));
assert_eq!(element.quarter_length(), Fraction::new(2, 1));
let diminished = part.augment_or_diminish(Fraction::new(1, 2));
let (offset, element) = &diminished.measure(0).unwrap().elements()[0];
assert_eq!(*offset, Fraction::new(1, 2));
assert_eq!(element.quarter_length(), Fraction::new(1, 2));
}
#[test]
fn test_quantize_snaps_offsets_and_durations_to_grid() {
use crate::core::{Duration, Note, Pitch, Step};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.set_time_signature(TimeSignature::new(4, 4));
m1.insert(
Fraction::new(11, 40), MusicElement::Note(Note::new(
Pitch::from_parts(Step::C, Some(4), None),
Duration::from_quarter_length(Fraction::new(24, 100)), )),
);
part.add_measure(m1);
part.quantize(Fraction::new(1, 4));
let (offset, element) = &part.measure(0).unwrap().elements()[0];
assert_eq!(*offset, Fraction::new(1, 4));
assert_eq!(element.quarter_length(), Fraction::new(1, 4));
}
#[test]
fn test_slice_by_quarter_lengths_splits_and_ties() {
use crate::core::{Note, Pitch, Step, TieType};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.set_time_signature(TimeSignature::new(4, 4));
m1.insert(
Fraction::new(0, 1),
MusicElement::Note(Note::half(Pitch::from_parts(Step::C, Some(4), None))), );
part.add_measure(m1);
part.slice_by_quarter_lengths(Fraction::new(1, 2), true);
let elements = part.measure(0).unwrap().elements().to_vec();
assert_eq!(elements.len(), 4); assert_eq!(elements[0].0, Fraction::new(0, 1));
assert_eq!(
elements[0].1.as_note().unwrap().tie().unwrap().type_,
TieType::Start
);
assert_eq!(
elements[1].1.as_note().unwrap().tie().unwrap().type_,
TieType::Continue
);
assert_eq!(elements[3].0, Fraction::new(3, 2));
assert_eq!(
elements[3].1.as_note().unwrap().tie().unwrap().type_,
TieType::Stop
);
}
#[test]
fn test_slice_by_beat_splits_note_crossing_beat_boundary() {
use crate::core::{Duration, Note, Pitch, Step, TieType};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.set_time_signature(TimeSignature::new(4, 4)); m1.insert(
Fraction::new(1, 2),
MusicElement::Note(Note::new(
Pitch::from_parts(Step::C, Some(4), None),
Duration::quarter(),
)),
);
part.add_measure(m1);
part.slice_by_beat();
let elements = part.measure(0).unwrap().elements().to_vec();
assert_eq!(elements.len(), 2);
assert_eq!(elements[0].0, Fraction::new(1, 2));
assert_eq!(
elements[0].1.as_note().unwrap().quarter_length(),
Fraction::new(1, 2)
);
assert_eq!(
elements[0].1.as_note().unwrap().tie().unwrap().type_,
TieType::Start
);
assert_eq!(elements[1].0, Fraction::new(1, 1));
assert_eq!(
elements[1].1.as_note().unwrap().quarter_length(),
Fraction::new(1, 2)
);
assert_eq!(
elements[1].1.as_note().unwrap().tie().unwrap().type_,
TieType::Stop
);
}
#[test]
fn test_slice_at_offsets_splits_at_arbitrary_points() {
use crate::core::{Duration, Note, Pitch, Step};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.insert(
Fraction::new(0, 1),
MusicElement::Note(Note::new(
Pitch::from_parts(Step::C, Some(4), None),
Duration::whole(), )),
);
part.add_measure(m1);
part.slice_at_offsets(&[Fraction::new(1, 1), Fraction::new(3, 1)]);
let elements = part.measure(0).unwrap().elements().to_vec();
assert_eq!(elements.len(), 3);
assert_eq!(
elements[0].1.as_note().unwrap().quarter_length(),
Fraction::new(1, 1)
);
assert_eq!(
elements[1].1.as_note().unwrap().quarter_length(),
Fraction::new(2, 1)
);
assert_eq!(
elements[2].1.as_note().unwrap().quarter_length(),
Fraction::new(1, 1)
);
}
#[test]
fn test_slice_by_greatest_divisor_finds_common_unit() {
use crate::core::{Duration, Note, Pitch, Step};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.insert(
Fraction::new(0, 1),
MusicElement::Note(Note::half(Pitch::from_parts(Step::C, Some(4), None))),
);
m1.insert(
Fraction::new(2, 1),
MusicElement::Note(Note::new(
Pitch::from_parts(Step::D, Some(4), None),
Duration::quarter(),
)),
);
part.add_measure(m1);
part.slice_by_greatest_divisor();
let elements = part.measure(0).unwrap().elements().to_vec();
assert_eq!(elements.len(), 3);
for (_, element) in &elements {
assert_eq!(element.quarter_length(), Fraction::new(1, 1));
}
}
#[test]
fn test_make_beams_groups_eighth_notes_within_beats() {
use crate::core::{Duration, Note, Pitch, Step};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.set_time_signature(TimeSignature::new(4, 4));
for i in 0..8 {
m1.insert(
Fraction::new(i, 2),
MusicElement::Note(Note::new(
Pitch::from_parts(Step::C, Some(4), None),
Duration::eighth(),
)),
);
}
part.add_measure(m1);
let beams = part.make_beams(0);
assert_eq!(beams.len(), 4); for beam in &beams {
assert_eq!(beam.indices().len(), 2);
}
}
#[test]
fn test_make_accidentals_reflects_key_signature_context() {
use crate::core::{Accidental, Note, Pitch, Step};
use crate::notation::KeySignature;
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.set_key_signature(KeySignature::g_major()); m1.insert(
Fraction::new(0, 1),
MusicElement::Note(Note::quarter(Pitch::from_parts(
Step::F,
Some(4),
Some(Accidental::Sharp),
))),
);
m1.insert(
Fraction::new(1, 1),
MusicElement::Note(Note::quarter(Pitch::from_parts(Step::F, Some(4), None))),
);
part.add_measure(m1);
let displays = part.make_accidentals(0);
assert_eq!(displays.len(), 2);
assert_eq!(displays[0].display_status, Some(false));
assert_eq!(displays[1].display_status, Some(true));
}
#[test]
fn test_make_notation_applies_ties_and_returns_beams_and_accidentals() {
use crate::core::Note;
use crate::core::{Pitch, Step};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.set_time_signature(TimeSignature::new(2, 4));
m1.insert(
Fraction::new(1, 1),
MusicElement::Note(Note::half(Pitch::from_parts(Step::C, Some(4), None))),
);
part.add_measure(m1);
part.add_measure(Measure::new(2));
let info = part.make_notation();
assert!(
part.measure(0).unwrap().elements()[0]
.1
.as_note()
.unwrap()
.tie()
.is_some()
);
assert_eq!(info.len(), 2);
}
#[test]
fn test_make_ties_splits_note_overflowing_measure_boundary() {
use crate::core::{Note, Pitch, Step, TieType};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.set_time_signature(TimeSignature::new(2, 4)); m1.insert(
Fraction::new(1, 1),
MusicElement::Note(Note::half(Pitch::from_parts(Step::C, Some(4), None))),
);
part.add_measure(m1);
part.add_measure(Measure::new(2));
part.make_ties();
let m1_note = part.measure(0).unwrap().elements()[0].1.as_note().unwrap();
assert_eq!(m1_note.quarter_length(), Fraction::new(1, 1));
assert_eq!(m1_note.tie().unwrap().type_, TieType::Start);
let m2_note = part.measure(1).unwrap().elements()[0].1.as_note().unwrap();
assert_eq!(m2_note.quarter_length(), Fraction::new(1, 1));
assert_eq!(m2_note.tie().unwrap().type_, TieType::Stop);
assert_eq!(m2_note.pitch().name(), "C");
}
#[test]
fn test_make_ties_creates_new_measures_when_needed() {
use crate::core::{Duration, Note, Pitch, Step};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.set_time_signature(TimeSignature::new(1, 4)); m1.insert(
Fraction::new(0, 1),
MusicElement::Note(Note::new(
Pitch::from_parts(Step::C, Some(4), None),
Duration::whole(), )),
);
part.add_measure(m1);
part.make_ties();
assert_eq!(part.num_measures(), 4);
for i in 0..4 {
let note = part.measure(i).unwrap().elements()[0].1.as_note().unwrap();
assert_eq!(note.quarter_length(), Fraction::new(1, 1));
}
}
#[test]
fn test_strip_ties_merges_tied_run_back_into_one_note() {
use crate::core::{Note, Pitch, Step, Tie};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.set_time_signature(TimeSignature::new(2, 4));
let mut first = Note::quarter(Pitch::from_parts(Step::C, Some(4), None));
first.set_tie(Some(Tie::start()));
m1.insert(Fraction::new(1, 1), MusicElement::Note(first));
part.add_measure(m1);
let mut m2 = Measure::new(2);
let mut second = Note::quarter(Pitch::from_parts(Step::C, Some(4), None));
second.set_tie(Some(Tie::stop()));
m2.insert(Fraction::new(0, 1), MusicElement::Note(second));
part.add_measure(m2);
part.strip_ties();
let merged = part.measure(0).unwrap().elements()[0].1.as_note().unwrap();
assert_eq!(merged.quarter_length(), Fraction::new(2, 1));
assert!(merged.tie().is_none());
assert!(part.measure(1).unwrap().is_empty());
}
#[test]
fn test_make_ties_then_strip_ties_round_trip() {
use crate::core::{Duration, Note, Pitch, Step};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.set_time_signature(TimeSignature::new(2, 4));
m1.insert(
Fraction::new(1, 1),
MusicElement::Note(Note::half(Pitch::from_parts(Step::C, Some(4), None))),
);
part.add_measure(m1);
part.add_measure(Measure::new(2));
part.make_ties();
part.strip_ties();
let note = part.measure(0).unwrap().elements()[0].1.as_note().unwrap();
assert_eq!(note.quarter_length(), Duration::half().quarter_length());
assert!(note.tie().is_none());
assert!(part.measure(1).unwrap().is_empty());
}
#[test]
fn test_extend_ties_connects_adjacent_same_pitch_notes() {
use crate::core::{Note, Pitch, Step};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.set_time_signature(TimeSignature::new(4, 4));
m1.insert(
Fraction::new(0, 1),
MusicElement::Note(Note::quarter(Pitch::from_parts(Step::C, Some(4), None))),
);
m1.insert(
Fraction::new(1, 1),
MusicElement::Note(Note::quarter(Pitch::from_parts(Step::C, Some(4), None))),
);
m1.insert(
Fraction::new(2, 1),
MusicElement::Note(Note::quarter(Pitch::from_parts(Step::D, Some(4), None))),
);
part.add_measure(m1);
part.extend_ties();
let elements = part.measure(0).unwrap().elements().to_vec();
assert_eq!(
elements[0].1.as_note().unwrap().tie().unwrap().type_,
crate::core::TieType::Start
);
assert_eq!(
elements[1].1.as_note().unwrap().tie().unwrap().type_,
crate::core::TieType::Stop
);
assert!(elements[2].1.as_note().unwrap().tie().is_none());
}
#[test]
fn test_make_measures_partitions_flat_elements_by_bar_duration() {
use crate::core::{Note, Pitch, Step};
let elements: Vec<(Fraction, MusicElement)> = (0..8)
.map(|i| {
(
Fraction::new(i, 1),
MusicElement::Note(Note::quarter(Pitch::from_parts(Step::C, Some(4), None))),
)
})
.collect();
let part = Part::make_measures(&elements, TimeSignature::new(4, 4));
assert_eq!(part.num_measures(), 2);
assert_eq!(part.measure(0).unwrap().len(), 4);
assert_eq!(part.measure(1).unwrap().len(), 4);
assert_eq!(
part.measure(0).unwrap().time_signature(),
Some(&TimeSignature::new(4, 4))
);
assert_eq!(
part.measure(1).unwrap().elements()[0].0,
Fraction::new(0, 1)
);
}
#[test]
fn test_make_measures_empty_input_yields_one_empty_measure() {
let part = Part::make_measures(&[], TimeSignature::new(3, 4));
assert_eq!(part.num_measures(), 1);
assert!(part.measure(0).unwrap().is_empty());
}
#[test]
fn test_get_time_signatures_only_explicit_changes() {
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.set_time_signature(TimeSignature::new(4, 4));
part.add_measure(m1);
part.add_measure(Measure::new(2)); let mut m3 = Measure::new(3);
m3.set_time_signature(TimeSignature::new(3, 4));
part.add_measure(m3);
assert_eq!(
part.get_time_signatures(),
vec![
(Fraction::new(0, 1), TimeSignature::new(4, 4)),
(Fraction::new(8, 1), TimeSignature::new(3, 4)),
]
);
}
#[test]
fn test_measure_offset_map() {
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.set_time_signature(TimeSignature::new(3, 4));
part.add_measure(m1);
part.add_measure(Measure::new(2)); part.add_measure(Measure::new(3));
assert_eq!(
part.measure_offset_map(),
vec![
(Fraction::new(0, 1), 1),
(Fraction::new(3, 1), 2),
(Fraction::new(6, 1), 3),
]
);
}
#[test]
fn test_flatten_and_recurse_use_absolute_offsets() {
use crate::core::{Note, Pitch, Step};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.set_time_signature(TimeSignature::new(4, 4));
m1.append(MusicElement::Note(Note::quarter(Pitch::from_parts(
Step::C,
Some(4),
None,
))));
part.add_measure(m1);
let mut m2 = Measure::new(2);
m2.append(MusicElement::Note(Note::quarter(Pitch::from_parts(
Step::D,
Some(4),
None,
))));
part.add_measure(m2);
let flat = part.flatten();
assert_eq!(flat.len(), 2);
assert_eq!(flat[0].0, Fraction::new(0, 1));
assert_eq!(flat[1].0, Fraction::new(4, 1));
let recursed = part.recurse();
assert_eq!(recursed.len(), 2);
assert_eq!(recursed[1].measure_number, 2);
assert_eq!(recursed[1].offset_in_measure, Fraction::new(0, 1));
assert_eq!(recursed[1].absolute_offset, Fraction::new(4, 1));
}
#[test]
fn test_get_elements_by_offset_and_at_or_before() {
use crate::core::{Note, Pitch, Step};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.set_time_signature(TimeSignature::new(4, 4));
m1.insert(
Fraction::new(0, 1),
MusicElement::Note(Note::quarter(Pitch::from_parts(Step::C, Some(4), None))),
);
m1.insert(
Fraction::new(2, 1),
MusicElement::Note(Note::quarter(Pitch::from_parts(Step::E, Some(4), None))),
);
part.add_measure(m1);
let in_range = part.get_elements_by_offset(Fraction::new(1, 1), Fraction::new(3, 1));
assert_eq!(in_range.len(), 1);
assert_eq!(in_range[0].0, Fraction::new(2, 1));
let at_or_before = part.get_element_at_or_before(Fraction::new(3, 1)).unwrap();
assert_eq!(at_or_before.0, Fraction::new(2, 1));
assert!(
part.get_element_at_or_before(Fraction::new(-1, 1))
.is_none()
);
}
#[test]
fn test_structural_checks_and_template() {
use crate::core::{Note, Pitch, Step};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.set_time_signature(TimeSignature::new(4, 4));
m1.append(MusicElement::Note(Note::quarter(Pitch::from_parts(
Step::C,
Some(4),
None,
))));
part.add_measure(m1);
assert!(part.has_measures());
assert!(!part.has_voices());
assert!(part.is_well_formed_notation());
let templated = part.template();
assert_eq!(templated.num_measures(), 1);
assert!(templated.measure(0).unwrap().is_empty());
assert_eq!(
templated.measure(0).unwrap().time_signature(),
Some(&TimeSignature::new(4, 4))
);
}
#[test]
fn test_is_well_formed_notation_detects_overfull_measure() {
use crate::core::{Note, Pitch, Step};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.set_time_signature(TimeSignature::new(2, 4)); m1.append(MusicElement::Note(Note::quarter(Pitch::from_parts(
Step::C,
Some(4),
None,
))));
m1.append(MusicElement::Note(Note::quarter(Pitch::from_parts(
Step::D,
Some(4),
None,
))));
m1.append(MusicElement::Note(Note::quarter(Pitch::from_parts(
Step::E,
Some(4),
None,
)))); part.add_measure(m1);
assert!(!part.is_well_formed_notation());
}
#[test]
fn test_explode_splits_chords_into_separate_parts_high_to_low() {
use crate::core::{Chord, Duration, Note, Pitch, Step};
let mut part = Part::with_name("Choir");
let mut m1 = Measure::new(1);
m1.set_time_signature(TimeSignature::new(4, 4));
m1.insert(
Fraction::new(0, 1),
MusicElement::Chord(Chord::from_pitches(
vec![
Pitch::from_parts(Step::C, Some(4), None),
Pitch::from_parts(Step::E, Some(4), None),
Pitch::from_parts(Step::G, Some(4), None),
],
Duration::quarter(),
)),
);
m1.insert(
Fraction::new(1, 1),
MusicElement::Note(Note::quarter(Pitch::from_parts(Step::D, Some(4), None))),
);
part.add_measure(m1);
let exploded = part.explode();
assert_eq!(exploded.len(), 3);
let m0 = exploded[0].measure(0).unwrap();
let m1_out = exploded[1].measure(0).unwrap();
let m2_out = exploded[2].measure(0).unwrap();
assert_eq!(m0.elements()[0].1.as_note().unwrap().pitch().name(), "G");
assert_eq!(m0.elements().len(), 2);
assert_eq!(
m1_out.elements()[0].1.as_note().unwrap().pitch().name(),
"E"
);
assert_eq!(
m2_out.elements()[0].1.as_note().unwrap().pitch().name(),
"C"
);
}
#[test]
fn test_voices_to_parts_and_back() {
use crate::core::{Note, Pitch, Step};
let mut voice1 = Voice::new(1);
voice1.insert(
Fraction::new(0, 1),
MusicElement::Note(Note::quarter(Pitch::from_parts(Step::C, Some(4), None))),
);
let mut voice2 = Voice::new(2);
voice2.insert(
Fraction::new(0, 1),
MusicElement::Note(Note::quarter(Pitch::from_parts(Step::E, Some(4), None))),
);
let parts = voices_to_parts(&[voice1, voice2]);
assert_eq!(parts.len(), 2);
assert_eq!(parts[0].name(), Some("Voice 1"));
assert_eq!(
parts[1].measure(0).unwrap().elements()[0]
.1
.as_note()
.unwrap()
.pitch()
.name(),
"E"
);
let voices_back = parts_to_voices(&[&parts[0], &parts[1]]);
assert_eq!(voices_back.len(), 2);
assert_eq!(
voices_back[0].elements()[0]
.1
.as_note()
.unwrap()
.pitch()
.name(),
"C"
);
assert_eq!(
voices_back[1].elements()[0]
.1
.as_note()
.unwrap()
.pitch()
.name(),
"E"
);
}
#[test]
fn test_renumber_measures_with_pickup() {
let mut part = Part::new();
part.add_measure(Measure::pickup());
part.add_measure(Measure::new(50));
part.add_measure(Measure::new(51));
part.renumber_measures();
assert_eq!(part.measure(0).unwrap().number(), 0);
assert_eq!(part.measure(1).unwrap().number(), 1);
assert_eq!(part.measure(2).unwrap().number(), 2);
}
#[test]
fn test_expand_repeats_duplicates_marked_section() {
use crate::core::{Note, Pitch, Step};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.set_repeat_start(true);
m1.append(MusicElement::Note(Note::quarter(Pitch::from_parts(
Step::C,
Some(4),
None,
))));
part.add_measure(m1);
let mut m2 = Measure::new(2);
m2.set_repeat_end(true);
m2.append(MusicElement::Note(Note::quarter(Pitch::from_parts(
Step::D,
Some(4),
None,
))));
part.add_measure(m2);
let mut m3 = Measure::new(3);
m3.append(MusicElement::Note(Note::quarter(Pitch::from_parts(
Step::E,
Some(4),
None,
))));
part.add_measure(m3);
let expanded = part.expand_repeats();
assert_eq!(expanded.num_measures(), 5);
let pitches: Vec<String> = (0..5)
.map(|i| {
expanded.measure(i).unwrap().elements()[0]
.1
.as_note()
.unwrap()
.pitch()
.name()
})
.collect();
assert_eq!(pitches, vec!["C", "D", "C", "D", "E"]);
for i in 0..5 {
assert_eq!(expanded.measure(i).unwrap().number(), i as u32 + 1);
}
}
#[test]
fn test_expand_repeats_no_repeats_is_identity() {
use crate::core::{Note, Pitch, Step};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.append(MusicElement::Note(Note::quarter(Pitch::from_parts(
Step::C,
Some(4),
None,
))));
part.add_measure(m1);
let expanded = part.expand_repeats();
assert_eq!(expanded.num_measures(), 1);
}
#[test]
fn test_analyze_dispatches_by_method_name() {
use crate::analysis::PartAnalysisResult;
use crate::core::{Note, Pitch, Step};
let mut part = Part::new();
let mut m1 = Measure::new(1);
m1.append(MusicElement::Note(Note::quarter(Pitch::from_parts(
Step::C,
Some(4),
None,
))));
m1.append(MusicElement::Note(Note::quarter(Pitch::from_parts(
Step::G,
Some(5),
None,
))));
part.add_measure(m1);
match part.analyze("ambitus").unwrap() {
PartAnalysisResult::Ambitus(Some(interval)) => assert!(interval.semitones() > 0),
other => panic!("expected Some(Ambitus), got {other:?}"),
}
assert!(matches!(
part.analyze("key"),
Some(PartAnalysisResult::Key(_))
));
assert!(matches!(
part.analyze("melodicIntervalDiversity"),
Some(PartAnalysisResult::MelodicIntervalDiversity(_))
));
assert!(part.analyze("nonsense").is_none());
}
}