1mod notate;
18mod partstaff;
19mod read;
20mod tree;
21
22pub use read::from_musicxml;
23
24use crate::articulations::Articulation;
25use crate::bar::{Barline, Ending, RepeatDirection};
26use crate::chord::Chord;
27use crate::chordsymbol::ChordSymbol;
28use crate::clef::Clef;
29use crate::defaults::FloatType;
30use crate::duration::{Duration, DurationType, Tuplet};
31use crate::dynamics::Dynamic;
32use crate::error::{Error, Result};
33use crate::expressions::{ArpeggioType, Expression, Ornament};
34use crate::instrument::Instrument;
35use crate::interval::Interval;
36use crate::key::KeySignature;
37use crate::layout::{Layout, PageLayout, StaffLayout, StaffType, SystemLayout, Tenths};
38use crate::metadata::{Metadata, namespace_name};
39use crate::meter::TimeSignature;
40use crate::notation::{
41 Beam, BeamDirection, BeamType, Beams, Lyric, Notehead, Placement, StemDirection, Tie,
42};
43use crate::note::Note;
44use crate::pitch::{Accidental, Pitch};
45use crate::repeat::{RepeatExpression, RepeatExpressionKind};
46use crate::rest::{FullMeasure, Rest};
47use crate::spanner::{
48 PedalForm, PedalObject, PedalObjectKind, PedalType, SlideType, Spanner, SpannerKind,
49};
50use crate::stream::{StaffGroup, Stream, StreamElement, StreamKind};
51use crate::style::TextStyle;
52use crate::tempo::{MetricModulation, MetronomeMark};
53use crate::text::TextBox;
54
55use tree::Element;
56
57pub const DIVISIONS_PER_QUARTER: FloatType = 10080.0;
61
62pub const MUSICXML_VERSION: &str = "4.0";
64
65#[derive(Clone, Debug, PartialEq, Eq)]
71#[non_exhaustive]
72pub struct ExportOptions {
73 pub encoding_date: Option<String>,
76 pub software: String,
79 pub default_title: Option<String>,
82 pub default_author: Option<String>,
85 pub make_notation: bool,
99}
100
101impl Default for ExportOptions {
102 fn default() -> Self {
103 Self {
104 encoding_date: None,
105 software: format!("music21-rs v.{}", env!("CARGO_PKG_VERSION")),
106 default_title: Some("Music21 Fragment".to_string()),
107 default_author: Some("Music21".to_string()),
108 make_notation: false,
109 }
110 }
111}
112
113pub fn to_musicxml(score: &Stream, options: &ExportOptions) -> Result<String> {
162 let prepared;
167 let score = if options.make_notation {
168 prepared = notate::notated(score)?;
169 &prepared
170 } else if holds_sounding_pitch(score) || score.holds_transposing_ottava() {
171 prepared = score.to_written_pitch_by(true)?;
172 &prepared
173 } else {
174 score
175 };
176 let root = score_element(score, options)?;
177 Ok(format!(
178 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE score-partwise PUBLIC \
179 \"-//Recordare//DTD MusicXML {MUSICXML_VERSION} Partwise//EN\" \
180 \"http://www.musicxml.org/dtds/partwise.dtd\">\n{}",
181 root.dump()
182 ))
183}
184
185fn holds_sounding_pitch(stream: &Stream) -> bool {
188 stream.at_sounding_pitch() == Some(true)
189 || stream
190 .events()
191 .iter()
192 .filter_map(|event| event.element().as_stream())
193 .any(holds_sounding_pitch)
194}
195
196fn export_error(message: impl Into<String>) -> Error {
197 Error::music_xml(message.into())
198}
199
200fn score_element(score: &Stream, options: &ExportOptions) -> Result<Element> {
204 if score.is_empty() {
205 return score_element(&empty_score(), options);
206 }
207 let parts: Vec<&Stream> = if has_part_like_streams(score) {
208 for event in score.events() {
209 if let StreamElement::Stream(inner) = event.element()
210 && matches!(inner.kind(), StreamKind::Score | StreamKind::Opus)
211 {
212 return Err(export_error(
213 "Exporting scores nested inside scores is not supported",
214 ));
215 }
216 }
217 score
218 .events()
219 .iter()
220 .filter_map(|event| event.element().as_stream())
221 .filter(|stream| matches!(stream.kind(), StreamKind::Part | StreamKind::PartStaff))
222 .collect()
223 } else {
224 vec![score]
225 };
226
227 let groups = score.staff_groups();
228 let joined = joinable_groups(&parts, groups);
229 let voice_bases = voice_bases(&parts, groups, &joined);
230 let numbered = id_locals(score, &parts);
231 let mut shared = Shared::default();
232 let mut exported = Vec::with_capacity(parts.len());
233 for (index, part) in parts.iter().copied().enumerate() {
234 let spanners = numbered.clone();
235 let mut role = StaffRole::default();
236 for group in &joined {
237 let parts_of = groups[*group].parts();
238 if let Some(at) = parts_of.iter().position(|position| *position == index) {
239 role.previous = at.checked_sub(1).map(|before| parts[parts_of[before]]);
240 role.later = at > 0;
241 if at == 0 {
242 role.followers = parts_of[1..]
243 .iter()
244 .map(|position| parts[*position])
245 .collect();
246 }
247 }
248 }
249 let previous = role.previous;
250 exported.push(Some(PartExporter::new(part, &mut shared, &role)?.parse(
251 spanners,
252 previous,
253 &voice_bases,
254 )?));
255 }
256
257 for group in &joined {
259 let positions = groups[*group].parts();
260 let mut roots: Vec<Element> = positions
261 .iter()
262 .map(|position| {
263 exported[*position]
264 .as_mut()
265 .map(|part| std::mem::replace(&mut part.part, Element::new("part")))
266 .expect("a staff is joined into one group only")
267 })
268 .collect();
269 let staves: Vec<&Stream> = positions.iter().map(|position| parts[*position]).collect();
270 partstaff::join(
271 &mut roots,
272 differs_across(&staves, first_key, same_key),
273 differs_across(&staves, first_meter, same_meter),
274 )?;
275 let first = roots.swap_remove(0);
276 if let Some(part) = exported[positions[0]].as_mut() {
277 part.part = first;
278 }
279 for position in &positions[1..] {
280 exported[*position] = None;
281 }
282 }
283
284 let mut root = Element::new("score-partwise");
285 root.set("version", MUSICXML_VERSION);
286 let metadata = score.metadata();
287 set_titles(&mut root, metadata, options);
288 set_identification(&mut root, metadata, options);
289 set_defaults(&mut root, score);
290 for event in score.events() {
291 if let StreamElement::TextBox(text) = event.element() {
292 root.push(credit_element(text)?);
293 }
294 }
295 set_part_list(root.sub("part-list"), &exported, groups, &joined);
296 for (index, part) in exported.into_iter().flatten().enumerate() {
297 add_divider_comment(&mut root, &format!("Part {}", index + 1));
298 root.push(part.part);
299 }
300 Ok(root)
301}
302
303#[derive(Default)]
305struct StaffRole<'a> {
306 previous: Option<&'a Stream>,
308 followers: Vec<&'a Stream>,
310 later: bool,
312}
313
314fn joinable_groups(parts: &[&Stream], groups: &[StaffGroup]) -> Vec<usize> {
319 let mut joinable: Vec<usize> = Vec::new();
320 for (index, group) in groups.iter().enumerate() {
321 let staves = group.parts();
322 if staves.len() <= 1 {
323 continue;
324 }
325 let fits = staves.iter().all(|position| {
326 parts.get(*position).is_some_and(|part| {
327 part.kind() == StreamKind::PartStaff && !part.measures().is_empty()
328 })
329 });
330 if !fits
331 || joinable
332 .iter()
333 .any(|known| groups[*known].parts() == staves)
334 {
335 continue;
336 }
337 joinable.push(index);
338 }
339 let mut seen: Vec<usize> = Vec::new();
340 for index in &joinable {
341 for position in groups[*index].parts() {
342 if seen.contains(position) {
343 return Vec::new();
344 }
345 seen.push(*position);
346 }
347 }
348 joinable
349}
350
351fn voice_bases(
356 parts: &[&Stream],
357 groups: &[StaffGroup],
358 joined: &[usize],
359) -> std::collections::HashMap<usize, u32> {
360 let mut bases = std::collections::HashMap::new();
361 for group in joined {
362 let mut measures: Vec<(FloatType, usize, &Stream)> = Vec::new();
363 for (staff, position) in groups[*group].parts().iter().enumerate() {
364 for event in parts[*position].events() {
365 if let Some(measure) = event
366 .element()
367 .as_stream()
368 .filter(|stream| stream.kind() == StreamKind::Measure)
369 {
370 measures.push((event.offset(), staff, measure));
371 }
372 }
373 }
374 measures.sort_by(|left, right| left.0.total_cmp(&right.0).then(left.1.cmp(&right.1)));
375 let mut next = 0;
376 let mut stack_offset = None;
377 for (offset, _, measure) in measures {
378 if stack_offset != Some(offset) {
379 stack_offset = Some(offset);
380 next = 0;
381 }
382 bases.insert(measure as *const Stream as usize, next);
383 next += measure
384 .voices()
385 .iter()
386 .filter(|voice| voice.id().is_none())
387 .count() as u32;
388 }
389 }
390 bases
391}
392
393fn first_of<'a>(
395 staff: &'a Stream,
396 pick: impl Fn(&'a StreamElement) -> bool,
397) -> Option<&'a StreamElement> {
398 staff
399 .recurse()
400 .into_iter()
401 .map(|(_, element)| element)
402 .find(|element| pick(element))
403}
404
405fn first_key(staff: &Stream) -> Option<&StreamElement> {
406 first_of(staff, |element| {
407 matches!(
408 element,
409 StreamElement::Key(_) | StreamElement::KeySignature(_)
410 )
411 })
412}
413
414fn first_meter(staff: &Stream) -> Option<&StreamElement> {
415 first_of(staff, |element| {
416 matches!(element, StreamElement::TimeSignature(_))
417 })
418}
419
420fn differs_across(
423 staves: &[&Stream],
424 first: impl Fn(&Stream) -> Option<&StreamElement>,
425 same: impl Fn(&StreamElement, &StreamElement) -> bool,
426) -> bool {
427 let mut initial: Option<&StreamElement> = None;
428 for staff in staves {
429 match initial {
430 None => initial = first(staff),
431 Some(initial) => {
432 if let Some(later) = first(staff)
433 && !same(later, initial)
434 {
435 return true;
436 }
437 }
438 }
439 }
440 false
441}
442
443fn same_key(left: &StreamElement, right: &StreamElement) -> bool {
447 match (left, right) {
448 (StreamElement::Key(left), StreamElement::Key(right)) => {
449 left.tonic().name() == right.tonic().name() && left.mode() == right.mode()
450 }
451 (StreamElement::KeySignature(left), StreamElement::KeySignature(right)) => {
452 left.sharps() == right.sharps()
453 && left.altered_pitches().ok().map(|pitches| {
454 pitches
455 .iter()
456 .map(Pitch::name_with_octave)
457 .collect::<Vec<_>>()
458 }) == right.altered_pitches().ok().map(|pitches| {
459 pitches
460 .iter()
461 .map(Pitch::name_with_octave)
462 .collect::<Vec<_>>()
463 })
464 }
465 _ => false,
466 }
467}
468
469fn same_meter(left: &StreamElement, right: &StreamElement) -> bool {
471 match (left, right) {
472 (StreamElement::TimeSignature(left), StreamElement::TimeSignature(right)) => {
473 left.numerator() == right.numerator() && left.denominator() == right.denominator()
474 }
475 _ => false,
476 }
477}
478
479fn set_part_list(
483 part_list: &mut Element,
484 parts: &[Option<ExportedPart>],
485 groups: &[StaffGroup],
486 joined: &[usize],
487) {
488 let mut numbers: Vec<Option<usize>> = vec![None; groups.len()];
489 let mut next = 1;
490 for (index, part) in parts.iter().enumerate() {
491 let Some(part) = part else {
492 continue;
493 };
494 for (group_index, group) in groups.iter().enumerate() {
495 if joined.contains(&group_index) {
496 continue;
497 }
498 if group.parts().first() == Some(&index) {
499 let mut start = staff_group_element(group);
500 start.set("number", next.to_string());
501 numbers[group_index] = Some(next);
502 next += 1;
503 part_list.push(start);
504 }
505 }
506 part_list.push(part.score_part.clone());
507 for (group_index, group) in groups.iter().enumerate() {
508 if joined.contains(&group_index) {
509 continue;
510 }
511 if group.parts().last() == Some(&index) {
512 let mut stop = Element::new("part-group");
513 stop.set("type", "stop");
514 if let Some(number) = numbers[group_index] {
515 stop.set("number", number.to_string());
516 }
517 part_list.push(stop);
518 }
519 }
520 }
521}
522
523fn staff_group_element(group: &StaffGroup) -> Element {
525 let mut element = Element::new("part-group");
526 element.set("type", "start");
527 if let Some(name) = group.name() {
528 element.sub_text("group-name", name);
529 }
530 if group.name_hidden() {
531 element.sub("group-name-display").set("print-object", "no");
532 }
533 if let Some(abbreviation) = group.abbreviation() {
534 element.sub_text("group-abbreviation", abbreviation);
535 }
536 if let Some(symbol) = group.symbol() {
537 element.sub_text("group-symbol", symbol);
538 }
539 let barline = element.sub("group-barline");
540 if let Some(bar_together) = group.bar_together() {
541 barline.set_text(bar_together.as_str());
542 }
543 element
544}
545
546type Extreme = (*const StreamElement, FloatType);
549
550#[derive(Default)]
554struct ArpeggioNumbers {
555 known: std::cell::RefCell<Vec<(*const Spanner, u32)>>,
556}
557
558impl ArpeggioNumbers {
559 fn number(&self, spanner: &Spanner) -> u32 {
560 let mut known = self.known.borrow_mut();
561 if let Some((_, number)) = known.iter().find(|(held, _)| std::ptr::eq(*held, spanner)) {
562 return *number;
563 }
564 let number = known.len() as u32 % 16 + 1;
565 known.push((spanner, number));
566 number
567 }
568}
569
570#[derive(Clone)]
572struct Numbered<'a> {
573 spanner: &'a Spanner,
574 id_local: u32,
575 elements: Vec<Option<&'a StreamElement>>,
577}
578
579impl Numbered<'_> {
580 fn is(joined: Option<&Option<&StreamElement>>, element: *const StreamElement) -> bool {
581 joined.is_some_and(|joined| joined.is_some_and(|joined| std::ptr::eq(joined, element)))
582 }
583
584 fn holds(&self, element: *const StreamElement) -> bool {
585 self.elements
586 .iter()
587 .any(|joined| Self::is(Some(joined), element))
588 }
589
590 fn is_first(&self, element: *const StreamElement) -> bool {
591 Self::is(self.elements.first(), element)
592 }
593
594 fn is_last(&self, element: *const StreamElement) -> bool {
595 Self::is(self.elements.last(), element)
596 }
597
598 fn note_extremes(&self) -> Option<(Extreme, Extreme)> {
602 let mut notes: Vec<Extreme> = Vec::new();
603 for element in self.elements.iter().flatten() {
604 match element {
605 StreamElement::Note(note) => notes.push((*element, note.pitch().ps())),
606 StreamElement::Chord(chord) => {
607 for note in chord.notes() {
608 notes.push((*element, note.pitch().ps()));
609 }
610 }
611 _ => {}
612 }
613 }
614 let first = *notes.first()?;
615 let (mut lowest, mut highest) = (first, first);
616 for note in notes {
617 if note.1 < lowest.1 {
618 lowest = note;
619 }
620 if note.1 > highest.1 {
621 highest = note;
622 }
623 }
624 Some((lowest, highest))
625 }
626}
627
628fn resolved_modulations(part: &Stream) -> Vec<(*const StreamElement, MetricModulation)> {
634 let mut elements = part.recurse();
635 elements.sort_by(|left, right| left.0.total_cmp(&right.0));
636 let mut tempos: Vec<(FloatType, Option<MetronomeMark>)> = Vec::new();
638 let mut resolved = Vec::new();
639 for (offset, element) in elements {
640 match element {
641 StreamElement::MetronomeMark(mark) => tempos.push((offset, Some(mark.clone()))),
642 StreamElement::TempoText(text) => tempos.push((offset, Some(text.metronome_mark()))),
643 StreamElement::MetricModulation(modulation) => {
644 let previous = tempos
645 .iter()
646 .rev()
647 .find(|(at, _)| *at < offset)
648 .and_then(|(_, mark)| mark.as_ref());
649 let mut modulation = (**modulation).clone();
650 let unnumbered =
651 |side: Option<&MetronomeMark>| side.is_some_and(|mark| mark.number().is_none());
652 if unnumbered(modulation.old_metronome()) {
653 modulation.update_from(previous);
654 }
655 if unnumbered(modulation.new_metronome()) {
656 modulation.update_from(previous);
657 }
658 tempos.push((offset, modulation.new_metronome().cloned()));
659 resolved.push((element as *const StreamElement, modulation));
660 }
661 _ => {}
662 }
663 }
664 resolved
665}
666
667fn id_locals<'a>(score: &'a Stream, parts: &[&'a Stream]) -> Vec<Numbered<'a>> {
673 let resolve = |holder: &'a Stream, spanner: &'a Spanner| {
674 let leaves = holder.leaves();
675 spanner
676 .spanned()
677 .iter()
678 .map(|position| {
679 position
680 .and_then(|position| leaves.get(position))
681 .map(|(_, element)| *element)
682 })
683 .collect::<Vec<_>>()
684 };
685 let mut held: Vec<(&'a Spanner, Vec<Option<&'a StreamElement>>)> = Vec::new();
686 for spanner in score.spanners() {
687 held.push((spanner, resolve(score, spanner)));
688 }
689 for part in parts {
690 if std::ptr::eq(*part, score) {
691 continue;
692 }
693 for spanner in part.spanners() {
694 held.push((spanner, resolve(part, spanner)));
695 }
696 }
697 let mut counts: Vec<(&'static str, u32)> = Vec::new();
698 held.into_iter()
699 .map(|(spanner, elements)| {
700 let class = spanner.kind().numbering_class();
701 let index = match counts.iter_mut().find(|(known, _)| *known == class) {
702 Some((_, count)) => {
703 *count += 1;
704 *count - 1
705 }
706 None => {
707 counts.push((class, 1));
708 0
709 }
710 };
711 Numbered {
712 spanner,
713 id_local: index % 6 + 1,
714 elements,
715 }
716 })
717 .collect()
718}
719
720fn has_part_like_streams(stream: &Stream) -> bool {
722 stream.events().iter().any(|event| {
723 event
724 .element()
725 .as_stream()
726 .is_some_and(|inner| matches!(inner.kind(), StreamKind::Part | StreamKind::PartStaff))
727 })
728}
729
730fn empty_score() -> Stream {
733 let mut measure = Stream::with_kind(StreamKind::Measure);
734 measure.push(Rest::new(Duration::whole()));
735 let mut part = Stream::with_kind(StreamKind::Part);
736 part.push(measure);
737 let mut score = Stream::with_kind(StreamKind::Score);
738 score.push(part);
739 let mut metadata = Metadata::new();
740 metadata.add_text("title", "This Page Intentionally Left Blank");
741 score.set_metadata(Some(metadata));
742 score
743}
744
745fn add_divider_comment(root: &mut Element, comment: &str) {
747 let length = comment.chars().count().min(60);
748 let low = (60 - length) / 2;
749 let high = (60 - length).div_ceil(2);
750 root.push_comment(format!(
751 "{} {comment} {}",
752 "=".repeat(low),
753 "=".repeat(high)
754 ));
755}
756
757fn set_titles(root: &mut Element, metadata: Option<&Metadata>, options: &ExportOptions) {
759 let title = metadata.and_then(|md| md.first_text("title"));
760 if let Some(title) = title {
761 root.sub("work").sub_text("work-title", title);
762 }
763 if let Some(number) = metadata.and_then(|md| md.first_text("movementNumber")) {
764 root.sub_text("movement-number", number);
765 }
766 let movement_title = metadata
767 .and_then(|md| md.first_text("movementName"))
768 .or(title)
769 .or(options.default_title.as_deref());
770 if let Some(movement_title) = movement_title {
771 root.sub_text("movement-title", movement_title);
772 }
773}
774
775fn set_identification(root: &mut Element, metadata: Option<&Metadata>, options: &ExportOptions) {
778 let identification = root.sub("identification");
779 let mut found_one = false;
780 if let Some(md) = metadata {
781 for (_, contributor) in md.contributors() {
782 let creator = identification.sub("creator");
783 if let Some(role) = contributor.role() {
784 creator.set("type", role);
785 }
786 creator.set_text(contributor.text());
787 found_one = true;
788 }
789 }
790 if !found_one && let Some(author) = &options.default_author {
791 let creator = identification.sub("creator");
792 creator.set("type", "composer");
793 creator.set_text(author.as_str());
794 }
795 if let Some(md) = metadata {
796 for copyright in md.get("copyright") {
797 let rights = identification.sub("rights");
798 if let Some(role) = copyright.role() {
799 rights.set("type", role);
800 }
801 rights.set_text(copyright.text());
802 }
803 }
804
805 let encoding = identification.sub("encoding");
806 if let Some(date) = &options.encoding_date {
807 encoding.sub_text("encoding-date", date.as_str());
808 }
809 match metadata {
810 Some(md) => {
811 let mut found_music21 = false;
812 for software in md.get("software") {
813 if software.text().contains("music21 v.") {
814 if found_music21 {
815 continue;
816 }
817 found_music21 = true;
818 }
819 encoding.sub_text("software", software.text());
820 }
821 }
822 None => encoding.sub_text("software", options.software.as_str()),
823 }
824 for element in ["beam", "stem", "accidental"] {
825 let supports = encoding.sub("supports");
826 supports.set("element", element);
827 supports.set("type", "yes");
828 }
829
830 if let Some(md) = metadata {
831 let mut miscellaneous = Element::new("miscellaneous");
832 let (mut skipped_name, mut skipped_number, mut skipped_title) = (false, false, false);
833 for (unique_name, value) in md.all() {
834 if crate::metadata::is_contributor_unique_name(unique_name) {
835 continue;
836 }
837 match unique_name {
838 "software" | "copyright" => continue,
839 "movementName" if !skipped_name => {
840 skipped_name = true;
841 continue;
842 }
843 "movementNumber" if !skipped_number => {
844 skipped_number = true;
845 continue;
846 }
847 "title" if !skipped_title => {
848 skipped_title = true;
849 continue;
850 }
851 _ => {}
852 }
853 let name = namespace_name(unique_name).unwrap_or(unique_name);
854 if name.starts_with("m21FileInfo:") {
855 continue;
856 }
857 let field = miscellaneous.sub("miscellaneous-field");
858 field.set("name", name);
859 field.set_text(value.text());
860 }
861 if miscellaneous.len() > 0 {
862 identification.push(miscellaneous);
863 }
864 }
865}
866
867fn set_defaults(root: &mut Element, score: &Stream) {
871 let first = score
872 .events()
873 .iter()
874 .find_map(|event| match event.element() {
875 StreamElement::Layout(Layout::Score(layout)) => Some(layout),
876 _ => None,
877 });
878 let defaults = root.sub("defaults");
879 let Some(layout) = first else {
880 let scaling = defaults.sub("scaling");
881 scaling.sub_text("millimeters", "7");
882 scaling.sub_text("tenths", "40");
883 return;
884 };
885 if layout.scaling_millimeters.is_some() || layout.scaling_tenths.is_some() {
886 let said = |value: Option<Tenths>| {
887 value.map_or_else(|| "None".to_string(), |value| value.to_string())
888 };
889 let scaling = defaults.sub("scaling");
890 scaling.sub_text("millimeters", said(layout.scaling_millimeters));
891 scaling.sub_text("tenths", said(layout.scaling_tenths));
892 }
893 if let Some(page) = &layout.page_layout {
894 defaults.push(page_layout_element(page));
895 }
896 if let Some(system) = &layout.system_layout {
897 defaults.push(system_layout_element(system));
898 }
899 for staff in &layout.staff_layouts {
900 defaults.push(staff_layout_element(staff));
901 }
902}
903
904pub(crate) fn set_print_style_align(element: &mut Element, style: &TextStyle) -> Result<()> {
907 for (name, value) in [
908 ("default-x", &style.absolute_x),
909 ("default-y", &style.absolute_y),
910 ("relative-x", &style.relative_x),
911 ("relative-y", &style.relative_y),
912 ] {
913 if let Some(value) = value {
914 element.set(name, value.to_string());
915 }
916 }
917 if let Some(font_style) = style.font_style() {
918 element.set("font-style", font_style);
919 }
920 if let Some(size) = &style.font_size {
921 element.set("font-size", size.to_string());
922 }
923 if let Some(weight) = style.font_weight() {
924 element.set("font-weight", weight);
925 }
926 match style.font_style() {
928 Some("bold") => {
929 element.set("font-weight", "bold");
930 element.remove_attribute("font-style");
931 }
932 Some("bolditalic") => {
933 element.set("font-weight", "bold");
934 element.set("font-style", "italic");
935 }
936 _ => {}
937 }
938 if let Some(family) = style
939 .font_family
940 .as_ref()
941 .filter(|family| !family.is_empty())
942 {
943 element.set("font-family", family.join(","));
944 }
945 if let Some(color) = &style.color {
946 element.set("color", normalize_color(color)?);
947 }
948 if let Some(align) = style.align_vertical() {
949 element.set("valign", align);
950 }
951 if let Some(align) = style.align_horizontal() {
952 element.set("halign", align);
953 }
954 Ok(())
955}
956
957fn credit_element(text: &TextBox) -> Result<Element> {
960 let mut credit = Element::new("credit");
961 credit.set("page", text.page.to_string());
962 let mut words = Element::new("credit-words");
963 if text.content.contains('\n') {
964 words.set("xml:space", "preserve");
965 }
966 words.set_text(text.content.clone());
967 set_print_style_align(&mut words, &text.style)?;
968 if let Some(justify) = text.style.justify() {
969 words.set("justify", justify);
970 }
971 credit.push(words);
972 Ok(credit)
973}
974
975fn sub_tenths(element: &mut Element, tag: &'static str, value: Option<Tenths>) {
978 if let Some(value) = value {
979 element.sub_text(tag, value.to_string());
980 }
981}
982
983fn page_layout_element(page: &PageLayout) -> Element {
985 let mut element = Element::new("page-layout");
986 sub_tenths(&mut element, "page-height", page.page_height);
987 sub_tenths(&mut element, "page-width", page.page_width);
988 let mut margins = Element::new("page-margins");
989 sub_tenths(&mut margins, "left-margin", page.left_margin);
990 sub_tenths(&mut margins, "right-margin", page.right_margin);
991 sub_tenths(&mut margins, "top-margin", page.top_margin);
992 sub_tenths(&mut margins, "bottom-margin", page.bottom_margin);
993 if margins.len() > 0 {
994 element.push(margins);
995 }
996 element
997}
998
999fn system_layout_element(system: &SystemLayout) -> Element {
1001 let mut element = Element::new("system-layout");
1002 let mut margins = Element::new("system-margins");
1003 sub_tenths(&mut margins, "top-margin", system.top_margin);
1004 sub_tenths(&mut margins, "bottom-margin", system.bottom_margin);
1005 sub_tenths(&mut margins, "left-margin", system.left_margin);
1006 sub_tenths(&mut margins, "right-margin", system.right_margin);
1007 if margins.len() > 0 {
1008 element.push(margins);
1009 }
1010 sub_tenths(&mut element, "system-distance", system.distance);
1011 sub_tenths(&mut element, "top-system-distance", system.top_distance);
1012 element
1013}
1014
1015fn staff_layout_element(staff: &StaffLayout) -> Element {
1017 let mut element = Element::new("staff-layout");
1018 sub_tenths(&mut element, "staff-distance", staff.distance);
1019 if let Some(number) = staff.staff_number {
1020 element.set("number", number.to_string());
1021 }
1022 element
1023}
1024
1025fn staff_details_element(staff: &StaffLayout) -> Element {
1028 let mut element = Element::new("staff-details");
1029 element.set(
1030 "print-object",
1031 if staff.hidden == Some(true) {
1032 "no"
1033 } else {
1034 "yes"
1035 },
1036 );
1037 if !matches!(staff.staff_type, StaffType::Regular | StaffType::Other) {
1038 element.sub_text("staff-type", staff.staff_type.as_str());
1039 }
1040 if let Some(lines) = staff.staff_lines {
1041 element.sub_text("staff-lines", lines.to_string());
1042 }
1043 element
1044}
1045
1046fn set_yes_no(element: &mut Element, name: &'static str, value: Option<bool>) {
1048 if let Some(value) = value {
1049 element.set(name, if value { "yes" } else { "no" });
1050 }
1051}
1052
1053#[derive(Default)]
1058struct Shared {
1059 midi_channels: Vec<Option<u8>>,
1060 instrument_ids: Vec<String>,
1061 part_ids: Vec<String>,
1062}
1063
1064struct ExportedPart {
1065 part: Element,
1066 score_part: Element,
1067}
1068
1069struct PlacedInstrument {
1071 offset: FloatType,
1072 instrument: Instrument,
1073}
1074
1075struct PartExporter<'a> {
1077 stream: &'a Stream,
1078 instruments: Vec<PlacedInstrument>,
1079}
1080
1081impl<'a> PartExporter<'a> {
1082 fn new(stream: &'a Stream, shared: &mut Shared, role: &StaffRole<'_>) -> Result<Self> {
1084 let mut instruments = instruments_of(stream);
1085 if instruments.is_empty() {
1086 let mut default = Instrument::new();
1087 default.set_part_name(Some(String::new()));
1088 instruments.push(PlacedInstrument {
1089 offset: 0.0,
1090 instrument: default,
1091 });
1092 }
1093
1094 let first = &mut instruments[0].instrument;
1097 let taken = |id: &str| shared.part_ids.iter().any(|known| known == id);
1098 if first.part_id().is_none_or(taken) {
1099 let fresh = stream
1100 .id()
1101 .filter(|id| !taken(id))
1102 .map(str::to_string)
1103 .unwrap_or_else(|| {
1104 (1..)
1105 .map(|number| format!("P{number}"))
1106 .find(|candidate| !taken(candidate))
1107 .expect("there is always an unused number")
1108 });
1109 first.set_part_id(Some(fresh));
1110 }
1111 let part_id = first.part_id().unwrap_or_default().to_string();
1112
1113 if role.later {
1117 return Ok(Self {
1118 stream,
1119 instruments,
1120 });
1121 }
1122 for follower in &role.followers {
1123 let mut theirs = instruments_of(follower);
1124 if theirs.is_empty() {
1125 let mut default = Instrument::new();
1126 default.set_part_name(Some(String::new()));
1127 theirs.push(PlacedInstrument {
1128 offset: 0.0,
1129 instrument: default,
1130 });
1131 }
1132 instruments.extend(theirs);
1133 instruments = deduplicate(instruments);
1134 }
1135
1136 let mut seen: Vec<String> = Vec::new();
1137 for (index, placed) in instruments.iter_mut().enumerate() {
1138 let instrument = &mut placed.instrument;
1139 if seen.iter().any(|kind| kind == instrument.kind()) {
1140 continue;
1141 }
1142 seen.push(instrument.kind().to_string());
1143 if instrument
1144 .midi_channel()
1145 .is_none_or(|channel| shared.midi_channels.contains(&Some(channel)))
1146 {
1147 let used: Vec<u8> = shared.midi_channels.iter().flatten().copied().collect();
1148 let _ = instrument.auto_assign_midi_channel(&used, 16);
1150 }
1151 shared.midi_channels.push(instrument.midi_channel());
1152 let duplicate = instrument
1153 .instrument_id()
1154 .is_none_or(|id| shared.instrument_ids.iter().any(|known| known == id));
1155 if duplicate {
1156 let fresh = (1..)
1157 .map(|number| format!("{part_id}-I{number}"))
1158 .find(|candidate| !shared.instrument_ids.contains(candidate))
1159 .expect("there is always an unused number");
1160 instrument.set_instrument_id(Some(fresh));
1161 }
1162 shared
1163 .instrument_ids
1164 .push(instrument.instrument_id().unwrap_or_default().to_string());
1165 if index == 0 {
1166 shared.part_ids.push(part_id.clone());
1167 }
1168 }
1169 Ok(Self {
1170 stream,
1171 instruments,
1172 })
1173 }
1174
1175 fn part_id(&self) -> &str {
1176 self.instruments[0].instrument.part_id().unwrap_or_default()
1177 }
1178
1179 fn parse(
1181 self,
1182 spanners: Vec<Numbered<'a>>,
1183 previous: Option<&'a Stream>,
1184 voice_bases: &'a std::collections::HashMap<usize, u32>,
1185 ) -> Result<ExportedPart> {
1186 let measures: Vec<(FloatType, &Stream)> = self
1187 .stream
1188 .events()
1189 .iter()
1190 .filter_map(|event| {
1191 event
1192 .element()
1193 .as_stream()
1194 .filter(|stream| stream.kind() == StreamKind::Measure)
1195 .map(|stream| (event.offset(), stream))
1196 })
1197 .collect();
1198 if measures.is_empty() {
1199 return Err(export_error(
1200 "Cannot export with makeNotation=False if there are no measures",
1201 ));
1202 }
1203
1204 let mut part = Element::new("part");
1205 part.set("id", self.part_id());
1206 let mut context = PartContext {
1207 instruments: &self.instruments,
1208 own_instruments: self
1209 .stream
1210 .recurse()
1211 .into_iter()
1212 .filter_map(|(offset, element)| match element {
1213 StreamElement::Instrument(instrument) => Some((offset, &**instrument)),
1214 _ => None,
1215 })
1216 .collect(),
1217 own_clefs: self
1218 .stream
1219 .recurse()
1220 .into_iter()
1221 .filter_map(|(offset, element)| match element {
1222 StreamElement::Clef(clef)
1223 if clef.places_pitches() && clef.lowest_line().is_some() =>
1224 {
1225 Some((offset, clef))
1226 }
1227 _ => None,
1228 })
1229 .collect(),
1230 meter: None,
1231 last_divisions: None,
1232 spanners,
1233 clef: None,
1234 previous,
1235 voice_bases,
1236 modulations: resolved_modulations(self.stream),
1237 };
1238 for (offset, measure) in measures {
1239 add_divider_comment(&mut part, &format!("Measure {}", measure.number()));
1240 part.push(MeasureExporter::new(measure, offset, &mut context).parse()?);
1241 }
1242 Ok(ExportedPart {
1243 score_part: self.score_part(),
1244 part,
1245 })
1246 }
1247
1248 fn score_part(&self) -> Element {
1250 let mut score_part = Element::new("score-part");
1251 score_part.set("id", self.part_id());
1252 let name = score_part.sub("part-name");
1253 name.set_text(part_name(self.stream).unwrap_or_default());
1254 if self.stream.name_hidden() {
1255 name.set("print-object", "no");
1256 }
1257 if let Some(abbreviation) = part_abbreviation(self.stream) {
1258 let element = score_part.sub("part-abbreviation");
1259 element.set_text(abbreviation);
1260 if self.stream.abbreviation_hidden() {
1261 element.set("print-object", "no");
1262 }
1263 }
1264 let mut seen: Vec<&str> = Vec::new();
1265 for placed in &self.instruments {
1266 let instrument = &placed.instrument;
1267 if seen.contains(&instrument.kind()) {
1268 continue;
1269 }
1270 if instrument.name().is_some()
1271 || instrument.abbreviation().is_some()
1272 || instrument.midi_program().is_some()
1273 {
1274 score_part.push(score_instrument(instrument));
1275 seen.push(instrument.kind());
1276 }
1277 }
1278 let mut seen: Vec<&str> = Vec::new();
1279 for placed in &self.instruments {
1280 let instrument = &placed.instrument;
1281 if seen.contains(&instrument.kind()) {
1282 continue;
1283 }
1284 if instrument.midi_program().is_some() || instrument.is_a("UnpitchedPercussion") {
1285 score_part.push(midi_instrument(instrument));
1286 seen.push(instrument.kind());
1287 }
1288 }
1289 score_part
1290 }
1291}
1292
1293fn instruments_of(stream: &Stream) -> Vec<PlacedInstrument> {
1295 stream
1296 .recurse()
1297 .into_iter()
1298 .filter_map(|(offset, element)| match element {
1299 StreamElement::Instrument(instrument) => Some(PlacedInstrument {
1300 offset,
1301 instrument: (**instrument).clone(),
1302 }),
1303 _ => None,
1304 })
1305 .collect()
1306}
1307
1308fn deduplicate(mut instruments: Vec<PlacedInstrument>) -> Vec<PlacedInstrument> {
1313 instruments.sort_by(|left, right| left.offset.total_cmp(&right.offset));
1314 let mut out: Vec<PlacedInstrument> = Vec::new();
1315 let mut start = 0;
1316 while start < instruments.len() {
1317 let offset = instruments[start].offset;
1318 let end = instruments[start..]
1319 .iter()
1320 .position(|placed| placed.offset != offset)
1321 .map_or(instruments.len(), |length| start + length);
1322 let group = &instruments[start..end];
1323 let agreed = |read: fn(&Instrument) -> Option<&str>| -> Option<Option<String>> {
1324 let mut said: Option<&str> = None;
1325 for placed in group {
1326 match (said, read(&placed.instrument)) {
1327 (_, None) => {}
1328 (None, Some(name)) => said = Some(name),
1329 (Some(known), Some(name)) if known == name => {}
1330 _ => return None,
1331 }
1332 }
1333 Some(said.map(str::to_string))
1334 };
1335 match (agreed(Instrument::part_name), agreed(Instrument::name)) {
1336 (Some(part_name), Some(name)) if group.len() > 1 => {
1337 let has_specific = group
1338 .iter()
1339 .any(|placed| placed.instrument.kind() != "Instrument");
1340 let mut kept: Vec<PlacedInstrument> = Vec::new();
1341 for placed in group {
1342 let kind = placed.instrument.kind();
1343 if (has_specific && kind == "Instrument")
1344 || kept.iter().any(|known| known.instrument.kind() == kind)
1345 {
1346 continue;
1347 }
1348 let mut instrument = placed.instrument.clone();
1349 instrument.set_part_name(part_name.clone());
1350 instrument.set_name(name.clone());
1351 kept.push(PlacedInstrument { offset, instrument });
1352 }
1353 out.extend(kept);
1354 }
1355 _ => out.extend(group.iter().map(|placed| PlacedInstrument {
1356 offset,
1357 instrument: placed.instrument.clone(),
1358 })),
1359 }
1360 start = end;
1361 }
1362 out
1363}
1364
1365fn part_name(part: &Stream) -> Option<&str> {
1368 part.name().or_else(|| {
1369 held_instruments(part)
1370 .into_iter()
1371 .find_map(|instrument| instrument.part_name().or(instrument.name()))
1372 })
1373}
1374
1375fn held_instruments(part: &Stream) -> Vec<&Instrument> {
1377 part.recurse()
1378 .into_iter()
1379 .filter_map(|(_, element)| match element {
1380 StreamElement::Instrument(instrument) => Some(&**instrument),
1381 _ => None,
1382 })
1383 .collect()
1384}
1385
1386fn part_abbreviation(part: &Stream) -> Option<&str> {
1388 part.abbreviation().or_else(|| {
1389 held_instruments(part)
1390 .into_iter()
1391 .find_map(|instrument| instrument.part_abbreviation().or(instrument.abbreviation()))
1392 })
1393}
1394
1395fn score_instrument(instrument: &Instrument) -> Element {
1397 let mut element = Element::new("score-instrument");
1398 element.set("id", instrument.instrument_id().unwrap_or_default());
1399 element.sub_text(
1400 "instrument-name",
1401 instrument.name().unwrap_or("None").to_string(),
1402 );
1403 if let Some(abbreviation) = instrument.abbreviation() {
1404 element.sub_text("instrument-abbreviation", abbreviation);
1405 }
1406 element
1407}
1408
1409fn midi_instrument(instrument: &Instrument) -> Element {
1411 let mut element = Element::new("midi-instrument");
1412 element.set("id", instrument.instrument_id().unwrap_or_default());
1413 let channel = instrument.midi_channel().unwrap_or(0);
1414 element.sub_text("midi-channel", (u32::from(channel) + 1).to_string());
1415 if let Some(program) = instrument.midi_program() {
1416 element.sub_text("midi-program", (u32::from(program) + 1).to_string());
1417 }
1418 if instrument.is_a("UnpitchedPercussion")
1419 && let Some(pitch) = instrument.percussion_pitch()
1420 {
1421 element.sub_text("midi-unpitched", (u32::from(pitch) + 1).to_string());
1422 }
1423 element
1424}
1425
1426struct PartContext<'a> {
1428 instruments: &'a [PlacedInstrument],
1429 own_instruments: Vec<(FloatType, &'a Instrument)>,
1432 own_clefs: Vec<(FloatType, &'a Clef)>,
1434 meter: Option<TimeSignature>,
1436 last_divisions: Option<FloatType>,
1437 spanners: Vec<Numbered<'a>>,
1440 modulations: Vec<(*const StreamElement, MetricModulation)>,
1443 clef: Option<Clef>,
1445 previous: Option<&'a Stream>,
1448 voice_bases: &'a std::collections::HashMap<usize, u32>,
1451}
1452
1453struct MeasureExporter<'a, 'b> {
1457 stream: &'a Stream,
1458 start: FloatType,
1460 context: &'a mut PartContext<'b>,
1461 root: Element,
1462 offset_in_measure: FloatType,
1463 current_voice: Option<String>,
1464 next_free_voice: u32,
1465 arpeggios: ArpeggioNumbers,
1466 element_offset: FloatType,
1468}
1469
1470impl<'a, 'b> MeasureExporter<'a, 'b> {
1471 fn new(stream: &'a Stream, start: FloatType, context: &'a mut PartContext<'b>) -> Self {
1472 let next_free_voice = context
1473 .voice_bases
1474 .get(&(stream as *const Stream as usize))
1475 .map_or(1, |base| base + 1);
1476 Self {
1477 stream,
1478 start,
1479 context,
1480 root: Element::new("measure"),
1481 offset_in_measure: 0.0,
1482 current_voice: None,
1483 next_free_voice,
1484 arpeggios: ArpeggioNumbers::default(),
1485 element_offset: 0.0,
1486 }
1487 }
1488
1489 fn matches_previous_staff(
1492 &self,
1493 element: &StreamElement,
1494 pick: impl Fn(&StreamElement) -> bool,
1495 same: impl Fn(&StreamElement, &StreamElement) -> bool,
1496 ) -> bool {
1497 let Some(previous) = self.context.previous else {
1498 return false;
1499 };
1500 let Some(measure) = previous
1501 .measures()
1502 .into_iter()
1503 .find(|measure| measure.number() == self.stream.number())
1504 else {
1505 return false;
1506 };
1507 measure
1508 .events()
1509 .iter()
1510 .filter(|event| event.offset() == 0.0)
1511 .map(|event| event.element())
1512 .find(|candidate| pick(candidate))
1513 .is_some_and(|theirs| same(element, theirs))
1514 }
1515
1516 fn parse(mut self) -> Result<Element> {
1518 let transposition = self.transposition();
1519 self.root.set("number", self.stream.number_with_suffix());
1521 self.root.set(
1522 "implicit",
1523 if self.stream.number_hidden() {
1524 "yes"
1525 } else {
1526 "no"
1527 },
1528 );
1529 self.set_print();
1530 self.set_attributes_for_start_of_measure(transposition.as_ref())?;
1531 let ending = self.stream.ending();
1534 let opens = ending.filter(|ending| ending.starts());
1535 if self.stream.left_barline().is_some() || opens.is_some() {
1536 let barline = barline_element(
1537 self.stream.left_barline(),
1538 "left",
1539 opens.map(|ending| (ending, "start")),
1540 );
1541 self.root.push(barline);
1542 }
1543 self.main_elements_parse()?;
1544 let closes = ending.filter(|ending| ending.stops());
1545 if self.stream.right_barline().is_some() || closes.is_some() {
1546 let barline = barline_element(
1547 self.stream.right_barline(),
1548 "right",
1549 closes.map(|ending| (ending, "stop")),
1550 );
1551 self.root.push(barline);
1552 }
1553 if let Some(meter) = last_of(self.stream, |element| match element {
1554 StreamElement::TimeSignature(meter) => Some(meter.clone()),
1555 _ => None,
1556 }) {
1557 self.context.meter = Some(meter);
1558 }
1559 Ok(self.root)
1560 }
1561
1562 fn transposition(&self) -> Option<Interval> {
1565 let end = self.start + self.stream.end_offset();
1566 self.context
1567 .instruments
1568 .iter()
1569 .find(|placed| {
1570 placed.offset >= self.start && (placed.offset < end || placed.offset == self.start)
1571 })
1572 .and_then(|placed| placed.instrument.transposition().cloned())
1573 }
1574
1575 fn set_print(&mut self) {
1578 let layouts: Vec<&Layout> = self
1579 .stream
1580 .events()
1581 .iter()
1582 .filter_map(|event| match event.element() {
1583 StreamElement::Layout(layout) => Some(layout),
1584 _ => None,
1585 })
1586 .collect();
1587 let mut print: Option<Element> = None;
1588 if let Some(page) = layouts.iter().find_map(|layout| match layout {
1589 Layout::Page(page) => Some(page),
1590 _ => None,
1591 }) {
1592 let mut element = Element::new("print");
1593 set_yes_no(&mut element, "new-page", page.is_new);
1594 if let Some(number) = page.page_number {
1595 element.set("page-number", number.to_string());
1596 }
1597 let laid = page_layout_element(page);
1598 if laid.len() > 0 {
1599 element.push(laid);
1600 }
1601 print = Some(element);
1602 }
1603 if let Some(system) = layouts.iter().find_map(|layout| match layout {
1604 Layout::System(system) => Some(system),
1605 _ => None,
1606 }) {
1607 let element = print.get_or_insert_with(|| Element::new("print"));
1608 set_yes_no(element, "new-system", system.is_new);
1609 let laid = system_layout_element(system);
1610 if laid.len() > 0 {
1611 element.push(laid);
1612 }
1613 }
1614 if let Some(staff) = layouts.iter().find_map(|layout| match layout {
1615 Layout::Staff(staff) => Some(staff),
1616 _ => None,
1617 }) {
1618 print
1619 .get_or_insert_with(|| Element::new("print"))
1620 .push(staff_layout_element(staff));
1621 }
1622 if !layouts.is_empty()
1625 && let Some(numbering) = self.stream.measure_numbering()
1626 {
1627 print
1628 .get_or_insert_with(|| Element::new("print"))
1629 .sub_text("measure-numbering", numbering.to_string());
1630 }
1631 if let Some(print) = print {
1632 self.root.push(print);
1633 }
1634 }
1635
1636 fn set_attributes_for_start_of_measure(
1638 &mut self,
1639 transposition: Option<&Interval>,
1640 ) -> Result<()> {
1641 let mut attributes = Element::new("attributes");
1642 if self.context.last_divisions != Some(DIVISIONS_PER_QUARTER) {
1643 attributes.sub_text("divisions", format_int(DIVISIONS_PER_QUARTER));
1644 self.context.last_divisions = Some(DIVISIONS_PER_QUARTER);
1645 }
1646 for event in self
1647 .stream
1648 .events()
1649 .iter()
1650 .filter(|event| event.offset() == 0.0)
1651 {
1652 if let Some(key) = key_element(event.element()) {
1653 let is_key = |element: &StreamElement| {
1654 matches!(
1655 element,
1656 StreamElement::Key(_) | StreamElement::KeySignature(_)
1657 )
1658 };
1659 if !self.matches_previous_staff(event.element(), is_key, same_key) {
1660 attributes.push(key?);
1661 }
1662 break;
1663 }
1664 }
1665 for event in self
1666 .stream
1667 .events()
1668 .iter()
1669 .filter(|event| event.offset() == 0.0)
1670 {
1671 if let StreamElement::TimeSignature(meter) = event.element() {
1672 let is_meter =
1673 |element: &StreamElement| matches!(element, StreamElement::TimeSignature(_));
1674 if !self.matches_previous_staff(event.element(), is_meter, same_meter) {
1675 attributes.push(time_signature_element(meter)?);
1676 }
1677 self.context.meter = Some(meter.clone());
1678 break;
1679 }
1680 }
1681 for event in self
1682 .stream
1683 .events()
1684 .iter()
1685 .filter(|event| event.offset() == 0.0)
1686 {
1687 if let StreamElement::Clef(clef) = event.element() {
1688 attributes.push(clef_element(clef)?);
1689 break;
1690 }
1691 }
1692 if let Some(staff) = self
1693 .stream
1694 .events()
1695 .iter()
1696 .find_map(|event| match event.element() {
1697 StreamElement::Layout(Layout::Staff(staff)) => Some(staff),
1698 _ => None,
1699 })
1700 {
1701 attributes.push(staff_details_element(staff));
1702 }
1703 if let Some(interval) = transposition {
1704 attributes.push(transpose_element(interval));
1705 }
1706 if let Some(style) = self.measure_style() {
1707 attributes.push(style);
1708 }
1709 if attributes.len() > 0 || attributes.has_attributes() {
1710 self.root.push(attributes);
1711 }
1712 Ok(())
1713 }
1714
1715 fn measure_style(&self) -> Option<Element> {
1719 let rest = self
1720 .stream
1721 .events()
1722 .iter()
1723 .find_map(|event| match event.element() {
1724 element @ StreamElement::Rest(_) => Some(element as *const StreamElement),
1725 _ => None,
1726 })?;
1727 let numbered = self.context.spanners.iter().find(|numbered| {
1728 numbered.spanner.kind() == SpannerKind::MultiMeasureRest && numbered.holds(rest)
1729 })?;
1730 if !numbered.is_first(rest) {
1731 return None;
1732 }
1733 let use_symbols = numbered
1734 .spanner
1735 .multi_rest()
1736 .is_none_or(|said| said.use_symbols);
1737 let mut multiple = Element::new("multiple-rest");
1738 multiple.set("use-symbols", if use_symbols { "yes" } else { "no" });
1739 multiple.set_text(numbered.spanner.num_rests().to_string());
1740 let mut style = Element::new("measure-style");
1741 style.push(multiple);
1742 Some(style)
1743 }
1744
1745 fn main_elements_parse(&mut self) -> Result<()> {
1747 let voices: Vec<&Stream> = self.stream.voices();
1748 if voices.is_empty() {
1749 return self.parse_flat_elements(self.stream, false, false);
1750 }
1751 self.parse_flat_elements(self.stream, false, true)?;
1752 let count = voices.len();
1753 for (index, voice) in voices.into_iter().enumerate() {
1754 self.parse_flat_elements(voice, true, index + 1 != count)?;
1755 }
1756 Ok(())
1757 }
1758
1759 fn parse_flat_elements(
1762 &mut self,
1763 stream: &Stream,
1764 is_voice: bool,
1765 backup_afterwards: bool,
1766 ) -> Result<()> {
1767 self.offset_in_measure = 0.0;
1768 self.current_voice = if is_voice {
1769 Some(match stream.id() {
1770 Some(id) => id.to_string(),
1771 None => {
1772 let number = self.next_free_voice;
1773 self.next_free_voice += 1;
1774 number.to_string()
1775 }
1776 })
1777 } else {
1778 None
1779 };
1780
1781 let events: Vec<(FloatType, &StreamElement)> = stream
1782 .events()
1783 .iter()
1784 .filter(|event| !matches!(event.element(), StreamElement::Stream(inner) if inner.kind() == StreamKind::Voice))
1785 .map(|event| (event.offset(), event.element()))
1786 .collect();
1787 let mut index = 0;
1788 while index < events.len() {
1789 let group_offset = events[index].0;
1790 let mut end = index;
1791 while end < events.len() && events[end].0 == group_offset {
1792 end += 1;
1793 }
1794 let group = &events[index..end];
1795 let forward = group_offset - self.offset_in_measure;
1796 if forward > 0.0
1797 && group.iter().any(|(_, element)| {
1798 is_general_note(element) || matches!(element, StreamElement::Clef(_))
1799 })
1800 {
1801 self.move_forward(forward);
1802 }
1803 let mut notes_for_later = Vec::new();
1804 for &(offset, element) in group {
1805 if is_written_as_note(element) {
1806 notes_for_later.push((offset, element));
1807 } else {
1808 self.parse_one_element(offset, element)?;
1809 }
1810 }
1811 for (offset, element) in notes_for_later {
1812 if let StreamElement::Rest(rest) = element
1815 && rest.hidden()
1816 && rest.quarter_length() != 0.0
1817 && rest.duration().components().is_empty()
1818 {
1819 continue;
1820 }
1821 self.parse_one_element(offset, element)?;
1822 }
1823 index = end;
1824 }
1825
1826 if backup_afterwards && self.offset_in_measure != 0.0 {
1827 self.move_backward(self.offset_in_measure);
1828 }
1829 self.current_voice = None;
1830 Ok(())
1831 }
1832
1833 fn move_forward(&mut self, by: FloatType) {
1835 let amount = divisions(by);
1836 if amount != 0 {
1837 self.root
1838 .sub("forward")
1839 .sub_text("duration", amount.to_string());
1840 self.offset_in_measure += by;
1841 }
1842 }
1843
1844 fn move_backward(&mut self, by: FloatType) {
1846 let amount = divisions(by);
1847 if amount != 0 {
1848 self.root
1849 .sub("backup")
1850 .sub_text("duration", amount.to_string());
1851 self.offset_in_measure -= by;
1852 }
1853 }
1854
1855 fn parse_one_element(&mut self, offset: FloatType, element: &StreamElement) -> Result<()> {
1857 let position = element as *const StreamElement;
1858 let (before, after) =
1861 related_spanners(position, &self.context.spanners, self.offset_in_measure);
1862 if is_written_as_note(element) {
1863 self.offset_in_measure += element.quarter_length();
1864 }
1865 for direction in before {
1866 self.root.push(direction);
1867 }
1868 self.write_element(offset, element, position)?;
1869 for direction in after {
1870 self.root.push(direction);
1871 }
1872 Ok(())
1873 }
1874
1875 fn write_element(
1878 &mut self,
1879 offset: FloatType,
1880 element: &StreamElement,
1881 position: *const StreamElement,
1882 ) -> Result<()> {
1883 self.element_offset = offset;
1884 match element {
1885 StreamElement::Note(note) => {
1886 let mxnote = self.note_element(note, 0, None, Some(position))?;
1887 self.root.push(mxnote);
1888 }
1889 StreamElement::Chord(chord) => {
1891 self.chord_elements(&written_order(chord), Some(position))?
1892 }
1893 StreamElement::Unpitched(stroke) => {
1894 let mxnote =
1895 self.written_note_element(stroke.written(), 0, None, Some(position), true)?;
1896 self.root.push(mxnote);
1897 }
1898 StreamElement::PercussionChord(chord) => {
1899 let written = chord.written();
1902 for (index, note) in written.notes().iter().enumerate() {
1903 let element = self.written_note_element(
1904 note,
1905 index,
1906 Some(written),
1907 Some(position),
1908 chord.is_unpitched(index),
1909 )?;
1910 self.root.push(element);
1911 }
1912 }
1913 StreamElement::Rest(rest) => {
1914 let mxnote = self.rest_element(rest, offset, Some(position))?;
1915 self.root.push(mxnote);
1916 }
1917 StreamElement::Dynamic(dynamic) => self.dynamic_element(dynamic, offset),
1918 StreamElement::MetronomeMark(mark) => self.tempo_elements(mark, offset)?,
1919 StreamElement::MetricModulation(modulation) => {
1920 let resolved = self
1921 .context
1922 .modulations
1923 .iter()
1924 .find(|(held, _)| std::ptr::eq(*held, position))
1925 .map_or(&**modulation, |(_, resolved)| resolved);
1926 let direction = self.metric_modulation_element(resolved, offset)?;
1927 self.root.push(direction);
1928 }
1929 StreamElement::TempoText(text) => {
1930 let direction = self.tempo_words(text.text(), text.placement(), offset);
1931 self.root.push(direction);
1932 }
1933 StreamElement::TextExpression(text) => {
1934 let words = Element::with_text("words", text.content());
1936 let direction = self.direction(words, text.placement(), offset, false);
1937 self.root.push(direction);
1938 }
1939 StreamElement::RepeatExpression(mark) => {
1940 let direction = self.repeat_expression_element(mark, offset);
1941 self.root.push(direction);
1942 }
1943 StreamElement::KeySignature(_) | StreamElement::Key(_) => {
1944 if let Some(key) = key_element(element) {
1945 let key = key?;
1946 self.wrap_in_attributes(key);
1947 }
1948 }
1949 StreamElement::TimeSignature(meter) => {
1950 let meter = time_signature_element(meter)?;
1951 self.wrap_in_attributes(meter);
1952 }
1953 StreamElement::Clef(clef) => {
1954 self.context.clef = Some(clef.clone());
1955 let clef = clef_element(clef)?;
1956 self.wrap_in_attributes(clef);
1957 }
1958 StreamElement::Instrument(_) => {}
1961 StreamElement::ChordSymbol(symbol) => {
1962 let harmony = self.harmony_element(symbol, offset)?;
1963 self.root.push(harmony);
1964 }
1965 StreamElement::Stream(inner) => {
1966 return Err(export_error(format!(
1967 "a {} inside a measure cannot be written",
1968 inner.kind()
1969 )));
1970 }
1971 StreamElement::PedalObject(object) => self.pedal_object_elements(object, position),
1972 StreamElement::RehearsalMark(mark) => {
1973 let mut rehearsal = Element::with_text("rehearsal", mark.content());
1976 if let Some(enclosure) = mark.enclosure() {
1977 rehearsal.set("enclosure", enclosure);
1978 }
1979 rehearsal.set("halign", "center");
1980 rehearsal.set("valign", "middle");
1981 let direction = self.direction(rehearsal, mark.placement(), offset, true);
1982 self.root.push(direction);
1983 }
1984 StreamElement::Barline(_)
1988 | StreamElement::Break(_)
1989 | StreamElement::Layout(_)
1990 | StreamElement::TextBox(_)
1991 | StreamElement::Variant(_)
1992 | StreamElement::GlobalComment(_)
1993 | StreamElement::MiscTandem(_)
1994 | StreamElement::SpineComment(_)
1995 | StreamElement::BrailleSegmentDivision(_) => {}
1996 }
1997 Ok(())
1998 }
1999
2000 fn pedal_object_elements(&mut self, object: &PedalObject, position: *const StreamElement) {
2004 let Some(pedal) = self
2005 .context
2006 .spanners
2007 .iter()
2008 .find(|numbered| {
2009 numbered.spanner.kind() == SpannerKind::PedalMark && numbered.holds(position)
2010 })
2011 .map(|numbered| numbered.spanner.pedal().unwrap_or_default())
2012 else {
2013 return;
2014 };
2015 let lined = pedal.form.is_some_and(PedalForm::has_line);
2016 let down = match pedal.pedal_type {
2019 Some(PedalType::Sostenuto) => "sostenuto",
2020 Some(PedalType::Sustain) => "start",
2021 _ => "sustain",
2022 };
2023 let types: &[&str] = match object.kind() {
2024 PedalObjectKind::Bounce => match pedal.form {
2025 Some(PedalForm::Line | PedalForm::SymbolLine) => &["change"],
2026 Some(PedalForm::SymbolAlt) => &[down],
2027 Some(PedalForm::Symbol) => &["stop", down],
2028 None => return,
2029 },
2030 PedalObjectKind::GapStart => &["discontinue"],
2031 PedalObjectKind::GapEnd => &["resume"],
2032 };
2033 for kind in types {
2034 let mut mark = Element::new("pedal");
2035 mark.set("type", *kind);
2036 mark.set(if lined { "line" } else { "sign" }, "yes");
2037 let direction = self.direction(mark, object.placement(), self.element_offset, true);
2038 self.root.push(direction);
2039 }
2040 }
2041
2042 fn wrap_in_attributes(&mut self, element: Element) {
2046 if self.offset_in_measure == 0.0 {
2047 return;
2048 }
2049 let mut attributes = Element::new("attributes");
2050 attributes.push(element);
2051 self.root.push(attributes);
2052 }
2053
2054 fn note_element(
2058 &self,
2059 note: &Note,
2060 index: usize,
2061 chord: Option<&Chord>,
2062 position: Option<*const StreamElement>,
2063 ) -> Result<Element> {
2064 self.written_note_element(note, index, chord, position, false)
2065 }
2066
2067 fn written_note_element(
2070 &self,
2071 note: &Note,
2072 index: usize,
2073 chord: Option<&Chord>,
2074 position: Option<*const StreamElement>,
2075 unpitched: bool,
2076 ) -> Result<Element> {
2077 let in_chord = index != 0;
2078 let mut mxnote = Element::new("note");
2079 if chord.is_some()
2080 && let Some(color) = note.color()
2081 {
2082 mxnote.set("color", normalize_color(color)?);
2083 }
2084 if let Some(color) = chord.map_or(note.color(), Chord::color) {
2085 mxnote.set("color", normalize_color(color)?);
2086 }
2087 let volume = match chord {
2088 Some(chord) => chord.has_volume_information().then(|| chord.volume()),
2089 None => note.has_volume_information().then(|| note.volume()),
2090 };
2091 if let Some(scalar) = volume.and_then(|volume| volume.velocity_scalar()) {
2092 let velocity = scalar * 100.0 * (127.0 / 90.0);
2093 mxnote.set("dynamics", format!("{velocity:.2}"));
2094 }
2095 if let Some(id) = note.id()
2097 && id
2098 .chars()
2099 .next()
2100 .is_some_and(|first| first.is_alphabetic() || first == '_')
2101 && id
2102 .chars()
2103 .all(|c| c.is_alphanumeric() || matches!(c, '_' | '-' | '.'))
2104 {
2105 mxnote.set("id", id);
2106 }
2107 let duration = chord
2108 .map_or_else(|| note.duration(), Chord::duration)
2109 .cloned()
2110 .unwrap_or_default();
2111 if note.hidden() {
2112 mxnote.set("print-object", "no");
2113 mxnote.set("print-spacing", "yes");
2114 }
2115 if chord
2116 .map_or(note.articulations(), Chord::articulations)
2117 .iter()
2118 .any(|articulation| articulation.is_a("Pizzicato"))
2119 {
2120 mxnote.set("pizzicato", "yes");
2121 }
2122
2123 if let Some(grace) = duration.grace() {
2124 let element = mxnote.sub("grace");
2125 element.set("slash", yes_no(grace.slash()));
2126 if let Some(share) = grace.steal_time_previous() {
2127 element.set("steal-time-previous", fraction_to_percent(share));
2128 }
2129 if let Some(share) = grace.steal_time_following() {
2130 element.set("steal-time-following", fraction_to_percent(share));
2131 }
2132 }
2133 if in_chord {
2134 mxnote.sub("chord");
2135 }
2136 if unpitched {
2137 let displayed = mxnote.sub("unpitched");
2138 displayed.sub_text("display-step", note.pitch().step().as_char().to_string());
2139 displayed.sub_text(
2140 "display-octave",
2141 musicxml_octave(note.pitch().implicit_octave()).to_string(),
2142 );
2143 } else {
2144 mxnote.push(pitch_element(note.pitch()));
2145 }
2146 if !duration.is_grace() {
2147 mxnote.sub_text("duration", divisions(duration.quarter_length()).to_string());
2148 }
2149 if let Some(tie) = note.tie() {
2150 for element in tie_elements(tie) {
2151 mxnote.push(element);
2152 }
2153 }
2154 let stored = chord.map_or(note.stored_instrument(), Chord::stored_instrument);
2155 self.set_note_instrument(&mut mxnote, stored)?;
2156 if let Some(voice) = &self.current_voice {
2157 mxnote.sub_text("voice", voice.as_str());
2158 }
2159 let (written_type, dots) = match written_type(&duration) {
2161 Err(_) if duration.is_grace() && duration.components().is_empty() => ("eighth", 0),
2162 other => other?,
2163 };
2164 let written = mxnote.sub("type");
2165 written.set_text(written_type);
2166 if let Some(size) = note.size() {
2167 written.set("size", size.as_str());
2168 }
2169 for _ in 0..dots {
2170 mxnote.sub("dot");
2171 }
2172 if !unpitched
2173 && let Some(accidental) = note.pitch().written_accidental()
2174 && accidental.display_status() != Some(false)
2175 {
2176 mxnote.push(accidental_element(accidental, "accidental")?);
2177 }
2178 if let Some(modification) = time_modification(&duration)? {
2179 mxnote.push(modification);
2180 }
2181
2182 let chord_stem = chord.map_or(note.stem_direction(), Chord::stem_direction);
2183 let stem = if !in_chord && chord_stem != StemDirection::Unspecified {
2184 Some(chord_stem)
2185 } else if chord.is_some() && note.stem_direction() != StemDirection::Unspecified {
2186 Some(note.stem_direction())
2187 } else {
2188 None
2189 };
2190 if let Some(stem) = stem {
2191 let text = match stem {
2192 StemDirection::NoStem => "none",
2193 other => other.as_str(),
2194 };
2195 mxnote.sub_text("stem", text);
2196 }
2197
2198 if let Some(notehead) = notehead_for(note, chord)? {
2199 mxnote.push(notehead);
2200 }
2201 if !in_chord {
2202 let beams = chord.map_or(note.beams(), Chord::beams);
2203 for element in beam_elements(beams)? {
2204 mxnote.push(element);
2205 }
2206 }
2207
2208 let spanners: &[Numbered<'_>] = match position {
2209 Some(_) => &self.context.spanners,
2210 None => &[],
2211 };
2212 let pitch = note.pitch().ps();
2213 let mut notations = note_notations(
2214 note,
2215 index,
2216 chord,
2217 position,
2218 spanners,
2219 &self.arpeggios,
2220 pitch,
2221 )?;
2222 if !in_chord {
2223 notations.extend(tuplet_notations(&duration)?);
2224 }
2225 if !notations.is_empty() {
2226 let mxnotations = mxnote.sub("notations");
2227 for element in notations {
2228 mxnotations.push(element);
2229 }
2230 }
2231 if !in_chord {
2232 let lyrics = chord.map_or(note.lyrics(), Chord::lyrics);
2233 for lyric in lyrics {
2234 if lyric.explicit_text().is_none() {
2235 continue;
2236 }
2237 mxnote.push(lyric_element(lyric)?);
2238 }
2239 }
2240 Ok(mxnote)
2241 }
2242
2243 fn set_note_instrument(&self, mxnote: &mut Element, stored: Option<&Instrument>) -> Result<()> {
2250 if self.context.instruments.len() <= 1 {
2251 return Ok(());
2252 }
2253 let at = self.start + self.element_offset;
2254 let default;
2255 let closest = match stored.or_else(|| {
2256 self.context
2257 .own_instruments
2258 .iter()
2259 .rev()
2260 .find(|(offset, _)| *offset <= at)
2261 .map(|(_, instrument)| *instrument)
2262 }) {
2263 Some(instrument) => instrument,
2264 None => {
2265 default = Instrument::new();
2266 &default
2267 }
2268 };
2269 let named = self
2270 .context
2271 .instruments
2272 .iter()
2273 .find(|placed| placed.instrument.kind() == closest.kind())
2274 .ok_or_else(|| {
2275 export_error(format!(
2276 "Instrument instance {} for a note not found in instrumentStream",
2277 closest.kind()
2278 ))
2279 })?;
2280 let element = mxnote.sub("instrument");
2281 if let Some(id) = named.instrument.instrument_id() {
2282 element.set("id", id);
2283 }
2284 Ok(())
2285 }
2286
2287 fn chord_elements(
2290 &mut self,
2291 chord: &Chord,
2292 position: Option<*const StreamElement>,
2293 ) -> Result<()> {
2294 for (index, note) in chord.notes().iter().enumerate() {
2295 let element = self.note_element(note, index, Some(chord), position)?;
2296 self.root.push(element);
2297 }
2298 Ok(())
2299 }
2300
2301 fn rest_element(
2303 &self,
2304 rest: &Rest,
2305 offset: FloatType,
2306 position: Option<*const StreamElement>,
2307 ) -> Result<Element> {
2308 let mut mxnote = Element::new("note");
2309 if let Some(color) = rest.color() {
2310 mxnote.set("color", normalize_color(color)?);
2311 }
2312 if rest.hidden() {
2313 mxnote.set("print-object", "no");
2314 mxnote.set("print-spacing", "yes");
2315 }
2316 let duration = rest.duration();
2317 let full_measure = match rest.full_measure() {
2319 FullMeasure::Yes | FullMeasure::Always => true,
2320 FullMeasure::No => false,
2321 FullMeasure::Auto => self
2322 .meter_at(offset)
2323 .is_some_and(|meter| meter.bar_quarter_length() == duration.quarter_length()),
2324 };
2325 let mxrest = mxnote.sub("rest");
2326 if full_measure {
2327 mxrest.set("measure", "yes");
2328 }
2329 if rest.step_shift() != 0 {
2330 let at = self.start + offset;
2335 let lowest = self
2336 .context
2337 .own_clefs
2338 .iter()
2339 .rev()
2340 .find(|(clef_offset, _)| *clef_offset <= at)
2341 .and_then(|(_, clef)| clef.lowest_line())
2342 .or_else(|| Clef::treble().lowest_line())
2343 .unwrap_or(31);
2344 let (step, octave) = step_and_octave(lowest + 4 + rest.step_shift());
2345 mxrest.sub_text("display-step", step.to_string());
2346 mxrest.sub_text("display-octave", musicxml_octave(octave).to_string());
2347 }
2348 mxnote.sub_text("duration", divisions(duration.quarter_length()).to_string());
2349 if let Some(tie) = rest.tie() {
2350 for element in tie_elements(tie) {
2351 mxnote.push(element);
2352 }
2353 }
2354 if let Some(voice) = &self.current_voice {
2355 mxnote.sub_text("voice", voice.as_str());
2356 }
2357 if !full_measure {
2358 let (written_type, dots) = written_type(duration)?;
2359 let written = mxnote.sub("type");
2360 written.set_text(written_type);
2361 if let Some(size) = rest.size() {
2362 written.set("size", size.as_str());
2363 }
2364 for _ in 0..dots {
2365 mxnote.sub("dot");
2366 }
2367 }
2368 if let Some(modification) = time_modification(duration)? {
2369 mxnote.push(modification);
2370 }
2371 let mut notations = notations(
2372 Marks {
2373 expressions: rest.expressions(),
2374 articulations: rest.articulations(),
2375 tie: rest.tie(),
2376 index: 0,
2377 last: 0,
2378 arpeggios: &self.arpeggios,
2379 pitch: None,
2380 },
2381 position,
2382 &self.context.spanners,
2383 )?;
2384 notations.extend(tuplet_notations(duration)?);
2385 if !notations.is_empty() {
2386 let mxnotations = mxnote.sub("notations");
2387 for element in notations {
2388 mxnotations.push(element);
2389 }
2390 }
2391 for lyric in rest.lyrics() {
2392 if lyric.explicit_text().is_none() {
2393 continue;
2394 }
2395 mxnote.push(lyric_element(lyric)?);
2396 }
2397 Ok(mxnote)
2398 }
2399
2400 fn meter_at(&self, offset: FloatType) -> Option<TimeSignature> {
2403 self.stream
2404 .events()
2405 .iter()
2406 .rev()
2407 .filter(|event| event.offset() <= offset)
2408 .find_map(|event| match event.element() {
2409 StreamElement::TimeSignature(meter) => Some(meter.clone()),
2410 _ => None,
2411 })
2412 .or_else(|| self.context.meter.clone())
2413 }
2414
2415 fn offset_element(&self, offset: FloatType, sound: bool) -> Option<Element> {
2420 offset_element_from(offset, self.offset_in_measure, sound)
2421 }
2422
2423 fn direction(
2425 &self,
2426 inner: Element,
2427 placement: Option<Placement>,
2428 offset: FloatType,
2429 sound: bool,
2430 ) -> Element {
2431 let mut direction = Element::new("direction");
2432 direction.sub("direction-type").push(inner);
2433 if let Some(placement) = placement {
2434 direction.set("placement", placement.as_str());
2435 }
2436 if let Some(element) = self.offset_element(offset, sound) {
2437 direction.push(element);
2438 }
2439 direction
2440 }
2441
2442 fn repeat_expression_element(&self, mark: &RepeatExpression, offset: FloatType) -> Element {
2446 if mark.use_symbol() && mark.kind().has_symbol() {
2447 let tag = match mark.kind() {
2448 RepeatExpressionKind::Segno => "segno",
2449 _ => "coda",
2450 };
2451 let mut sign = Element::new(tag);
2452 sign.set("default-y", "20");
2453 return self.direction(sign, mark.placement(), offset, true);
2454 }
2455 let mut words = Element::with_text("words", mark.text());
2456 words.set("justify", mark.kind().justify());
2457 self.direction(words, mark.placement(), offset, false)
2458 }
2459
2460 fn harmony_element(&self, symbol: &ChordSymbol, offset: FloatType) -> Result<Element> {
2463 let mut harmony = Element::new("harmony");
2464 if let Some(placement) = symbol.placement() {
2465 harmony.set("placement", placement.as_str());
2466 }
2467 let alter_of = |pitch: &Pitch| {
2468 pitch
2469 .written_accidental()
2470 .map(|accidental| accidental.alter())
2471 };
2472 if symbol.is_no_chord() {
2473 let root = harmony.sub("root");
2474 let step = root.sub("root-step");
2475 step.set_text("C");
2476 step.set("text", "");
2477 let kind = harmony.sub("kind");
2478 kind.set_text("none");
2479 kind.set(
2480 "text",
2481 symbol.kind_text().ok_or_else(|| {
2482 export_error("NoChord object's chordKindStr must be non-empty")
2483 })?,
2484 );
2485 } else {
2486 let root = symbol.root();
2487 let written = harmony.sub("root");
2488 written.sub_text("root-step", root.step().as_char().to_string());
2489 if let Some(alter) = alter_of(root) {
2490 written.sub_text("root-alter", num_to_int_or_float(alter));
2491 }
2492 let mut kind = symbol.kind().unwrap_or("none");
2493 for (alias, target) in crate::chordsymbol::CHORD_KIND_ALIASES {
2494 if target == kind {
2495 kind = alias;
2496 }
2497 }
2498 let written = harmony.sub("kind");
2499 written.set_text(kind);
2500 if let Some(text) = symbol.kind_text() {
2501 written.set("text", text);
2502 }
2503 let sounded = symbol.pitches().ok().filter(|pitches| !pitches.is_empty());
2506 let chord = sounded
2507 .as_ref()
2508 .and_then(|pitches| Chord::new(pitches.as_slice()).ok())
2509 .map(|mut chord| {
2510 chord.set_root(Some(root.clone()));
2511 chord
2512 });
2513 if let Some(inversion) = chord.as_ref().and_then(Chord::inversion)
2514 && inversion != 0
2515 {
2516 harmony.sub_text("inversion", inversion.to_string());
2517 }
2518 let bass = symbol.bass().cloned().or_else(|| {
2519 sounded
2520 .as_ref()
2521 .and_then(|pitches| pitches.first().cloned())
2522 });
2523 if let Some(bass) = bass
2524 && bass.name() != root.name()
2525 {
2526 let written = harmony.sub("bass");
2527 written.sub_text("bass-step", bass.step().as_char().to_string());
2528 if let Some(alter) = alter_of(&bass) {
2529 written.sub_text("bass-alter", num_to_int_or_float(alter));
2530 }
2531 }
2532 for modification in symbol.chord_step_modifications() {
2533 let degree = harmony.sub("degree");
2534 degree.sub_text("degree-value", modification.degree().to_string());
2535 degree.sub_text(
2536 "degree-alter",
2537 num_to_int_or_float(modification.interval().semitones()),
2538 );
2539 degree.sub_text(
2540 "degree-type",
2541 modification.modification_type().music21_name(),
2542 );
2543 }
2544 }
2545 if let Some(element) = self.offset_element(offset, true) {
2546 harmony.push(element);
2547 }
2548 Ok(harmony)
2549 }
2550
2551 fn dynamic_element(&mut self, dynamic: &Dynamic, offset: FloatType) {
2553 let mut dynamics = Element::new("dynamics");
2554 if DYNAMIC_MARKS.contains(&dynamic.value()) {
2555 dynamics.sub(dynamic_tag(dynamic.value()));
2556 } else {
2557 dynamics.sub_text("other-dynamics", dynamic.value());
2558 }
2559 dynamics.set("default-x", "-36");
2561 dynamics.set("default-y", "-80");
2562 let mut direction = self.direction(dynamics, dynamic.placement(), offset, true);
2563 let volume = (dynamic.volume_scalar() * 127.0) as i64;
2564 direction.sub("sound").set("dynamics", volume.to_string());
2565 self.root.push(direction);
2566 }
2567
2568 fn tempo_elements(&mut self, mark: &MetronomeMark, offset: FloatType) -> Result<()> {
2571 let shown = !mark.number_implicit() && mark.number().is_some();
2572 let direction = if shown {
2573 let mut metronome = Element::new("metronome");
2574 let (beat_unit, dots) = written_type(mark.referent())?;
2575 metronome.sub_text("beat-unit", beat_unit);
2576 for _ in 0..dots {
2577 metronome.sub("beat-unit-dot");
2578 }
2579 if let Some(number) = mark.number() {
2580 metronome.sub_text("per-minute", num_to_int_or_float(number));
2581 }
2582 metronome.set("parentheses", yes_no(mark.parentheses()));
2583 self.direction(metronome, mark.placement(), offset, true)
2584 } else {
2585 self.direction(Element::new("words"), mark.placement(), offset, true)
2586 };
2587 let mut direction = direction;
2588 if let Some(bpm) = mark.sounding_quarter_bpm().filter(|bpm| *bpm != 0.0) {
2589 direction
2590 .sub("sound")
2591 .set("tempo", num_to_int_or_float(bpm));
2592 }
2593 self.root.push(direction);
2594
2595 if let Some(text) = mark.text_expression(false) {
2596 let direction = self.tempo_words(text, None, offset);
2597 self.root.push(direction);
2598 }
2599 Ok(())
2600 }
2601
2602 fn metric_modulation_element(
2606 &self,
2607 modulation: &MetricModulation,
2608 offset: FloatType,
2609 ) -> Result<Element> {
2610 let mut metronome = Element::new("metronome");
2611 for side in [modulation.old_metronome(), modulation.new_metronome()] {
2612 let side = side.ok_or_else(|| {
2613 export_error("a metric modulation needs a mark on both sides to be written")
2614 })?;
2615 let (beat_unit, dots) = written_type(side.referent())?;
2616 metronome.sub_text("beat-unit", beat_unit);
2617 for _ in 0..dots {
2618 metronome.sub("beat-unit-dot");
2619 }
2620 }
2621 metronome.set("parentheses", yes_no(modulation.parentheses()));
2622 let mut direction = self.direction(metronome, modulation.placement(), offset, true);
2623 if let Some(bpm) = modulation
2624 .new_metronome()
2625 .and_then(MetronomeMark::sounding_quarter_bpm)
2626 .filter(|bpm| *bpm != 0.0)
2627 {
2628 direction
2629 .sub("sound")
2630 .set("tempo", num_to_int_or_float(bpm));
2631 }
2632 Ok(direction)
2633 }
2634
2635 fn tempo_words(&self, text: &str, placement: Option<Placement>, offset: FloatType) -> Element {
2638 let mut words = Element::with_text("words", text);
2639 words.set("default-y", "45");
2640 words.set("font-weight", "bold");
2641 self.direction(words, placement, offset, false)
2642 }
2643}
2644
2645fn note_notations(
2653 note: &Note,
2654 index: usize,
2655 chord: Option<&Chord>,
2656 position: Option<*const StreamElement>,
2657 spanners: &[Numbered<'_>],
2658 arpeggios: &ArpeggioNumbers,
2659 pitch: FloatType,
2660) -> Result<Vec<Element>> {
2661 notations(
2662 Marks {
2663 expressions: chord.map_or(note.expressions(), Chord::expressions),
2664 articulations: chord.map_or(note.articulations(), Chord::articulations),
2665 tie: note.tie(),
2666 index,
2667 last: chord.map_or(0, |chord| chord.notes().len().saturating_sub(1)),
2668 arpeggios,
2669 pitch: Some(pitch),
2670 },
2671 position,
2672 spanners,
2673 )
2674}
2675
2676struct Marks<'a> {
2680 expressions: &'a [Expression],
2681 articulations: &'a [Articulation],
2682 tie: Option<&'a Tie>,
2683 index: usize,
2684 last: usize,
2685 arpeggios: &'a ArpeggioNumbers,
2687 pitch: Option<FloatType>,
2689}
2690
2691fn notations(
2693 marks: Marks<'_>,
2694 position: Option<*const StreamElement>,
2695 spanners: &[Numbered<'_>],
2696) -> Result<Vec<Element>> {
2697 let index = marks.index;
2698 let first = index == 0;
2699 let mut notations = Vec::new();
2700 let (articulations, technical) = articulation_elements(marks.articulations, index);
2701 let mut ornaments: Option<Element> = None;
2702 for expression in marks.expressions {
2703 match expression {
2704 Expression::Arpeggio(ArpeggioType::NonArpeggio) => {
2705 let last = marks.last;
2706 let side = if first {
2707 Some("bottom")
2708 } else if index == last {
2709 Some("top")
2710 } else {
2711 None
2712 };
2713 if let Some(side) = side {
2714 let mut element = Element::new("non-arpeggiate");
2715 element.set("type", side);
2716 notations.push(element);
2717 }
2718 }
2719 Expression::Arpeggio(arpeggio) => {
2720 let mut element = Element::new("arpeggiate");
2721 if *arpeggio != ArpeggioType::Normal {
2722 element.set("direction", arpeggio.as_str());
2723 }
2724 notations.push(element);
2725 }
2726 Expression::Fermata(fermata) if first => {
2727 let mut element = Element::new("fermata");
2728 element.set("type", fermata.fermata_type().as_str());
2729 if let Some(shape @ ("angled" | "square")) = fermata.shape() {
2730 element.set_text(shape);
2731 }
2732 notations.push(element);
2733 }
2734 Expression::Ornament(ornament) if first => {
2735 let written = ornaments.get_or_insert_with(|| Element::new("ornaments"));
2736 written.push(ornament_element(ornament));
2737 for mark in ornament_accidental_marks(ornament)? {
2738 written.push(mark);
2739 }
2740 }
2741 Expression::Fermata(_) | Expression::Ornament(_) => {}
2742 }
2743 }
2744 if let Some(tie) = marks.tie {
2745 notations.extend(tied_elements(tie));
2746 }
2747 if let Some(position) = position {
2748 notations.extend(spanner_notations(position, spanners, &marks));
2749 }
2750 notations.extend(articulations);
2751 notations.extend(technical);
2752 notations.extend(ornaments);
2753 Ok(notations)
2754}
2755
2756fn written_order(chord: &Chord) -> Chord {
2760 chord.sort_ascending()
2761}
2762
2763fn articulation_elements(
2769 articulations: &[Articulation],
2770 index: usize,
2771) -> (Option<Element>, Option<Element>) {
2772 let mut fingering = 0;
2773 let mut applicable = Vec::new();
2774 for articulation in articulations {
2775 if articulation.is_a("Fingering") {
2776 if fingering == index {
2777 applicable.push(articulation);
2778 }
2779 fingering += 1;
2780 } else if index == 0 && !articulation.is_a("HammerOn") && !articulation.is_a("PullOff") {
2781 applicable.push(articulation);
2782 }
2783 }
2784 let mut written: Option<Element> = None;
2785 let mut technical: Option<Element> = None;
2786 for articulation in applicable {
2787 if articulation.is_a("Pizzicato")
2788 || (articulation.is_a("StringIndication") && articulation.number() < 1)
2789 {
2790 continue;
2791 }
2792 if articulation.is_a("TechnicalIndication") {
2793 technical
2794 .get_or_insert_with(|| Element::new("technical"))
2795 .push(technical_element(articulation));
2796 } else {
2797 written
2798 .get_or_insert_with(|| Element::new("articulations"))
2799 .push(articulation_element(articulation));
2800 }
2801 }
2802 (written, technical)
2803}
2804
2805const ARTICULATION_NAMES: [(&str, &str); 15] = [
2809 ("StrongAccent", "strong-accent"),
2810 ("Staccatissimo", "staccatissimo"),
2811 ("Spiccato", "spiccato"),
2812 ("Tenuto", "tenuto"),
2813 ("DetachedLegato", "detached-legato"),
2814 ("Scoop", "scoop"),
2815 ("Plop", "plop"),
2816 ("Doit", "doit"),
2817 ("Falloff", "falloff"),
2818 ("BreathMark", "breath-mark"),
2819 ("Caesura", "caesura"),
2820 ("Stress", "stress"),
2821 ("Unstress", "unstress"),
2822 ("Staccato", "staccato"),
2823 ("Accent", "accent"),
2824];
2825
2826fn articulation_element(articulation: &Articulation) -> Element {
2828 let name = ARTICULATION_NAMES
2829 .iter()
2830 .find(|(class, _)| articulation.is_a(class))
2831 .map_or("other-articulation", |(_, name)| name);
2832 let mut element = Element::new(name);
2833 if let Some(placement) = articulation.placement() {
2834 element.set("placement", placement);
2835 }
2836 match name {
2837 "strong-accent" => {
2838 element.set("type", articulation.point_direction().unwrap_or("None"));
2839 }
2840 "breath-mark" => {
2841 if let Some(symbol) = articulation.symbol() {
2842 element.set_text(symbol);
2843 }
2844 }
2845 "other-articulation" => {
2846 if let Some(text) = articulation.display_text() {
2847 element.set_text(text);
2848 }
2849 }
2850 _ => {}
2851 }
2852 element
2853}
2854
2855const TECHNICAL_NAMES: [(&str, &str); 20] = [
2857 ("UpBow", "up-bow"),
2858 ("DownBow", "down-bow"),
2859 ("StringHarmonic", "harmonic"),
2860 ("OpenString", "open-string"),
2861 ("StringThumbPosition", "thumb-position"),
2862 ("Fingering", "fingering"),
2863 ("FrettedPluck", "pluck"),
2864 ("DoubleTongue", "double-tongue"),
2865 ("TripleTongue", "triple-tongue"),
2866 ("Stopped", "stopped"),
2867 ("SnapPizzicato", "snap-pizzicato"),
2868 ("StringIndication", "string"),
2869 ("FretBend", "bend"),
2870 ("FretTap", "tap"),
2871 ("FretIndication", "fret"),
2872 ("OrganHeel", "heel"),
2873 ("OrganToe", "toe"),
2874 ("HarpFingerNails", "fingernails"),
2875 ("HandbellIndication", "handbell"),
2876 ("Harmonic", "harmonic"),
2877];
2878
2879fn technical_element(articulation: &Articulation) -> Element {
2881 use crate::articulations::Finger;
2882 let name = TECHNICAL_NAMES
2883 .iter()
2884 .find(|(class, _)| articulation.is_a(class))
2885 .map_or("other-technical", |(_, name)| name);
2886 let mut element = Element::new(name);
2887 if let Some(placement) = articulation.placement() {
2888 element.set("placement", placement);
2889 }
2890 match name {
2891 "fingering" => {
2892 element.set_text(match articulation.finger() {
2893 Some(Finger::Number(number)) => number.to_string(),
2894 Some(Finger::Written(text)) => text.clone(),
2895 None => "None".to_string(),
2896 });
2897 element.set("alternate", yes_no(articulation.alternate()));
2898 element.set("substitution", yes_no(articulation.substitution()));
2899 }
2900 "heel" | "toe" => element.set("substitution", yes_no(articulation.substitution())),
2901 "string" | "fret" => element.set_text(articulation.number().to_string()),
2902 "bend" => {
2903 let alter = element.sub("bend-alter");
2904 if let Some(interval) = articulation.bend_alter() {
2905 alter.set_text(num_to_int_or_float(interval.semitones()));
2906 }
2907 if articulation.pre_bend() {
2908 element.sub("pre-bend");
2909 }
2910 if let Some(release) = articulation.release() {
2911 let offset = (DIVISIONS_PER_QUARTER * release) as i64;
2912 element.sub("release").set("offset", offset.to_string());
2913 }
2914 if let Some(with_bar) = articulation.with_bar() {
2915 element.sub_text("with-bar", with_bar);
2916 }
2917 }
2918 "harmonic" => {
2919 if articulation.is_a("StringHarmonic") {
2920 match articulation.harmonic_type() {
2921 Some("artificial") => {
2922 element.sub("artificial");
2923 }
2924 Some("natural") => {
2925 element.sub("natural");
2926 }
2927 _ => {}
2928 }
2929 match articulation.pitch_type() {
2930 Some("base") => {
2931 element.sub("base-pitch");
2932 }
2933 Some("sounding") => {
2934 element.sub("sounding-pitch");
2935 }
2936 Some("touching") => {
2937 element.sub("touching-pitch");
2938 }
2939 _ => {}
2940 }
2941 }
2942 }
2943 "handbell" | "other-technical" => {
2944 if let Some(text) = articulation.display_text() {
2945 element.set_text(text);
2946 }
2947 }
2948 _ => {}
2949 }
2950 element
2951}
2952
2953fn related_spanners(
2959 position: *const StreamElement,
2960 spanners: &[Numbered<'_>],
2961 offset_in_measure: FloatType,
2962) -> (Vec<Element>, Vec<Element>) {
2963 let mut before = Vec::new();
2964 let mut after = Vec::new();
2965 let of_kind = |wanted: fn(SpannerKind) -> bool| {
2968 spanners
2969 .iter()
2970 .filter(move |numbered| wanted(numbered.spanner.kind()))
2971 };
2972 let ordered = of_kind(|kind| kind == SpannerKind::Ottava)
2973 .chain(of_kind(SpannerKind::is_wedge))
2974 .chain(of_kind(|kind| kind == SpannerKind::Line))
2975 .chain(of_kind(|kind| kind == SpannerKind::PedalMark));
2976 for numbered in ordered {
2977 let (spanner, id_local) = (numbered.spanner, numbered.id_local);
2978 if !numbered.holds(position) {
2979 continue;
2980 }
2981 let single = numbered.elements.len() == 1;
2982 let starts = single || numbered.is_first(position);
2983 let stops = single || numbered.is_last(position);
2984 for (here, start) in [(starts, true), (stops, false)] {
2985 if !here {
2986 continue;
2987 }
2988 let mark = match spanner.kind() {
2989 SpannerKind::Ottava => {
2990 let mut shift = Element::new("octave-shift");
2991 shift.set("number", id_local.to_string());
2992 let octave = spanner.shift();
2993 shift.set(
2996 "type",
2997 match (start, octave.is_some_and(|octave| octave.up())) {
2998 (false, _) => "stop",
2999 (true, true) => "down",
3000 (true, false) => "up",
3001 },
3002 );
3003 if let Some(octave) = octave {
3004 shift.set("size", octave.magnitude().to_string());
3005 }
3006 shift
3007 }
3008 SpannerKind::Line => {
3009 let mut bracket = Element::new("bracket");
3010 bracket.set("number", id_local.to_string());
3011 bracket.set("line-type", spanner.line_type().unwrap_or("solid"));
3012 bracket.set("type", if start { "start" } else { "stop" });
3013 let ends = spanner.ends();
3014 let (tick, height) = if start {
3015 (ends.start, ends.start_height)
3016 } else {
3017 (ends.end, ends.end_height)
3018 };
3019 bracket.set("line-end", tick.as_str());
3020 if let Some(height) = height {
3021 bracket.set("end-length", num_to_int_or_float(height));
3022 }
3023 bracket
3024 }
3025 SpannerKind::PedalMark => pedal_element(spanner, id_local, start),
3026 _ => wedge_element(spanner, id_local, start),
3027 };
3028 let mut direction = Element::new("direction");
3029 if let Some(placement) = spanner.placement() {
3030 direction.set("placement", placement.as_str());
3031 }
3032 direction.sub("direction-type").push(mark);
3033 if start {
3034 before.push(direction);
3035 } else {
3036 after.push(direction);
3037 }
3038 if start
3039 && spanner.kind() == SpannerKind::PedalMark
3040 && spanner.pedal().and_then(|pedal| pedal.form) == Some(PedalForm::SymbolLine)
3041 {
3042 let mut line = Element::new("pedal");
3043 line.set("type", "resume");
3044 line.set("line", "yes");
3045 let mut direction = Element::new("direction");
3046 direction.sub("direction-type").push(line);
3047 if let Some(placement) = spanner.placement() {
3048 direction.set("placement", placement.as_str());
3049 }
3050 if let Some(offset) = offset_element_from(spanner.offset(), offset_in_measure, true)
3051 {
3052 direction.push(offset);
3053 }
3054 before.push(direction);
3055 }
3056 }
3057 }
3058 (before, after)
3059}
3060
3061fn offset_element_from(
3064 offset: FloatType,
3065 offset_in_measure: FloatType,
3066 sound: bool,
3067) -> Option<Element> {
3068 if offset == offset_in_measure && !is_fraction_offset(offset) {
3072 return None;
3073 }
3074 let difference = ((offset - offset_in_measure) * DIVISIONS_PER_QUARTER) as i64;
3075 let mut element = Element::with_text("offset", difference.to_string());
3076 if sound {
3077 element.set("sound", "yes");
3078 }
3079 Some(element)
3080}
3081
3082fn pedal_element(spanner: &Spanner, id_local: u32, start: bool) -> Element {
3088 let pedal = spanner.pedal().unwrap_or_default();
3089 let mut element = Element::new("pedal");
3090 element.set("number", id_local.to_string());
3091 let line = pedal.form.is_some_and(PedalForm::has_line);
3092 if start {
3093 element.set(
3094 "type",
3095 if pedal.pedal_type == Some(PedalType::Sostenuto) {
3096 "sostenuto"
3097 } else {
3098 "start"
3099 },
3100 );
3101 if pedal.form == Some(PedalForm::Line) {
3102 element.set("line", "yes");
3103 } else {
3104 element.set("sign", "yes");
3105 }
3106 if pedal.abbreviated {
3107 element.set("abbreviated", "yes");
3108 }
3109 } else {
3110 element.set("type", "stop");
3111 if line {
3112 element.set("line", "yes");
3113 } else {
3114 element.set("sign", "yes");
3115 }
3116 }
3117 element
3118}
3119
3120fn wedge_element(spanner: &Spanner, id_local: u32, start: bool) -> Element {
3123 let crescendo = spanner.kind() == SpannerKind::Crescendo;
3124 let mut wedge = Element::new("wedge");
3125 wedge.set("number", id_local.to_string());
3126 wedge.set(
3127 "type",
3128 match (start, crescendo) {
3129 (true, true) => "crescendo",
3130 (true, false) => "diminuendo",
3131 (false, _) => "stop",
3132 },
3133 );
3134 if start == crescendo {
3135 wedge.set("spread", "0");
3136 } else if let Some(spread) = spanner.spread() {
3137 wedge.set("spread", num_to_int_or_float(spread));
3138 }
3139 wedge
3140}
3141
3142fn spanner_notations(
3145 position: *const StreamElement,
3146 spanners: &[Numbered<'_>],
3147 marks: &Marks<'_>,
3148) -> Vec<Element> {
3149 let mut notations = Vec::new();
3150 for numbered in spanners {
3154 let spanner = numbered.spanner;
3155 if spanner.kind() != SpannerKind::ArpeggioMark || !numbered.holds(position) {
3156 continue;
3157 }
3158 let mut element = if spanner.arpeggio() == ArpeggioType::NonArpeggio {
3159 let Some(((low, low_ps), (high, high_ps))) = numbered.note_extremes() else {
3160 continue;
3161 };
3162 let is = |at: *const StreamElement, ps: FloatType| {
3163 std::ptr::eq(at, position) && marks.pitch == Some(ps)
3164 };
3165 let side = if is(low, low_ps) {
3166 "bottom"
3167 } else if is(high, high_ps) {
3168 "top"
3169 } else {
3170 continue;
3171 };
3172 let mut element = Element::new("non-arpeggiate");
3173 element.set("type", side);
3174 element
3175 } else {
3176 let mut element = Element::new("arpeggiate");
3177 if spanner.arpeggio() != ArpeggioType::Normal {
3178 element.set("direction", spanner.arpeggio().as_str());
3179 }
3180 element
3181 };
3182 let notes: usize = numbered
3183 .elements
3184 .iter()
3185 .flatten()
3186 .map(|element| match element {
3187 StreamElement::Chord(chord) => chord.notes().len(),
3188 StreamElement::PercussionChord(chord) => chord.len(),
3190 _ => 1,
3191 })
3192 .sum();
3193 if numbered.elements.len() > 1 || notes > 1 {
3194 element.set("number", marks.arpeggios.number(spanner).to_string());
3195 }
3196 notations.push(element);
3197 }
3198 if marks.index != 0 {
3199 return notations;
3200 }
3201 for numbered in spanners {
3202 let (spanner, id_local) = (numbered.spanner, numbered.id_local);
3203 if spanner.kind() != SpannerKind::Slur {
3204 continue;
3205 }
3206 let mut slur = Element::new("slur");
3207 if numbered.is_first(position) {
3208 slur.set("type", "start");
3209 if let Some(line_type) = spanner.line_type() {
3210 slur.set("line-type", line_type);
3211 }
3212 if let Some(placement) = spanner.placement() {
3213 slur.set("placement", placement.as_str());
3214 }
3215 } else if numbered.is_last(position) {
3216 slur.set("type", "stop");
3217 } else {
3218 continue;
3219 }
3220 slur.set("number", id_local.to_string());
3221 notations.push(slur);
3222 }
3223 for numbered in spanners {
3226 let (spanner, id_local) = (numbered.spanner, numbered.id_local);
3227 if spanner.kind() != SpannerKind::Glissando {
3228 continue;
3229 }
3230 let details = spanner.glissando_details().cloned().unwrap_or_default();
3231 let mut glissando = Element::new(if details.slide_type == SlideType::Continuous {
3232 "slide"
3233 } else {
3234 "glissando"
3235 });
3236 glissando.set("number", id_local.to_string());
3237 if let Some(line_type) = spanner.line_type() {
3238 glissando.set("line-type", line_type);
3239 }
3240 if numbered.is_first(position) {
3241 if let Some(label) = details.label {
3242 glissando.set_text(label);
3243 }
3244 glissando.set("type", "start");
3245 } else if numbered.is_last(position) {
3246 glissando.set("type", "stop");
3247 } else {
3248 continue;
3249 }
3250 notations.push(glissando);
3251 }
3252 let mut ornaments = Element::new("ornaments");
3256 for numbered in spanners {
3257 let spanner = numbered.spanner;
3258 if spanner.kind() != SpannerKind::TremoloSpanner || !numbered.holds(position) {
3259 continue;
3260 }
3261 let mut tremolo = Element::with_text(
3262 "tremolo",
3263 spanner.number_of_marks().unwrap_or(3).to_string(),
3264 );
3265 if numbered.is_first(position) {
3266 tremolo.set("type", "start");
3267 if let Some(placement) = spanner.placement() {
3268 tremolo.set("placement", placement.as_str());
3269 }
3270 } else if numbered.is_last(position) {
3271 tremolo.set("type", "stop");
3272 }
3273 ornaments.push(tremolo);
3274 }
3275 for numbered in spanners {
3276 let (spanner, id_local) = (numbered.spanner, numbered.id_local);
3277 if spanner.kind() != SpannerKind::TrillExtension {
3278 continue;
3279 }
3280 let first = numbered.is_first(position);
3281 let last = numbered.is_last(position);
3282 if !first && !last {
3283 continue;
3284 }
3285 let mut line = Element::new("wavy-line");
3286 line.set("number", id_local.to_string());
3287 if first {
3288 line.set("type", "start");
3289 if let Some(placement) = spanner.placement() {
3290 line.set("placement", placement.as_str());
3291 }
3292 } else {
3293 line.set("type", "stop");
3294 }
3295 ornaments.push(line);
3296 if first && last {
3297 let mut stop = Element::new("wavy-line");
3298 stop.set("number", id_local.to_string());
3299 stop.set("type", "stop");
3300 ornaments.push(stop);
3301 }
3302 }
3303 if ornaments.len() > 0 {
3304 notations.push(ornaments);
3305 }
3306 notations
3307}
3308
3309fn ornament_element(ornament: &Ornament) -> Element {
3311 let tag = if ornament.is_a("Turn") {
3312 match (ornament.is_a("InvertedTurn"), ornament.is_delayed()) {
3313 (true, true) => "delayed-inverted-turn",
3314 (true, false) => "inverted-turn",
3315 (false, true) => "delayed-turn",
3316 (false, false) => "turn",
3317 }
3318 } else {
3319 [
3320 ("Trill", "trill-mark"),
3321 ("InvertedMordent", "inverted-mordent"),
3322 ("Mordent", "mordent"),
3323 ("Shake", "shake"),
3324 ("Schleifer", "schleifer"),
3325 ("Tremolo", "tremolo"),
3326 ]
3327 .into_iter()
3328 .find(|(class, _)| ornament.is_a(class))
3329 .map_or("other-ornament", |(_, tag)| tag)
3330 };
3331 let mut element = Element::new(tag);
3332 if let Some(placement) = ornament.placement() {
3333 element.set("placement", placement);
3334 }
3335 if ornament.is_a("Tremolo") {
3336 element.set("type", "single");
3337 element.set_text(ornament.number_of_marks().to_string());
3338 }
3339 element
3340}
3341
3342fn ornament_accidental_marks(ornament: &Ornament) -> Result<Vec<Element>> {
3345 let shown = |accidental: Option<&Accidental>| {
3346 accidental
3347 .filter(|accidental| accidental.display_status() == Some(true))
3348 .cloned()
3349 };
3350 let mut marks = Vec::new();
3351 let mut mark = |accidental: Accidental, placement: &str| -> Result<()> {
3352 let mut element = accidental_element(&accidental, "accidental-mark")?;
3353 element.set("placement", placement);
3354 marks.push(element);
3355 Ok(())
3356 };
3357 if ornament.is_a("Turn") {
3358 if let Some(upper) = shown(ornament.upper_accidental()) {
3359 mark(upper, "above")?;
3360 }
3361 if let Some(lower) = shown(ornament.lower_accidental()) {
3362 mark(lower, "below")?;
3363 }
3364 } else if (ornament.is_a("GeneralMordent") || ornament.is_a("Trill"))
3365 && let Some(accidental) = shown(ornament.accidental())
3366 {
3367 let below = ornament.direction() == Some(crate::interval::IntervalDirection::Descending);
3368 mark(accidental, if below { "below" } else { "above" })?;
3369 }
3370 Ok(marks)
3371}
3372
3373fn barline_element(
3376 barline: Option<&Barline>,
3377 location: &str,
3378 ending: Option<(&Ending, &str)>,
3379) -> Element {
3380 let mut element = Element::new("barline");
3381 let repeat = barline.and_then(Barline::repeat_direction);
3382 if let Some(barline) = barline
3383 && repeat.is_none()
3384 {
3385 element.sub_text("bar-style", barline.bar_type().musicxml_bar_style());
3386 }
3387 element.set("location", location);
3388 if let Some((ending, kind)) = ending {
3389 let written = element.sub("ending");
3390 written.set("number", ending.number_text());
3391 written.set("type", kind);
3392 }
3393 if let (Some(barline), Some(direction)) = (barline, repeat) {
3394 let written = element.sub("repeat");
3395 written.set(
3396 "direction",
3397 match direction {
3398 RepeatDirection::Start => "forward",
3399 RepeatDirection::End => "backward",
3400 },
3401 );
3402 if let Some(times) = barline.repeat_times() {
3403 written.set("times", times.to_string());
3404 }
3405 }
3406 element
3407}
3408
3409fn last_of<T>(stream: &Stream, read: impl Fn(&StreamElement) -> Option<T>) -> Option<T> {
3411 stream
3412 .events()
3413 .iter()
3414 .rev()
3415 .find_map(|event| read(event.element()))
3416}
3417
3418fn is_general_note(element: &StreamElement) -> bool {
3420 matches!(
3421 element,
3422 StreamElement::Note(_)
3423 | StreamElement::Chord(_)
3424 | StreamElement::Unpitched(_)
3425 | StreamElement::PercussionChord(_)
3426 | StreamElement::Rest(_)
3427 | StreamElement::ChordSymbol(_)
3428 )
3429}
3430
3431fn is_written_as_note(element: &StreamElement) -> bool {
3434 matches!(
3435 element,
3436 StreamElement::Note(_)
3437 | StreamElement::Chord(_)
3438 | StreamElement::Unpitched(_)
3439 | StreamElement::PercussionChord(_)
3440 | StreamElement::Rest(_)
3441 )
3442}
3443
3444fn musicxml_octave(octave: i32) -> i32 {
3455 octave.clamp(0, 9)
3456}
3457
3458fn step_and_octave(number: i32) -> (char, i32) {
3461 let octave = (number - 1) / 7;
3462 let index = (number - 1 - 7 * octave).rem_euclid(7);
3463 (['C', 'D', 'E', 'F', 'G', 'A', 'B'][index as usize], octave)
3464}
3465
3466fn fraction_to_percent(share: FloatType) -> String {
3469 ((share * 100.0) as i64).to_string()
3470}
3471
3472fn divisions(quarter_length: FloatType) -> i64 {
3474 (quarter_length * DIVISIONS_PER_QUARTER).round_ties_even() as i64
3475}
3476
3477fn format_int(value: FloatType) -> String {
3478 (value as i64).to_string()
3479}
3480
3481fn num_to_int_or_float(value: FloatType) -> String {
3483 let rounded = value.round_ties_even();
3484 if (rounded - value).abs() < 1e-6 {
3485 format!("{}", rounded as i64)
3486 } else {
3487 format!("{value}")
3488 }
3489}
3490
3491fn musicxml_type(duration_type: DurationType) -> Result<&'static str> {
3493 match duration_type {
3494 DurationType::Longa => Ok("long"),
3495 DurationType::TwentyFortyEighth => Err(export_error(
3496 "Cannot convert \"2048th\" duration to MusicXML (too short).",
3497 )),
3498 DurationType::DuplexMaxima => Err(export_error(
3499 "Cannot convert \"duplex-maxima\" duration to MusicXML (too long).",
3500 )),
3501 DurationType::Zero => Err(export_error(
3502 "Cannot convert durations without types to MusicXML.",
3503 )),
3504 other => Ok(other.music21_name()),
3505 }
3506}
3507
3508fn written_type(duration: &Duration) -> Result<(&'static str, u32)> {
3511 let components = duration.components();
3512 match components.as_slice() {
3513 [(duration_type, dots)] => Ok((musicxml_type(*duration_type)?, *dots)),
3514 [] if duration.quarter_length() == 0.0 => musicxml_type(DurationType::Zero).map(|t| (t, 0)),
3515 [] => Err(export_error(
3516 "Cannot convert inexpressible durations to MusicXML.",
3517 )),
3518 _ => Err(export_error(
3519 "Cannot convert complex durations to MusicXML. Try exporting with \
3520 makeNotation=True or manually running splitAtDurations()",
3521 )),
3522 }
3523}
3524
3525fn time_modification(duration: &Duration) -> Result<Option<Element>> {
3527 let tuplets = duration.tuplets();
3528 match tuplets.as_slice() {
3529 [] => Ok(None),
3530 [tuplet] => tuplet_to_time_modification(tuplet).map(Some),
3531 _ => {
3532 let multiplier = duration.aggregate_tuplet_multiplier();
3533 let (numerator, denominator) = (
3534 *multiplier.numer().unwrap_or(&1),
3535 *multiplier.denom().unwrap_or(&1),
3536 );
3537 let composite = Tuplet::ratio(
3538 u32::try_from(denominator).unwrap_or(1),
3539 u32::try_from(numerator).unwrap_or(1),
3540 );
3541 tuplet_to_time_modification(&composite).map(Some)
3542 }
3543 }
3544}
3545
3546fn tuplet_to_time_modification(tuplet: &Tuplet) -> Result<Element> {
3548 let mut element = Element::new("time-modification");
3549 element.sub_text("actual-notes", tuplet.actual().to_string());
3550 element.sub_text("normal-notes", tuplet.normal().to_string());
3551 if let Some((duration_type, dots)) = tuplet.duration_normal() {
3552 element.sub_text("normal-type", musicxml_type(duration_type)?);
3553 for _ in 0..dots {
3554 element.sub("normal-dot");
3555 }
3556 }
3557 Ok(element)
3558}
3559
3560fn tuplet_notations(duration: &Duration) -> Result<Vec<Element>> {
3563 let mut out = Vec::new();
3564 for (index, tuplet) in duration.tuplets().iter().enumerate() {
3565 out.extend(tuplet_elements(tuplet, index + 1)?);
3566 }
3567 Ok(out)
3568}
3569
3570fn tuplet_elements(tuplet: &Tuplet, number: usize) -> Result<Vec<Element>> {
3573 use crate::duration::{TupletBracket, TupletShow, TupletType};
3574 let types: &[&str] = match tuplet.tuplet_type() {
3575 None => return Ok(Vec::new()),
3576 Some(TupletType::Start) => &["start"],
3577 Some(TupletType::Stop) => &["stop"],
3578 Some(TupletType::StartStop) => &["start", "stop"],
3579 };
3580 let mut out = Vec::new();
3581 for kind in types {
3582 let mut element = Element::new("tuplet");
3583 element.set("type", *kind);
3584 element.set("number", number.to_string());
3585 if *kind == "start" {
3586 element.set("bracket", yes_no(tuplet.bracket() != TupletBracket::None));
3587 if let Some(placement) = tuplet.placement() {
3588 element.set("placement", placement.as_str());
3589 }
3590 let actual = tuplet.actual_show();
3591 let normal = tuplet.normal_show();
3592 let shows_number = |show: Option<TupletShow>| {
3593 matches!(show, Some(TupletShow::Both | TupletShow::Number))
3594 };
3595 let shows_type = |show: Option<TupletShow>| {
3596 matches!(show, Some(TupletShow::Both | TupletShow::Type))
3597 };
3598 if actual.is_none() {
3599 element.set("show-number", "none");
3600 } else if shows_number(actual) && shows_number(normal) {
3601 element.set("show-number", "both");
3602 } else if actual == Some(TupletShow::Both) {
3603 element.set("show-number", "actual");
3606 }
3607 if shows_type(actual) && shows_type(normal) {
3608 element.set("show-type", "both");
3609 } else if shows_type(actual) {
3610 element.set("show-type", "actual");
3611 }
3612 if tuplet.bracket() == TupletBracket::Slur {
3613 element.set("line-shape", "curved");
3614 }
3615 for (tag, count, value) in [
3616 ("tuplet-actual", tuplet.actual(), tuplet.duration_actual()),
3617 ("tuplet-normal", tuplet.normal(), tuplet.duration_normal()),
3618 ] {
3619 let side = element.sub(tag);
3620 side.sub_text("tuplet-number", count.to_string());
3621 if let Some((duration_type, dots)) = value {
3622 side.sub_text("tuplet-type", musicxml_type(duration_type)?);
3623 for _ in 0..dots {
3624 side.sub("tuplet-dot");
3625 }
3626 }
3627 }
3628 }
3629 out.push(element);
3630 }
3631 Ok(out)
3632}
3633
3634fn pitch_element(pitch: &Pitch) -> Element {
3636 let mut element = Element::new("pitch");
3637 element.sub_text("step", pitch.step().as_char().to_string());
3638 if let Some(accidental) = pitch.written_accidental() {
3639 element.sub_text("alter", num_to_int_or_float(accidental.alter()));
3640 }
3641 element.sub_text(
3642 "octave",
3643 musicxml_octave(pitch.implicit_octave()).to_string(),
3644 );
3645 element
3646}
3647
3648const KNOWN_ACCIDENTALS: [&str; 40] = [
3651 "natural",
3652 "sharp",
3653 "double-sharp",
3654 "triple-sharp",
3655 "quadruple-sharp",
3656 "flat",
3657 "double-flat",
3658 "triple-flat",
3659 "quadruple-flat",
3660 "half-sharp",
3661 "one-and-a-half-sharp",
3662 "half-flat",
3663 "one-and-a-half-flat",
3664 "double-sharp-down",
3665 "double-sharp-up",
3666 "flat-flat-down",
3667 "flat-flat-up",
3668 "arrow-down",
3669 "arrow-up",
3670 "other",
3671 "sharp-down",
3672 "sharp-up",
3673 "natural-down",
3674 "natural-up",
3675 "flat-down",
3676 "flat-up",
3677 "slash-quarter-sharp",
3678 "slash-sharp",
3679 "slash-flat",
3680 "double-slash-flat",
3681 "sharp-1",
3682 "sharp-2",
3683 "sharp-3",
3684 "sharp-5",
3685 "flat-1",
3686 "flat-2",
3687 "flat-3",
3688 "flat-4",
3689 "sori",
3690 "koron",
3691];
3692
3693fn accidental_element(accidental: &Accidental, tag: &'static str) -> Result<Element> {
3695 let name = match accidental.name() {
3696 "half-sharp" => "quarter-sharp",
3697 "one-and-a-half-sharp" => "three-quarters-sharp",
3698 "half-flat" => "quarter-flat",
3699 "one-and-a-half-flat" => "three-quarters-flat",
3700 "double-flat" => "flat-flat",
3701 other if KNOWN_ACCIDENTALS.contains(&other) => other,
3702 _ => "other",
3703 };
3704 let mut element = Element::with_text(tag, name);
3705 let style = accidental.display_style();
3706 if matches!(style, "parentheses" | "both") {
3707 element.set("parentheses", "yes");
3708 }
3709 if matches!(style, "bracket" | "both") {
3710 element.set("bracket", "yes");
3711 }
3712 if let Some(color) = accidental.color() {
3713 element.set("color", normalize_color(color)?);
3714 }
3715 Ok(element)
3716}
3717
3718fn tie_elements(tie: &Tie) -> Vec<Element> {
3720 tie_like("tie", tie)
3721}
3722
3723fn tie_like(tag: &'static str, tie: &Tie) -> Vec<Element> {
3724 let continues = tie.tie_type() == crate::notation::TieType::Continue;
3725 let mut first = Element::new(tag);
3726 first.set(
3727 "type",
3728 if continues {
3729 "stop"
3730 } else {
3731 tie.tie_type().as_str()
3732 },
3733 );
3734 let mut out = vec![first];
3735 if continues {
3736 let mut second = Element::new(tag);
3737 second.set("type", "start");
3738 out.push(second);
3739 }
3740 out
3741}
3742
3743fn tied_elements(tie: &Tie) -> Vec<Element> {
3745 use crate::notation::{TieStyle, TieType};
3746 if tie.style() == TieStyle::Hidden {
3747 return Vec::new();
3748 }
3749 let mut out = tie_like("tied", tie);
3750 let last = out.last_mut().expect("a tie writes at least one element");
3751 if tie.style() != TieStyle::Normal && tie.tie_type() != TieType::Stop {
3752 last.set("line-type", tie.style().as_str());
3753 }
3754 if let Some(placement) = tie.placement() {
3755 last.set("placement", placement.as_str());
3756 last.set(
3757 "orientation",
3758 match placement {
3759 Placement::Above => "over",
3760 Placement::Below => "under",
3761 },
3762 );
3763 }
3764 out
3765}
3766
3767fn notehead_for(note: &Note, chord: Option<&Chord>) -> Result<Option<Element>> {
3769 let marked =
3770 |notehead: Notehead, parenthesis: bool, fill: Option<bool>, color: Option<&str>| {
3771 notehead != Notehead::Normal || parenthesis || fill.is_some() || color.is_some()
3772 };
3773 let (notehead, parenthesis, fill, color) = if marked(
3774 note.notehead(),
3775 note.notehead_parenthesis(),
3776 note.notehead_fill(),
3777 note.color(),
3778 ) {
3779 (
3780 note.notehead(),
3781 note.notehead_parenthesis(),
3782 note.notehead_fill(),
3783 note.color(),
3784 )
3785 } else if let Some(chord) = chord
3786 && marked(
3787 chord.notehead(),
3788 chord.notehead_parenthesis(),
3789 chord.notehead_fill(),
3790 chord.color(),
3791 )
3792 {
3793 (
3794 chord.notehead(),
3795 chord.notehead_parenthesis(),
3796 chord.notehead_fill(),
3797 chord.color(),
3798 )
3799 } else {
3800 return Ok(None);
3801 };
3802 let mut element = Element::with_text("notehead", notehead.as_str());
3803 if let Some(fill) = fill {
3804 element.set("filled", yes_no(fill));
3805 }
3806 element.set("parentheses", yes_no(parenthesis));
3807 if let Some(color) = color {
3808 element.set("color", normalize_color(color)?);
3809 }
3810 Ok(Some(element))
3811}
3812
3813fn yes_no(value: bool) -> &'static str {
3814 if value { "yes" } else { "no" }
3815}
3816
3817fn beam_elements(beams: &Beams) -> Result<Vec<Element>> {
3819 beams.beams().iter().map(beam_element).collect()
3820}
3821
3822fn beam_element(beam: &Beam) -> Result<Element> {
3824 let text = match beam.beam_type() {
3825 Some(BeamType::Start) => "begin",
3826 Some(BeamType::Continue) => "continue",
3827 Some(BeamType::Stop) => "end",
3828 Some(BeamType::PartialBeam) => match beam.direction() {
3829 Some(BeamDirection::Left) => "backward hook",
3830 Some(BeamDirection::Right) => "forward hook",
3831 None => {
3832 return Err(export_error(
3833 "partial beam defined without a proper direction set (set to None)",
3834 ));
3835 }
3836 },
3837 None => {
3838 return Err(export_error("unexpected beam type encountered (None)"));
3839 }
3840 };
3841 let mut element = Element::with_text("beam", text);
3842 element.set(
3843 "number",
3844 beam.number()
3845 .map_or_else(|| "None".to_string(), |number| number.to_string()),
3846 );
3847 Ok(element)
3848}
3849
3850fn lyric_element(lyric: &Lyric) -> Result<Element> {
3852 let mut element = Element::new("lyric");
3853 let write_syllable = |element: &mut Element, lyric: &Lyric| {
3854 if let Some(syllabic) = lyric.explicit_syllabic() {
3855 element.sub_text("syllabic", syllabic.as_str());
3856 }
3857 element.sub_text("text", lyric.explicit_text().unwrap_or_default());
3858 };
3859 if lyric.is_composite() {
3860 for (index, component) in lyric.components().iter().enumerate() {
3861 if component.is_composite() {
3862 continue;
3863 }
3864 if index >= 1 {
3865 let elision = element.sub("elision");
3866 if !component.elision_before().is_empty() {
3867 elision.set_text(component.elision_before());
3868 }
3869 }
3870 write_syllable(&mut element, component);
3871 }
3872 } else {
3873 write_syllable(&mut element, lyric);
3874 }
3875 element.set("name", lyric.identifier());
3876 element.set("number", lyric.number().to_string());
3877 if let Some(justify) = lyric.justify() {
3878 element.set("justify", justify.as_str());
3879 }
3880 if let Some(placement) = lyric.placement() {
3881 element.set("placement", placement.as_str());
3882 }
3883 if lyric.is_hidden() {
3884 element.set("print-object", "no");
3885 }
3886 if let Some(color) = lyric.color() {
3887 element.set("color", normalize_color(color)?);
3888 }
3889 Ok(element)
3890}
3891
3892fn key_element(element: &StreamElement) -> Option<Result<Element>> {
3894 match element {
3895 StreamElement::KeySignature(signature) => Some(key_signature_element(signature, None)),
3896 StreamElement::Key(key) => {
3897 let mut signature = key.key_signature();
3898 signature.set_color(key.color().map(str::to_string));
3899 Some(key_signature_element(&signature, Some(key.mode())))
3900 }
3901 _ => None,
3902 }
3903}
3904
3905fn key_signature_element(key: &KeySignature, mode: Option<&str>) -> Result<Element> {
3907 let mut element = Element::new("key");
3908 if let Some(color) = key.color() {
3909 element.set("color", normalize_color(color)?);
3910 }
3911 let altered = key.altered_pitches()?;
3912 match (!key.is_non_traditional()).then_some(key.sharps()) {
3913 Some(sharps) => {
3914 element.sub_text("fifths", sharps.to_string());
3915 if let Some(mode) = mode {
3916 element.sub_text("mode", mode);
3917 }
3918 }
3919 None => {
3920 for pitch in &altered {
3921 element.sub_text("key-step", pitch.step().as_char().to_string());
3922 let alter = pitch.written_accidental().map_or(0.0, Accidental::alter);
3923 element.sub_text("key-alter", num_to_int_or_float(alter));
3924 }
3925 }
3926 }
3927 for (index, pitch) in altered.iter().enumerate() {
3928 if let Some(octave) = pitch.octave() {
3929 let mut octave_element = Element::with_text("key-octave", octave.to_string());
3930 octave_element.set("number", (index + 1).to_string());
3931 element.push(octave_element);
3932 }
3933 }
3934 Ok(element)
3935}
3936
3937fn time_signature_element(meter: &TimeSignature) -> Result<Element> {
3939 let mut element = Element::new("time");
3940 if let Some(color) = meter.color() {
3941 element.set("color", normalize_color(color)?);
3942 }
3943 let parts: Vec<(String, String)> = meter
3944 .display_sequence()
3945 .flattened()
3946 .iter()
3947 .map(|part| (part.numerator().to_string(), part.denominator().to_string()))
3948 .collect();
3949 let parts = if meter.summed_numerator() {
3950 fraction_to_slash_mixed(parts)
3951 } else {
3952 parts
3953 };
3954 for (beats, beat_type) in parts {
3955 element.sub_text("beats", beats);
3956 element.sub_text("beat-type", beat_type);
3957 }
3958 if meter.symbolize_denominator() {
3959 element.set("symbol", "note");
3960 } else if let Some(symbol) = meter.symbol() {
3961 element.set("symbol", symbol);
3962 }
3963 if meter.is_hidden() {
3964 element.set("print-object", "no");
3965 }
3966 Ok(element)
3967}
3968
3969fn fraction_to_slash_mixed(parts: Vec<(String, String)>) -> Vec<(String, String)> {
3972 let mut out: Vec<(String, String)> = Vec::new();
3973 for (numerator, denominator) in parts {
3974 match out.last_mut() {
3975 Some((numerators, last)) if *last == denominator => {
3976 numerators.push('+');
3977 numerators.push_str(&numerator);
3978 }
3979 _ => out.push((numerator, denominator)),
3980 }
3981 }
3982 out
3983}
3984
3985fn clef_element(clef: &Clef) -> Result<Element> {
3987 let mut element = Element::new("clef");
3988 if let Some(color) = clef.color() {
3989 element.set("color", normalize_color(color)?);
3990 }
3991 element.sub_text("sign", clef.sign().unwrap_or("G"));
3992 if let Some(line) = clef.line() {
3993 element.sub_text("line", line.to_string());
3994 }
3995 if clef.octave_change() != 0 {
3996 element.sub_text("clef-octave-change", clef.octave_change().to_string());
3997 }
3998 Ok(element)
3999}
4000
4001fn transpose_element(interval: &Interval) -> Element {
4003 let generic = interval.generic().directed();
4004 let steps = generic.abs() - 1;
4005 let (mut octave_shift, mut diatonic) = (steps / 7, steps % 7);
4006 let mut chromatic = (interval.semitones().abs() as i64) % 12;
4007 if generic < 0 {
4008 diatonic = -diatonic;
4009 octave_shift = -octave_shift;
4010 chromatic = -chromatic;
4011 }
4012 let mut element = Element::new("transpose");
4013 element.sub_text("diatonic", diatonic.to_string());
4014 element.sub_text("chromatic", chromatic.to_string());
4015 if octave_shift != 0 {
4016 element.sub_text("octave-change", octave_shift.to_string());
4017 }
4018 element
4019}
4020
4021const DYNAMIC_MARKS: [&str; 27] = [
4023 "p",
4024 "pp",
4025 "ppp",
4026 "pppp",
4027 "ppppp",
4028 "pppppp",
4029 "f",
4030 "ff",
4031 "fff",
4032 "ffff",
4033 "fffff",
4034 "ffffff",
4035 "mp",
4036 "mf",
4037 "sf",
4038 "sfp",
4039 "sfpp",
4040 "fp",
4041 "rf",
4042 "rfz",
4043 "sfz",
4044 "sffz",
4045 "fz",
4046 "n",
4047 "pf",
4048 "sfzp",
4049 "other-dynamics",
4050];
4051
4052fn dynamic_tag(mark: &str) -> &'static str {
4054 DYNAMIC_MARKS
4055 .iter()
4056 .find(|known| **known == mark)
4057 .copied()
4058 .unwrap_or("other-dynamics")
4059}
4060
4061fn is_fraction_offset(offset: FloatType) -> bool {
4065 let scaled = offset * 32768.0;
4066 scaled.fract() != 0.0
4067}
4068
4069fn normalize_color(color: &str) -> Result<String> {
4072 if color.is_empty() {
4073 return Ok(String::new());
4074 }
4075 if color.contains('#') {
4076 return Ok(color.to_uppercase());
4077 }
4078 css_color_hex(color)
4079 .map(str::to_string)
4080 .ok_or_else(|| export_error(format!("{color:?} is not a CSS colour name")))
4081}
4082
4083pub fn css_color_hex(name: &str) -> Option<&'static str> {
4094 let name = name.to_ascii_lowercase();
4095 CSS_COLORS
4096 .binary_search_by(|(known, _)| known.cmp(&name.as_str()))
4097 .ok()
4098 .map(|index| CSS_COLORS[index].1)
4099}
4100
4101pub const CSS_COLORS: [(&str, &str); 147] = [
4104 ("aliceblue", "#F0F8FF"),
4105 ("antiquewhite", "#FAEBD7"),
4106 ("aqua", "#00FFFF"),
4107 ("aquamarine", "#7FFFD4"),
4108 ("azure", "#F0FFFF"),
4109 ("beige", "#F5F5DC"),
4110 ("bisque", "#FFE4C4"),
4111 ("black", "#000000"),
4112 ("blanchedalmond", "#FFEBCD"),
4113 ("blue", "#0000FF"),
4114 ("blueviolet", "#8A2BE2"),
4115 ("brown", "#A52A2A"),
4116 ("burlywood", "#DEB887"),
4117 ("cadetblue", "#5F9EA0"),
4118 ("chartreuse", "#7FFF00"),
4119 ("chocolate", "#D2691E"),
4120 ("coral", "#FF7F50"),
4121 ("cornflowerblue", "#6495ED"),
4122 ("cornsilk", "#FFF8DC"),
4123 ("crimson", "#DC143C"),
4124 ("cyan", "#00FFFF"),
4125 ("darkblue", "#00008B"),
4126 ("darkcyan", "#008B8B"),
4127 ("darkgoldenrod", "#B8860B"),
4128 ("darkgray", "#A9A9A9"),
4129 ("darkgreen", "#006400"),
4130 ("darkgrey", "#A9A9A9"),
4131 ("darkkhaki", "#BDB76B"),
4132 ("darkmagenta", "#8B008B"),
4133 ("darkolivegreen", "#556B2F"),
4134 ("darkorange", "#FF8C00"),
4135 ("darkorchid", "#9932CC"),
4136 ("darkred", "#8B0000"),
4137 ("darksalmon", "#E9967A"),
4138 ("darkseagreen", "#8FBC8F"),
4139 ("darkslateblue", "#483D8B"),
4140 ("darkslategray", "#2F4F4F"),
4141 ("darkslategrey", "#2F4F4F"),
4142 ("darkturquoise", "#00CED1"),
4143 ("darkviolet", "#9400D3"),
4144 ("deeppink", "#FF1493"),
4145 ("deepskyblue", "#00BFFF"),
4146 ("dimgray", "#696969"),
4147 ("dimgrey", "#696969"),
4148 ("dodgerblue", "#1E90FF"),
4149 ("firebrick", "#B22222"),
4150 ("floralwhite", "#FFFAF0"),
4151 ("forestgreen", "#228B22"),
4152 ("fuchsia", "#FF00FF"),
4153 ("gainsboro", "#DCDCDC"),
4154 ("ghostwhite", "#F8F8FF"),
4155 ("gold", "#FFD700"),
4156 ("goldenrod", "#DAA520"),
4157 ("gray", "#808080"),
4158 ("green", "#008000"),
4159 ("greenyellow", "#ADFF2F"),
4160 ("grey", "#808080"),
4161 ("honeydew", "#F0FFF0"),
4162 ("hotpink", "#FF69B4"),
4163 ("indianred", "#CD5C5C"),
4164 ("indigo", "#4B0082"),
4165 ("ivory", "#FFFFF0"),
4166 ("khaki", "#F0E68C"),
4167 ("lavender", "#E6E6FA"),
4168 ("lavenderblush", "#FFF0F5"),
4169 ("lawngreen", "#7CFC00"),
4170 ("lemonchiffon", "#FFFACD"),
4171 ("lightblue", "#ADD8E6"),
4172 ("lightcoral", "#F08080"),
4173 ("lightcyan", "#E0FFFF"),
4174 ("lightgoldenrodyellow", "#FAFAD2"),
4175 ("lightgray", "#D3D3D3"),
4176 ("lightgreen", "#90EE90"),
4177 ("lightgrey", "#D3D3D3"),
4178 ("lightpink", "#FFB6C1"),
4179 ("lightsalmon", "#FFA07A"),
4180 ("lightseagreen", "#20B2AA"),
4181 ("lightskyblue", "#87CEFA"),
4182 ("lightslategray", "#778899"),
4183 ("lightslategrey", "#778899"),
4184 ("lightsteelblue", "#B0C4DE"),
4185 ("lightyellow", "#FFFFE0"),
4186 ("lime", "#00FF00"),
4187 ("limegreen", "#32CD32"),
4188 ("linen", "#FAF0E6"),
4189 ("magenta", "#FF00FF"),
4190 ("maroon", "#800000"),
4191 ("mediumaquamarine", "#66CDAA"),
4192 ("mediumblue", "#0000CD"),
4193 ("mediumorchid", "#BA55D3"),
4194 ("mediumpurple", "#9370DB"),
4195 ("mediumseagreen", "#3CB371"),
4196 ("mediumslateblue", "#7B68EE"),
4197 ("mediumspringgreen", "#00FA9A"),
4198 ("mediumturquoise", "#48D1CC"),
4199 ("mediumvioletred", "#C71585"),
4200 ("midnightblue", "#191970"),
4201 ("mintcream", "#F5FFFA"),
4202 ("mistyrose", "#FFE4E1"),
4203 ("moccasin", "#FFE4B5"),
4204 ("navajowhite", "#FFDEAD"),
4205 ("navy", "#000080"),
4206 ("oldlace", "#FDF5E6"),
4207 ("olive", "#808000"),
4208 ("olivedrab", "#6B8E23"),
4209 ("orange", "#FFA500"),
4210 ("orangered", "#FF4500"),
4211 ("orchid", "#DA70D6"),
4212 ("palegoldenrod", "#EEE8AA"),
4213 ("palegreen", "#98FB98"),
4214 ("paleturquoise", "#AFEEEE"),
4215 ("palevioletred", "#DB7093"),
4216 ("papayawhip", "#FFEFD5"),
4217 ("peachpuff", "#FFDAB9"),
4218 ("peru", "#CD853F"),
4219 ("pink", "#FFC0CB"),
4220 ("plum", "#DDA0DD"),
4221 ("powderblue", "#B0E0E6"),
4222 ("purple", "#800080"),
4223 ("red", "#FF0000"),
4224 ("rosybrown", "#BC8F8F"),
4225 ("royalblue", "#4169E1"),
4226 ("saddlebrown", "#8B4513"),
4227 ("salmon", "#FA8072"),
4228 ("sandybrown", "#F4A460"),
4229 ("seagreen", "#2E8B57"),
4230 ("seashell", "#FFF5EE"),
4231 ("sienna", "#A0522D"),
4232 ("silver", "#C0C0C0"),
4233 ("skyblue", "#87CEEB"),
4234 ("slateblue", "#6A5ACD"),
4235 ("slategray", "#708090"),
4236 ("slategrey", "#708090"),
4237 ("snow", "#FFFAFA"),
4238 ("springgreen", "#00FF7F"),
4239 ("steelblue", "#4682B4"),
4240 ("tan", "#D2B48C"),
4241 ("teal", "#008080"),
4242 ("thistle", "#D8BFD8"),
4243 ("tomato", "#FF6347"),
4244 ("turquoise", "#40E0D0"),
4245 ("violet", "#EE82EE"),
4246 ("wheat", "#F5DEB3"),
4247 ("white", "#FFFFFF"),
4248 ("whitesmoke", "#F5F5F5"),
4249 ("yellow", "#FFFF00"),
4250 ("yellowgreen", "#9ACD32"),
4251];
4252
4253#[cfg(test)]
4254mod tests {
4255 use super::*;
4259 use crate::notation::{BeamType, Syllabic, TieStyle, TieType};
4260 use crate::volume::Volume;
4261
4262 static NO_BASES: std::sync::LazyLock<std::collections::HashMap<usize, u32>> =
4263 std::sync::LazyLock::new(std::collections::HashMap::new);
4264
4265 fn with_measure<T>(run: impl FnOnce(&mut MeasureExporter<'_, '_>) -> T) -> T {
4266 let stream = Stream::with_kind(StreamKind::Measure);
4267 let mut context = PartContext {
4268 instruments: &[],
4269 own_instruments: Vec::new(),
4270 own_clefs: Vec::new(),
4271 meter: None,
4272 last_divisions: None,
4273 spanners: Vec::new(),
4274 clef: None,
4275 previous: None,
4276 voice_bases: &NO_BASES,
4277 modulations: Vec::new(),
4278 };
4279 let mut exporter = MeasureExporter::new(&stream, 0.0, &mut context);
4280 run(&mut exporter)
4281 }
4282
4283 #[test]
4284 fn a_segno_and_a_coda_are_signs_and_the_rest_are_words() {
4285 let sign = |kind| {
4287 with_measure(|exporter| {
4288 exporter
4289 .repeat_expression_element(&RepeatExpression::new(kind), 0.0)
4290 .dump()
4291 })
4292 };
4293 assert_eq!(
4294 sign(RepeatExpressionKind::Segno),
4295 "<direction>
4296 <direction-type>
4297 <segno default-y=\"20\" />
4298 </direction-type>
4299</direction>"
4300 );
4301 assert_eq!(
4302 sign(RepeatExpressionKind::Coda),
4303 "<direction>
4304 <direction-type>
4305 <coda default-y=\"20\" />
4306 </direction-type>
4307</direction>"
4308 );
4309 assert_eq!(
4310 sign(RepeatExpressionKind::Fine),
4311 "<direction>
4312 <direction-type>
4313 <words justify=\"right\">fine</words>
4314 </direction-type>
4315</direction>"
4316 );
4317 }
4318
4319 #[test]
4320 fn a_pedal_goes_down_and_comes_up() {
4321 use crate::spanner::Pedal;
4322 let pedal = |pedal_type, form| {
4324 Spanner::pedal_mark(
4325 Pedal {
4326 pedal_type,
4327 form,
4328 abbreviated: false,
4329 },
4330 vec![0, 1],
4331 )
4332 };
4333 let symbol = pedal(Some(PedalType::Sustain), Some(PedalForm::Symbol));
4334 assert_eq!(
4335 pedal_element(&symbol, 1, true).dump(),
4336 "<pedal number=\"1\" sign=\"yes\" type=\"start\" />"
4337 );
4338 assert_eq!(
4339 pedal_element(&symbol, 1, false).dump(),
4340 "<pedal number=\"1\" sign=\"yes\" type=\"stop\" />"
4341 );
4342 let line = pedal(Some(PedalType::Sostenuto), Some(PedalForm::Line));
4343 assert_eq!(
4344 pedal_element(&line, 2, true).dump(),
4345 "<pedal line=\"yes\" number=\"2\" type=\"sostenuto\" />"
4346 );
4347 assert_eq!(
4348 pedal_element(&line, 2, false).dump(),
4349 "<pedal line=\"yes\" number=\"2\" type=\"stop\" />"
4350 );
4351 }
4352
4353 #[test]
4354 fn glissandi_a_tremolo_and_a_pedal_with_a_bounce_are_written_as_music21_writes_them() {
4355 use crate::spanner::{Glissando, Pedal};
4356 let bar = |number, names: [&str; 4]| {
4358 let mut measure = Stream::with_kind(StreamKind::Measure);
4359 measure.set_number(number);
4360 for name in names {
4361 measure.push(Note::from_name(name).unwrap());
4362 }
4363 measure
4364 };
4365 let mut first = bar(1, ["C4", "D4", "E4", "F4"]);
4366 first.insert(3.0, PedalObject::new(PedalObjectKind::Bounce));
4367 let mut part = Stream::with_kind(StreamKind::Part);
4368 part.push(first);
4369 part.push(bar(2, ["G4", "A4", "B4", "C5"]));
4370 let mut score = Stream::with_kind(StreamKind::Score);
4371 score.push(part);
4372 let kinds: Vec<bool> = score
4375 .leaves()
4376 .iter()
4377 .map(|(_, element)| matches!(element, StreamElement::PedalObject(_)))
4378 .collect();
4379 assert_eq!(kinds.iter().position(|bounce| *bounce), Some(4));
4380
4381 let pedal = Spanner::pedal_mark(
4384 Pedal {
4385 pedal_type: Some(PedalType::Sustain),
4386 form: Some(PedalForm::SymbolLine),
4387 abbreviated: false,
4388 },
4389 vec![1, 7, 4],
4390 );
4391 score.add_spanner(pedal);
4392 score.add_spanner(Spanner::glissando(vec![2, 3]));
4393 let mut slide = Spanner::glissando(vec![5, 6]);
4394 slide.set_glissando_details(Some(Glissando {
4395 slide_type: SlideType::Continuous,
4396 label: Some("gl.".to_string()),
4397 }));
4398 score.add_spanner(slide);
4399 let mut tremolo = Spanner::tremolo(3, vec![7, 8]).unwrap();
4400 tremolo.set_placement(Some(Placement::Above));
4401 score.add_spanner(tremolo);
4402
4403 let written = to_musicxml(&score, &ExportOptions::default()).unwrap();
4404 let direction = |pedal: &str, offset: &str| {
4405 format!(
4406 " <direction>\n <direction-type>\n {pedal}\n \
4407 </direction-type>\n{offset} </direction>\n"
4408 )
4409 };
4410 assert!(written.contains(&format!(
4413 "{}{}",
4414 direction("<pedal number=\"1\" sign=\"yes\" type=\"start\" />", ""),
4415 direction(
4416 "<pedal line=\"yes\" type=\"resume\" />",
4417 " <offset sound=\"yes\">-10080</offset>\n"
4418 )
4419 )));
4420 assert!(written.contains(&format!(
4422 "{}{}",
4423 direction("<pedal line=\"yes\" type=\"change\" />", ""),
4424 direction("<pedal line=\"yes\" number=\"1\" type=\"stop\" />", "")
4425 )));
4426 for notation in [
4427 "<glissando line-type=\"wavy\" number=\"1\" type=\"start\" />",
4428 "<glissando line-type=\"wavy\" number=\"1\" type=\"stop\" />",
4429 "<slide line-type=\"wavy\" number=\"2\" type=\"start\">gl.</slide>",
4430 "<slide line-type=\"wavy\" number=\"2\" type=\"stop\" />",
4431 "<tremolo placement=\"above\" type=\"start\">3</tremolo>",
4432 "<tremolo type=\"stop\">3</tremolo>",
4433 ] {
4434 assert!(written.contains(notation), "{notation} in\n{written}");
4435 }
4436 }
4437
4438 #[test]
4439 fn a_bounce_is_written_as_its_pedal_is_drawn() {
4440 use crate::spanner::Pedal;
4441 let written = |form, pedal_type, kind| {
4443 let mut measure = Stream::with_kind(StreamKind::Measure);
4444 measure.set_number(1);
4445 measure.push(Note::from_name("C4").unwrap());
4446 measure.insert(1.0, PedalObject::new(kind));
4447 measure.push(Note::from_name("D4").unwrap());
4448 let mut part = Stream::with_kind(StreamKind::Part);
4449 part.push(measure);
4450 part.add_spanner(Spanner::pedal_mark(
4451 Pedal {
4452 pedal_type,
4453 form: Some(form),
4454 abbreviated: false,
4455 },
4456 vec![0, 1, 2],
4457 ));
4458 let mut score = Stream::with_kind(StreamKind::Score);
4459 score.push(part);
4460 let document = to_musicxml(&score, &ExportOptions::default()).unwrap();
4461 document
4462 .lines()
4463 .map(str::trim)
4464 .filter(|line| line.starts_with("<pedal") && !line.contains("number"))
4465 .collect::<Vec<_>>()
4466 .join("")
4467 };
4468 let sustain = Some(PedalType::Sustain);
4469 assert_eq!(
4470 written(PedalForm::Line, sustain, PedalObjectKind::Bounce),
4471 "<pedal line=\"yes\" type=\"change\" />"
4472 );
4473 assert_eq!(
4474 written(PedalForm::Symbol, sustain, PedalObjectKind::Bounce),
4475 "<pedal sign=\"yes\" type=\"stop\" /><pedal sign=\"yes\" type=\"start\" />"
4476 );
4477 assert_eq!(
4478 written(
4479 PedalForm::SymbolAlt,
4480 Some(PedalType::Sostenuto),
4481 PedalObjectKind::Bounce
4482 ),
4483 "<pedal sign=\"yes\" type=\"sostenuto\" />"
4484 );
4485 assert_eq!(
4486 written(
4487 PedalForm::Symbol,
4488 Some(PedalType::Soft),
4489 PedalObjectKind::Bounce
4490 ),
4491 "<pedal sign=\"yes\" type=\"stop\" /><pedal sign=\"yes\" type=\"sustain\" />"
4492 );
4493 assert_eq!(
4494 written(PedalForm::Line, sustain, PedalObjectKind::GapStart),
4495 "<pedal line=\"yes\" type=\"discontinue\" />"
4496 );
4497 assert_eq!(
4498 written(PedalForm::Symbol, sustain, PedalObjectKind::GapEnd),
4499 "<pedal sign=\"yes\" type=\"resume\" />"
4500 );
4501 }
4502
4503 #[test]
4504 fn a_mark_at_a_triplet_offset_always_says_its_offset() {
4505 with_measure(|exporter| {
4508 exporter.offset_in_measure = 8.0 / 3.0;
4509 let written = exporter.offset_element(8.0 / 3.0, true).unwrap();
4510 assert_eq!(written.dump(), "<offset sound=\"yes\">0</offset>");
4511 exporter.offset_in_measure = 2.5;
4512 assert!(exporter.offset_element(2.5, true).is_none());
4513 });
4514 }
4515
4516 #[test]
4517 fn a_stroke_is_written_where_it_is_displayed() {
4518 let stroke = crate::percussion::Unpitched::from_display_name("D5")
4520 .unwrap()
4521 .with_duration(Duration::quarter());
4522 let written = with_measure(|exporter| {
4523 exporter
4524 .written_note_element(stroke.written(), 0, None, None, true)
4525 .unwrap()
4526 });
4527 assert_eq!(
4528 written.dump(),
4529 [
4530 "<note>",
4531 " <unpitched>",
4532 " <display-step>D</display-step>",
4533 " <display-octave>5</display-octave>",
4534 " </unpitched>",
4535 " <duration>10080</duration>",
4536 " <type>quarter</type>",
4537 "</note>",
4538 ]
4539 .join(
4540 "
4541"
4542 )
4543 );
4544 }
4545
4546 #[test]
4547 fn a_colour_is_written_in_hex() {
4548 assert_eq!(normalize_color("").unwrap(), "");
4549 assert_eq!(normalize_color("red").unwrap(), "#FF0000");
4550 assert_eq!(normalize_color("#00ff00").unwrap(), "#00FF00");
4551 assert!(normalize_color("reddish").is_err());
4552 }
4553
4554 #[test]
4555 fn a_note_writes_as_music21_writes_it() {
4556 let mut note = Note::from_name("D#5")
4557 .unwrap()
4558 .with_duration(Duration::new(3.0).unwrap());
4559 note.set_volume(Some(Volume::from_velocity_scalar(0.5).unwrap()));
4560 note.set_color(Some("#C0C0C0".to_string()));
4561 let xml =
4562 with_measure(|exporter| exporter.note_element(¬e, 0, None, None).unwrap()).dump();
4563 assert_eq!(
4564 xml,
4565 "<note color=\"#C0C0C0\" dynamics=\"70.56\">
4566 <pitch>
4567 <step>D</step>
4568 <alter>1</alter>
4569 <octave>5</octave>
4570 </pitch>
4571 <duration>30240</duration>
4572 <type>half</type>
4573 <dot />
4574 <accidental>sharp</accidental>
4575 <notehead color=\"#C0C0C0\" parentheses=\"no\">normal</notehead>
4576</note>"
4577 );
4578 }
4579
4580 #[test]
4581 fn a_chord_writes_a_note_for_each_of_its_notes() {
4582 let chord = Chord::new("A-2 D3 E4")
4583 .unwrap()
4584 .with_duration(Duration::half());
4585 let root = with_measure(|exporter| {
4586 exporter.chord_elements(&chord, None).unwrap();
4587 exporter.root.clone()
4588 });
4589 assert_eq!(
4590 root.dump(),
4591 "<measure>
4592 <note>
4593 <pitch>
4594 <step>A</step>
4595 <alter>-1</alter>
4596 <octave>2</octave>
4597 </pitch>
4598 <duration>20160</duration>
4599 <type>half</type>
4600 <accidental>flat</accidental>
4601 </note>
4602 <note>
4603 <chord />
4604 <pitch>
4605 <step>D</step>
4606 <octave>3</octave>
4607 </pitch>
4608 <duration>20160</duration>
4609 <type>half</type>
4610 </note>
4611 <note>
4612 <chord />
4613 <pitch>
4614 <step>E</step>
4615 <octave>4</octave>
4616 </pitch>
4617 <duration>20160</duration>
4618 <type>half</type>
4619 </note>
4620</measure>"
4621 );
4622 }
4623
4624 #[test]
4628 fn a_rest_says_whether_it_is_a_measure_rest() {
4629 let mut measure = Stream::with_kind(StreamKind::Measure);
4630 measure.insert(0.0, TimeSignature::new(4, 4).unwrap());
4631 let mut context = PartContext {
4632 instruments: &[],
4633 own_instruments: Vec::new(),
4634 own_clefs: Vec::new(),
4635 meter: None,
4636 last_divisions: None,
4637 spanners: Vec::new(),
4638 clef: None,
4639 previous: None,
4640 voice_bases: &NO_BASES,
4641 modulations: Vec::new(),
4642 };
4643 let exporter = MeasureExporter::new(&measure, 0.0, &mut context);
4644 let written = |length: Duration, said: FullMeasure| {
4645 let mut rest = Rest::new(length);
4646 rest.set_full_measure(said);
4647 exporter.rest_element(&rest, 0.0, None).unwrap().dump()
4648 };
4649 let whole = || Duration::new(4.0).unwrap();
4650 assert!(!written(whole(), FullMeasure::No).contains("measure=\"yes\""));
4651 assert!(written(whole(), FullMeasure::Auto).contains("measure=\"yes\""));
4652 assert!(written(Duration::half(), FullMeasure::Always).contains("measure=\"yes\""));
4653 assert!(written(Duration::half(), FullMeasure::Yes).contains("measure=\"yes\""));
4654 }
4655
4656 #[test]
4657 fn a_rest_filling_its_bar_is_a_measure_rest() {
4658 let mut measure = Stream::with_kind(StreamKind::Measure);
4659 measure.insert(0.0, TimeSignature::new(4, 4).unwrap());
4660 let rest = Rest::new(Duration::half());
4661 let mut context = PartContext {
4662 instruments: &[],
4663 own_instruments: Vec::new(),
4664 own_clefs: Vec::new(),
4665 meter: None,
4666 last_divisions: None,
4667 spanners: Vec::new(),
4668 clef: None,
4669 previous: None,
4670 voice_bases: &NO_BASES,
4671 modulations: Vec::new(),
4672 };
4673 let exporter = MeasureExporter::new(&measure, 0.0, &mut context);
4674 assert_eq!(
4675 exporter.rest_element(&rest, 0.0, None).unwrap().dump(),
4676 "<note>\n <rest />\n <duration>20160</duration>\n <type>half</type>\n</note>"
4677 );
4678
4679 let mut measure = Stream::with_kind(StreamKind::Measure);
4680 measure.insert(0.0, TimeSignature::new(2, 4).unwrap());
4681 let exporter = MeasureExporter::new(&measure, 0.0, &mut context);
4682 assert_eq!(
4683 exporter.rest_element(&rest, 0.0, None).unwrap().dump(),
4684 "<note>\n <rest measure=\"yes\" />\n <duration>20160</duration>\n</note>"
4685 );
4686 }
4687
4688 #[test]
4689 fn a_rests_colour_is_written_on_its_note() {
4690 let mut rest = Rest::new(Duration::quarter());
4692 rest.set_color(Some("red".to_string()));
4693 rest.set_hidden(true);
4694 let xml = with_measure(|exporter| exporter.rest_element(&rest, 0.0, None).unwrap()).dump();
4695 assert!(
4696 xml.starts_with("<note color=\"#FF0000\" print-object=\"no\" print-spacing=\"yes\">"),
4697 "{xml}"
4698 );
4699 }
4700
4701 #[test]
4702 fn a_triplet_carries_its_time_modification() {
4703 let rest = Rest::new(Duration::new(1.0 / 3.0).unwrap());
4704 let xml = with_measure(|exporter| exporter.rest_element(&rest, 0.0, None).unwrap()).dump();
4705 assert_eq!(
4706 xml,
4707 "<note>
4708 <rest />
4709 <duration>3360</duration>
4710 <type>eighth</type>
4711 <time-modification>
4712 <actual-notes>3</actual-notes>
4713 <normal-notes>2</normal-notes>
4714 <normal-type>eighth</normal-type>
4715 </time-modification>
4716</note>"
4717 );
4718 }
4719
4720 #[test]
4721 fn a_duration_needing_a_tie_is_refused() {
4722 let note = Note::from_name("C4")
4723 .unwrap()
4724 .with_duration(Duration::new(5.0).unwrap());
4725 let error =
4726 with_measure(|exporter| exporter.note_element(¬e, 0, None, None).unwrap_err());
4727 assert_eq!(
4728 error,
4729 Error::music_xml(
4730 "Cannot convert complex durations to MusicXML. Try exporting with \
4731 makeNotation=True or manually running splitAtDurations()"
4732 .to_string()
4733 )
4734 );
4735 }
4736
4737 #[test]
4738 fn moving_forward_and_back_counts_divisions() {
4739 let root = with_measure(|exporter| {
4740 exporter.move_forward(1.0);
4741 exporter.move_backward(2.0);
4742 exporter.root.clone()
4743 });
4744 assert_eq!(
4745 root.dump(),
4746 "<measure>
4747 <forward>
4748 <duration>10080</duration>
4749 </forward>
4750 <backup>
4751 <duration>20160</duration>
4752 </backup>
4753</measure>"
4754 );
4755 }
4756
4757 #[test]
4758 fn ties_are_written_twice_over() {
4759 let tie = Tie::new(TieType::Continue);
4760 let written: Vec<String> = tie_elements(&tie).iter().map(Element::dump).collect();
4761 assert_eq!(written, ["<tie type=\"stop\" />", "<tie type=\"start\" />"]);
4762 let tied: Vec<String> = tied_elements(&tie).iter().map(Element::dump).collect();
4763 assert_eq!(tied, ["<tied type=\"stop\" />", "<tied type=\"start\" />"]);
4764 let mut hidden = Tie::new(TieType::Continue);
4765 hidden.set_style(TieStyle::Hidden);
4766 assert!(tied_elements(&hidden).is_empty());
4767 }
4768
4769 #[test]
4770 fn beams_name_their_ends() {
4771 let mut beam = Beam::new(BeamType::Start, None);
4772 beam.set_number(Some(1));
4773 assert_eq!(
4774 beam_element(&beam).unwrap().dump(),
4775 "<beam number=\"1\">begin</beam>"
4776 );
4777 beam.set_beam_type(Some(BeamType::Stop));
4778 assert_eq!(
4779 beam_element(&beam).unwrap().dump(),
4780 "<beam number=\"1\">end</beam>"
4781 );
4782 beam.set_beam_type(Some(BeamType::PartialBeam));
4783 beam.set_direction(Some(BeamDirection::Left));
4784 assert_eq!(
4785 beam_element(&beam).unwrap().dump(),
4786 "<beam number=\"1\">backward hook</beam>"
4787 );
4788 beam.set_direction(None);
4789 assert_eq!(
4790 beam_element(&beam).unwrap_err(),
4791 Error::music_xml(
4792 "partial beam defined without a proper direction set (set to None)".to_string()
4793 )
4794 );
4795 }
4796
4797 #[test]
4798 fn key_signatures_traditional_and_not() {
4799 assert_eq!(
4800 key_signature_element(&KeySignature::new(-3), None)
4801 .unwrap()
4802 .dump(),
4803 "<key>\n <fifths>-3</fifths>\n</key>"
4804 );
4805 let pitches = vec![
4806 Pitch::from_name("C#").unwrap(),
4807 Pitch::from_name("E-4").unwrap(),
4808 ];
4809 assert_eq!(
4810 key_signature_element(&KeySignature::from_altered_pitches(pitches), None)
4811 .unwrap()
4812 .dump(),
4813 "<key>
4814 <key-step>C</key-step>
4815 <key-alter>1</key-alter>
4816 <key-step>E</key-step>
4817 <key-alter>-1</key-alter>
4818 <key-octave number=\"2\">4</key-octave>
4819</key>"
4820 );
4821 }
4822
4823 #[test]
4824 fn a_meter_written_in_parts_writes_each() {
4825 assert_eq!(
4826 time_signature_element(&TimeSignature::new(3, 4).unwrap())
4827 .unwrap()
4828 .dump(),
4829 "<time>\n <beats>3</beats>\n <beat-type>4</beat-type>\n</time>"
4830 );
4831 assert_eq!(
4832 time_signature_element(&TimeSignature::from_ratio_string("3/4+2/4").unwrap())
4833 .unwrap()
4834 .dump(),
4835 "<time>
4836 <beats>3</beats>
4837 <beat-type>4</beat-type>
4838 <beats>2</beats>
4839 <beat-type>4</beat-type>
4840</time>"
4841 );
4842 }
4843
4844 #[test]
4845 fn clefs_write_their_sign_line_and_octave() {
4846 assert_eq!(
4847 clef_element(&Clef::of_kind(crate::clef::ClefKind::GClef))
4848 .unwrap()
4849 .dump(),
4850 "<clef>\n <sign>G</sign>\n</clef>"
4851 );
4852 assert_eq!(
4853 clef_element(&Clef::of_kind(crate::clef::ClefKind::Treble8vbClef))
4854 .unwrap()
4855 .dump(),
4856 "<clef>
4857 <sign>G</sign>
4858 <line>2</line>
4859 <clef-octave-change>-1</clef-octave-change>
4860</clef>"
4861 );
4862 assert_eq!(
4863 clef_element(&Clef::of_kind(crate::clef::ClefKind::PercussionClef))
4864 .unwrap()
4865 .dump(),
4866 "<clef>\n <sign>percussion</sign>\n</clef>"
4867 );
4868 }
4869
4870 #[test]
4871 fn a_transposition_is_steps_semitones_and_octaves() {
4872 let dump = |name: &str| transpose_element(&Interval::from_name(name).unwrap()).dump();
4873 assert_eq!(
4874 dump("P5"),
4875 "<transpose>\n <diatonic>4</diatonic>\n <chromatic>7</chromatic>\n</transpose>"
4876 );
4877 assert_eq!(
4878 dump("A13"),
4879 "<transpose>
4880 <diatonic>5</diatonic>
4881 <chromatic>10</chromatic>
4882 <octave-change>1</octave-change>
4883</transpose>"
4884 );
4885 assert_eq!(
4886 dump("-M9"),
4887 "<transpose>
4888 <diatonic>-1</diatonic>
4889 <chromatic>-2</chromatic>
4890 <octave-change>-1</octave-change>
4891</transpose>"
4892 );
4893 }
4894
4895 #[test]
4896 fn a_dynamic_is_a_direction_with_its_loudness() {
4897 let root = with_measure(|exporter| {
4898 exporter.dynamic_element(&Dynamic::new("ppp"), 1.0);
4899 exporter.root.clone()
4900 });
4901 assert_eq!(
4902 root.dump(),
4903 "<measure>
4904 <direction>
4905 <direction-type>
4906 <dynamics default-x=\"-36\" default-y=\"-80\">
4907 <ppp />
4908 </dynamics>
4909 </direction-type>
4910 <offset sound=\"yes\">10080</offset>
4911 <sound dynamics=\"19\" />
4912 </direction>
4913</measure>"
4914 );
4915 }
4916
4917 #[test]
4918 fn a_metronome_mark_writes_its_beat_and_its_word() {
4919 let mark =
4920 MetronomeMark::with_number_and_text(40.0, "slow").with_referent(Duration::half());
4921 let root = with_measure(|exporter| {
4922 exporter.tempo_elements(&mark, 0.0).unwrap();
4923 exporter.root.clone()
4924 });
4925 assert_eq!(
4926 root.dump(),
4927 "<measure>
4928 <direction>
4929 <direction-type>
4930 <metronome parentheses=\"no\">
4931 <beat-unit>half</beat-unit>
4932 <per-minute>40</per-minute>
4933 </metronome>
4934 </direction-type>
4935 <sound tempo=\"80\" />
4936 </direction>
4937 <direction>
4938 <direction-type>
4939 <words default-y=\"45\" font-weight=\"bold\">slow</words>
4940 </direction-type>
4941 </direction>
4942</measure>"
4943 );
4944
4945 let sounding = MetronomeMark::default().with_number_sounding(60.0);
4946 let root = with_measure(|exporter| {
4947 exporter.tempo_elements(&sounding, 0.0).unwrap();
4948 exporter.root.clone()
4949 });
4950 assert_eq!(
4951 root.dump(),
4952 "<measure>
4953 <direction>
4954 <direction-type>
4955 <words />
4956 </direction-type>
4957 <sound tempo=\"60\" />
4958 </direction>
4959</measure>"
4960 );
4961 }
4962
4963 #[test]
4964 fn a_chord_symbol_is_a_harmony() {
4965 let dump = |figure: &str| {
4966 let symbol = ChordSymbol::parse_music21(figure).unwrap();
4967 with_measure(|exporter| exporter.harmony_element(&symbol, 0.0).unwrap()).dump()
4968 };
4969 assert_eq!(
4971 dump("C7"),
4972 "<harmony>
4973 <root>
4974 <root-step>C</root-step>
4975 </root>
4976 <kind>dominant</kind>
4977</harmony>"
4978 );
4979 assert_eq!(
4980 dump("F sus add 9"),
4981 "<harmony>
4982 <root>
4983 <root-step>F</root-step>
4984 </root>
4985 <kind>suspended-fourth</kind>
4986 <degree>
4987 <degree-value>9</degree-value>
4988 <degree-alter>0</degree-alter>
4989 <degree-type>add</degree-type>
4990 </degree>
4991</harmony>"
4992 );
4993 let no_chord = ChordSymbol::no_chord(None);
4994 assert_eq!(
4995 with_measure(|exporter| exporter.harmony_element(&no_chord, 0.0).unwrap()).dump(),
4996 "<harmony>
4997 <root>
4998 <root-step text=\"\">C</root-step>
4999 </root>
5000 <kind text=\"N.C.\">none</kind>
5001</harmony>"
5002 );
5003 }
5004
5005 #[test]
5006 fn a_lyric_names_its_verse() {
5007 let mut lyric = Lyric::new("hel");
5008 lyric.set_syllabic(Syllabic::Begin);
5009 assert_eq!(
5010 lyric_element(&lyric).unwrap().dump(),
5011 "<lyric name=\"1\" number=\"1\">
5012 <syllabic>begin</syllabic>
5013 <text>hel</text>
5014</lyric>"
5015 );
5016 }
5017
5018 #[test]
5019 fn a_whole_score_writes_as_a_music21_fragment() {
5020 let mut measure = Stream::with_kind(StreamKind::Measure);
5021 measure.set_number(1);
5022 measure.insert(0.0, TimeSignature::new(1, 4).unwrap());
5023 measure.insert(0.0, Clef::treble());
5024 measure.push(Note::from_name("D#4").unwrap());
5025 let mut part = Stream::with_kind(StreamKind::Part);
5026 part.push(measure);
5027 let mut score = Stream::with_kind(StreamKind::Score);
5028 score.push(part);
5029 let options = ExportOptions {
5030 encoding_date: Some("2026-09-28".to_string()),
5031 software: "music21 v.11.0.0".to_string(),
5032 ..ExportOptions::default()
5033 };
5034 assert_eq!(
5035 to_musicxml(&score, &options).unwrap(),
5036 "<?xml version=\"1.0\" encoding=\"utf-8\"?>
5037<!DOCTYPE score-partwise PUBLIC \"-//Recordare//DTD MusicXML 4.0 Partwise//EN\" \"http://www.musicxml.org/dtds/partwise.dtd\">
5038<score-partwise version=\"4.0\">
5039 <movement-title>Music21 Fragment</movement-title>
5040 <identification>
5041 <creator type=\"composer\">Music21</creator>
5042 <encoding>
5043 <encoding-date>2026-09-28</encoding-date>
5044 <software>music21 v.11.0.0</software>
5045 <supports element=\"beam\" type=\"yes\" />
5046 <supports element=\"stem\" type=\"yes\" />
5047 <supports element=\"accidental\" type=\"yes\" />
5048 </encoding>
5049 </identification>
5050 <defaults>
5051 <scaling>
5052 <millimeters>7</millimeters>
5053 <tenths>40</tenths>
5054 </scaling>
5055 </defaults>
5056 <part-list>
5057 <score-part id=\"P1\">
5058 <part-name />
5059 </score-part>
5060 </part-list>
5061 <!--=========================== Part 1 ===========================-->
5062 <part id=\"P1\">
5063 <!--========================= Measure 1 ==========================-->
5064 <measure implicit=\"no\" number=\"1\">
5065 <attributes>
5066 <divisions>10080</divisions>
5067 <time>
5068 <beats>1</beats>
5069 <beat-type>4</beat-type>
5070 </time>
5071 <clef>
5072 <sign>G</sign>
5073 <line>2</line>
5074 </clef>
5075 </attributes>
5076 <note>
5077 <pitch>
5078 <step>D</step>
5079 <alter>1</alter>
5080 <octave>4</octave>
5081 </pitch>
5082 <duration>10080</duration>
5083 <type>quarter</type>
5084 <accidental>sharp</accidental>
5085 </note>
5086 </measure>
5087 </part>
5088</score-partwise>"
5089 );
5090 }
5091
5092 #[test]
5093 fn a_part_that_changes_instrument_says_who_plays_each_note() {
5094 let measure = |number, instrument: &str, stored: Option<&str>| {
5097 let mut measure = Stream::with_kind(StreamKind::Measure);
5098 measure.set_number(number);
5099 measure.insert(0.0, Instrument::of_kind(instrument).unwrap());
5100 let mut note = Note::from_name("C4")
5101 .unwrap()
5102 .with_duration(Duration::quarter());
5103 note.set_stored_instrument(stored.map(|kind| Instrument::of_kind(kind).unwrap()));
5104 measure.insert(0.0, note);
5105 measure
5106 };
5107 let mut part = Stream::with_kind(StreamKind::Part);
5108 part.insert(0.0, measure(1, "Violin", None));
5109 part.insert(1.0, measure(2, "Viola", None));
5110 part.insert(2.0, measure(3, "Viola", Some("Violin")));
5111 let mut score = Stream::with_kind(StreamKind::Score);
5112 score.push(part);
5113 let written = to_musicxml(&score, &ExportOptions::default()).unwrap();
5114 let ids: Vec<&str> = written
5115 .lines()
5116 .filter_map(|line| line.trim().strip_prefix("<instrument id=\""))
5117 .map(|rest| rest.trim_end_matches("\" />"))
5118 .collect();
5119 assert_eq!(ids.len(), 3);
5120 assert_ne!(ids[0], ids[1]);
5121 assert_eq!(ids[0], ids[2]);
5122 for id in &ids {
5123 assert!(written.contains(&format!("<score-instrument id=\"{id}\">")));
5124 }
5125 }
5126
5127 #[test]
5128 fn a_part_without_measures_is_refused() {
5129 let mut part = Stream::with_kind(StreamKind::Part);
5130 part.push(Note::from_name("C4").unwrap());
5131 let mut score = Stream::with_kind(StreamKind::Score);
5132 score.push(part);
5133 assert_eq!(
5134 to_musicxml(&score, &ExportOptions::default()).unwrap_err(),
5135 Error::music_xml(
5136 "Cannot export with makeNotation=False if there are no measures".to_string()
5137 )
5138 );
5139 }
5140
5141 #[test]
5142 fn divider_comments_are_centred_in_sixty_signs() {
5143 let mut root = Element::new("score-partwise");
5144 add_divider_comment(&mut root, "second accidental below");
5145 assert_eq!(
5146 root.dump(),
5147 "<score-partwise>\n <!--================== second accidental below ===================-->\n</score-partwise>"
5148 );
5149 }
5150}