use crate::model::{
AccidentalMark, ChordEvent, EventAttachments, Fraction, Part, Pitch, RestEvent, RestVisibility,
TieRole, TimedEventKind, TimelineEventKind, TupletRole,
};
use super::{
FractionExt, MeasureSequence, MusicXmlWriter, NoteWrite, SequenceEvent, TimeModification,
TupletNumbers, unsupported_note_type_warning, unsupported_tuplet_time_modification_warning,
variable_chord_duration_export_warning,
};
impl<'score> MusicXmlWriter<'score> {
pub(crate) fn write_sequence(
&mut self,
sequence: &MeasureSequence<'score>,
part: &Part,
) -> Fraction {
let mut cursor = Fraction::zero();
let mut last_onset = Fraction::zero();
let tuplet_numbers = sequence_tuplet_numbers(sequence);
for event in &sequence.events {
let onset = event.onset();
let is_chord_member = event.is_chord_member();
if is_chord_member && onset == last_onset {
self.write_event(event, sequence, part, &tuplet_numbers, true);
continue;
}
if let Some((clef, pre_backup, cursor_forward)) = event.clef_cursor_script() {
let attachments = event.attachments();
self.write_harmony_and_directions(attachments, sequence, part);
self.write_grace_groups(attachments, sequence, part, &tuplet_numbers);
self.write_backup(pre_backup);
cursor = cursor.subtract(pre_backup);
self.write_mid_tune_clef(clef, sequence.staff, part);
self.write_forward(cursor_forward);
cursor = cursor.checked_add(cursor_forward);
continue;
}
if cursor.less_than(onset) {
self.write_forward(onset.subtract(cursor));
cursor = onset;
} else if onset.less_than(cursor) {
self.write_backup(cursor.subtract(onset));
cursor = onset;
}
self.write_event(event, sequence, part, &tuplet_numbers, false);
if event.advances_time() {
cursor = cursor.checked_add(event.duration());
last_onset = onset;
}
}
cursor
}
fn write_event(
&mut self,
event: &SequenceEvent<'score>,
sequence: &MeasureSequence<'score>,
part: &Part,
tuplet_numbers: &TupletNumbers,
chord_member: bool,
) {
let attachments = event.attachments();
self.write_harmony_and_directions(attachments, sequence, part);
self.write_grace_groups(attachments, sequence, part, tuplet_numbers);
match event {
SequenceEvent::Timed(timed) => match &timed.kind {
TimedEventKind::Note(note) => {
self.write_note(
NoteWrite {
pitch: Some(¬e.pitch),
rest: None,
duration: timed.duration,
source: timed.source,
written_accidental: note.written_accidental.as_ref(),
attachments,
chord_member: chord_member || note.chord_member,
measure_rest: false,
unpitched: sequence.unpitched,
grace: false,
grace_slash: false,
chord_tuplet_time_modification: None,
},
sequence,
part,
tuplet_numbers,
);
}
TimedEventKind::Chord(chord) => {
self.write_chord(chord, attachments, sequence, part, tuplet_numbers);
}
TimedEventKind::Rest(rest) => {
if timed.attachments.musicxml_forward {
self.write_forward(timed.duration);
return;
}
self.write_note(
NoteWrite {
pitch: None,
rest: Some(rest),
duration: timed.duration,
source: timed.source,
written_accidental: None,
attachments,
chord_member: false,
measure_rest: sequence.is_full_measure_rest(
timed.onset,
timed.duration,
rest,
),
unpitched: false,
grace: false,
grace_slash: false,
chord_tuplet_time_modification: None,
},
sequence,
part,
tuplet_numbers,
);
}
TimedEventKind::Spacer
| TimedEventKind::Barline(_)
| TimedEventKind::RepeatEnding(_)
| TimedEventKind::RepeatEndingClose(_) => {}
TimedEventKind::KeyChange(key) => {
self.active_key = Some(key.clone());
self.write_mid_tune_key(key);
}
TimedEventKind::MeterChange(meter) => self.write_mid_tune_meter(meter),
TimedEventKind::ClefChange(clef) => {
if let Some(cursor_back) = clef.musicxml_cursor_back {
self.write_backup(cursor_back);
self.write_mid_tune_clef(clef, sequence.staff, part);
self.write_forward(cursor_back);
} else {
self.write_mid_tune_clef(clef, sequence.staff, part);
}
}
TimedEventKind::TempoChange(tempo) => self.write_tempo_direction(tempo),
TimedEventKind::SectionLabel(label) => self.write_rehearsal_direction(label),
},
SequenceEvent::Overlay(timed) => match &timed.kind {
TimelineEventKind::Note {
step,
octave,
effective_accidental,
accidental,
accidental_source,
chord,
} => {
let pitch = Pitch {
step: *step,
alter: effective_accidental
.map(|accidental| accidental.alter())
.unwrap_or(0),
octave: *octave,
spelling_source: timed.span,
};
let written_accidental = accidental.map(|kind| AccidentalMark {
kind,
explicit: true,
courtesy: false,
source: accidental_source.unwrap_or(timed.span),
});
self.write_note(
NoteWrite {
pitch: Some(&pitch),
rest: None,
duration: timed.duration,
source: timed.span,
written_accidental: written_accidental.as_ref(),
attachments,
chord_member: chord_member || *chord,
measure_rest: false,
unpitched: sequence.unpitched,
grace: false,
grace_slash: false,
chord_tuplet_time_modification: None,
},
sequence,
part,
tuplet_numbers,
);
}
TimelineEventKind::Rest { visibility, .. } => {
let rest = RestEvent {
visibility: *visibility,
};
self.write_note(
NoteWrite {
pitch: None,
rest: Some(&rest),
duration: timed.duration,
source: timed.span,
written_accidental: None,
attachments,
chord_member: false,
measure_rest: sequence.is_full_measure_rest(
timed.onset,
timed.duration,
&rest,
),
unpitched: false,
grace: false,
grace_slash: false,
chord_tuplet_time_modification: None,
},
sequence,
part,
tuplet_numbers,
);
}
TimelineEventKind::KeyChange(_)
| TimelineEventKind::MeterChange(_)
| TimelineEventKind::ClefChange(_)
| TimelineEventKind::TempoChange(_)
| TimelineEventKind::SectionLabel(_) => {}
TimelineEventKind::Spacer
| TimelineEventKind::Barline { .. }
| TimelineEventKind::VariantEnding { .. }
| TimelineEventKind::VariantEndingClose { .. } => {}
},
}
self.write_after_grace_groups(attachments, sequence, part, tuplet_numbers);
}
fn write_chord(
&mut self,
chord: &ChordEvent,
event_attachments: &EventAttachments,
sequence: &MeasureSequence<'score>,
part: &Part,
tuplet_numbers: &TupletNumbers,
) {
let variable_durations = chord
.members
.iter()
.any(|member| member.duration != chord.members[0].duration);
if variable_durations {
self.diagnostics
.push(variable_chord_duration_export_warning(chord.source_span));
}
let chord_tuplet_time_modification =
TimeModification::composite(&event_attachments.tuplets)
.ok()
.flatten()
.or_else(|| tremolo_time_modification_for(event_attachments));
for (index, member) in chord.members.iter().enumerate() {
let attachments = if index == 0 {
event_attachments.clone()
} else {
member.attachments.clone()
};
self.write_note(
NoteWrite {
pitch: Some(&member.pitch),
rest: None,
duration: member.duration,
source: member.source_span,
written_accidental: member.written_accidental.as_ref(),
attachments: &attachments,
chord_member: index > 0,
measure_rest: false,
unpitched: sequence.unpitched,
grace: false,
grace_slash: false,
chord_tuplet_time_modification: (index > 0)
.then_some(chord_tuplet_time_modification)
.flatten(),
},
sequence,
part,
tuplet_numbers,
);
}
}
pub(crate) fn write_note(
&mut self,
note: NoteWrite<'_>,
sequence: &MeasureSequence<'score>,
part: &Part,
tuplet_numbers: &TupletNumbers,
) {
let print_no = note
.rest
.is_some_and(|rest| rest.visibility == RestVisibility::Invisible);
let attrs = print_no.then_some([("print-object", "no")]);
let attrs_slice = attrs.as_ref().map_or(&[][..], |attrs| &attrs[..]);
self.xml.start("note", attrs_slice);
if note.chord_member {
self.xml.empty("chord", &[]);
}
if note.grace {
if note.grace_slash {
self.xml.empty("grace", &[("slash", "yes")]);
} else {
self.xml.empty("grace", &[]);
}
}
if let Some(pitch) = note.pitch {
if note.unpitched {
self.write_unpitched(pitch);
} else {
self.write_pitch(pitch);
}
} else if note.measure_rest {
self.xml.empty("rest", &[("measure", "yes")]);
} else {
self.xml.empty("rest", &[]);
}
let explicit_time_modification = match note.chord_tuplet_time_modification {
Some(time_modification) => Some(time_modification),
None => match TimeModification::composite(¬e.attachments.tuplets) {
Ok(None) => tremolo_time_modification_for(note.attachments),
Ok(time_modification) => time_modification,
Err(()) => {
self.diagnostics
.push(unsupported_tuplet_time_modification_warning(note.source));
None
}
},
};
let spelling = note_spelling(note.duration, explicit_time_modification);
let omit_inexpressible_rest_spelling = note.rest.is_some()
&& explicit_time_modification.is_none()
&& (spelling.unsupported || spelling.time_modification.is_some());
if spelling.unsupported && !omit_inexpressible_rest_spelling {
self.diagnostics
.push(unsupported_note_type_warning(note.source, note.duration));
}
if !note.grace {
let duration = self.duration_to_divisions(note.duration, note.source);
self.xml.text_element("duration", &duration.to_string());
}
self.write_ties(¬e.attachments.ties);
if note.pitch.is_some()
&& !note.grace
&& let Some(instrument) = ¬e.attachments.instrument
{
self.xml
.empty("instrument", &[("id", instrument.id.as_str())]);
}
self.xml.text_element("voice", &sequence.voice_number);
if !omit_inexpressible_rest_spelling {
self.xml.text_element("type", spelling.note_type);
for _ in 0..spelling.dots {
self.xml.empty("dot", &[]);
}
}
if let Some(accidental) = note.written_accidental
&& accidental.explicit
&& self.score.accidental_policy.preserve_explicit_accidentals
{
self.xml
.text_element("accidental", accidental.kind.musicxml_name());
}
let time_modification = if omit_inexpressible_rest_spelling {
None
} else {
explicit_time_modification.or(spelling.time_modification)
};
if let Some(time_modification) = time_modification {
self.write_time_modification(time_modification);
}
if part.staves.len() > 1 {
self.xml
.text_element("staff", &sequence.staff.value.to_string());
}
let ordered_attachments;
let notation_attachments = if note.attachments.tuplets.len() > 1 {
ordered_attachments = ordered_tuplet_notation_attachments(note.attachments);
&ordered_attachments
} else {
note.attachments
};
self.write_notations(
notation_attachments,
time_modification,
tuplet_numbers,
&sequence.slur_voice_key,
);
self.write_lyrics(¬e.attachments.lyrics, &sequence.slur_voice_key);
self.xml.end("note");
}
fn write_pitch(&mut self, pitch: &Pitch) {
self.xml.start("pitch", &[]);
self.xml.text_element("step", &pitch.step.to_string());
if pitch.alter != 0 {
self.xml.text_element("alter", &pitch.alter.to_string());
}
self.xml.text_element("octave", &pitch.octave.to_string());
self.xml.end("pitch");
}
fn write_unpitched(&mut self, pitch: &Pitch) {
self.xml.start("unpitched", &[]);
self.xml
.text_element("display-step", &pitch.step.to_string());
self.xml
.text_element("display-octave", &pitch.octave.to_string());
self.xml.end("unpitched");
}
fn write_ties(&mut self, ties: &[crate::model::TieAttachment]) {
for tie in ties {
self.xml.empty(
"tie",
&[(
"type",
match tie.role {
TieRole::Start => "start",
TieRole::Stop => "stop",
},
)],
);
}
}
}
fn tremolo_time_modification_for(attachments: &EventAttachments) -> Option<TimeModification> {
attachments
.decorations
.iter()
.find_map(|decoration| super::notation::tremolo_time_modification(&decoration.name))
}
fn ordered_tuplet_notation_attachments(attachments: &EventAttachments) -> EventAttachments {
let mut ordered = attachments.clone();
ordered.tuplets.sort_by(|a, b| {
let role_rank = |role| match role {
TupletRole::Start => 0u8,
TupletRole::Continue => 1,
TupletRole::Stop => 2,
};
role_rank(a.role)
.cmp(&role_rank(b.role))
.then_with(|| match a.role {
TupletRole::Stop => b.pair_id.cmp(&a.pair_id),
TupletRole::Start | TupletRole::Continue => a.pair_id.cmp(&b.pair_id),
})
});
ordered
}
fn sequence_tuplet_numbers(sequence: &MeasureSequence<'_>) -> TupletNumbers {
let mut numbers = TupletNumbers::default();
let mut active = Vec::<(u32, u32)>::new();
for event in &sequence.events {
let mut starts = event
.attachments()
.tuplets
.iter()
.filter(|tuplet| tuplet.role == TupletRole::Start)
.collect::<Vec<_>>();
starts.sort_by_key(|tuplet| tuplet.pair_id);
for tuplet in starts {
if numbers
.pairs
.iter()
.any(|(pair, _)| *pair == tuplet.pair_id)
{
continue;
}
let number = next_tuplet_number(&active);
numbers.pairs.push((tuplet.pair_id, number));
active.push((tuplet.pair_id, number));
}
let mut stops = event
.attachments()
.tuplets
.iter()
.filter(|tuplet| tuplet.role == TupletRole::Stop)
.collect::<Vec<_>>();
stops.sort_by_key(|tuplet| std::cmp::Reverse(tuplet.pair_id));
for tuplet in stops {
if !numbers
.pairs
.iter()
.any(|(pair, _)| *pair == tuplet.pair_id)
{
numbers.pairs.push((tuplet.pair_id, 1));
}
active.retain(|(pair, _)| *pair != tuplet.pair_id);
}
}
numbers
}
fn next_tuplet_number(active: &[(u32, u32)]) -> u32 {
for number in 1..=16 {
if !active
.iter()
.any(|(_, active_number)| *active_number == number)
{
return number;
}
}
16
}
#[derive(Debug, Clone, Copy)]
struct NoteSpelling {
note_type: &'static str,
dots: u8,
time_modification: Option<TimeModification>,
unsupported: bool,
}
fn note_spelling(
duration: Fraction,
explicit_time_modification: Option<TimeModification>,
) -> NoteSpelling {
if duration == Fraction::zero() {
return NoteSpelling {
note_type: "eighth",
dots: 0,
time_modification: None,
unsupported: false,
};
}
if let Some(time_modification) = explicit_time_modification {
let normal_duration = duration.checked_mul(Fraction::new(
time_modification.actual_notes,
time_modification.normal_notes,
));
for candidate in note_type_candidates() {
for dots in 0..=3 {
if dotted_fraction(candidate.fraction, dots) == normal_duration {
return NoteSpelling {
note_type: candidate.name,
dots,
time_modification: None,
unsupported: false,
};
}
}
}
}
for candidate in note_type_candidates() {
for dots in 0..=3 {
if dotted_fraction(candidate.fraction, dots) == duration {
return NoteSpelling {
note_type: candidate.name,
dots,
time_modification: None,
unsupported: false,
};
}
}
}
for candidate in note_type_candidates() {
for actual_notes in 2u32..=9 {
for normal_notes in 1u32..=9 {
if normal_notes.saturating_mul(2) < actual_notes
|| normal_notes > actual_notes.saturating_mul(2)
{
continue;
}
if candidate
.fraction
.checked_mul(Fraction::new(normal_notes, actual_notes))
== duration
{
return NoteSpelling {
note_type: candidate.name,
dots: 0,
time_modification: Some(TimeModification {
actual_notes,
normal_notes,
}),
unsupported: false,
};
}
}
}
}
NoteSpelling {
note_type: "quarter",
dots: 0,
time_modification: None,
unsupported: true,
}
}
#[derive(Debug, Clone, Copy)]
struct NoteTypeCandidate {
name: &'static str,
fraction: Fraction,
}
fn note_type_candidates() -> &'static [NoteTypeCandidate] {
&[
NoteTypeCandidate {
name: "maxima",
fraction: Fraction {
numerator: 8,
denominator: 1,
},
},
NoteTypeCandidate {
name: "long",
fraction: Fraction {
numerator: 4,
denominator: 1,
},
},
NoteTypeCandidate {
name: "breve",
fraction: Fraction {
numerator: 2,
denominator: 1,
},
},
NoteTypeCandidate {
name: "whole",
fraction: Fraction {
numerator: 1,
denominator: 1,
},
},
NoteTypeCandidate {
name: "half",
fraction: Fraction {
numerator: 1,
denominator: 2,
},
},
NoteTypeCandidate {
name: "quarter",
fraction: Fraction {
numerator: 1,
denominator: 4,
},
},
NoteTypeCandidate {
name: "eighth",
fraction: Fraction {
numerator: 1,
denominator: 8,
},
},
NoteTypeCandidate {
name: "16th",
fraction: Fraction {
numerator: 1,
denominator: 16,
},
},
NoteTypeCandidate {
name: "32nd",
fraction: Fraction {
numerator: 1,
denominator: 32,
},
},
NoteTypeCandidate {
name: "64th",
fraction: Fraction {
numerator: 1,
denominator: 64,
},
},
NoteTypeCandidate {
name: "128th",
fraction: Fraction {
numerator: 1,
denominator: 128,
},
},
]
}
fn dotted_fraction(base: Fraction, dots: u8) -> Fraction {
let mut duration = base;
let mut dot = base;
for _ in 0..dots {
dot = Fraction::new(dot.numerator, dot.denominator.saturating_mul(2));
duration = duration.checked_add(dot);
}
duration
}