Skip to main content

music21/musicxml/
mod.rs

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