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